Merge "Add IntRange annotation to int parameters where appropriate" into androidx-main
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/FrameTimingMetricValidation.kt
similarity index 95%
rename from benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt
rename to benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/FrameTimingMetricValidation.kt
index 8d2e9a2..e88aea4 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/FrameTimingMetricValidation.kt
@@ -18,7 +18,7 @@
import android.content.Intent
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.JankMetric
+import androidx.benchmark.macro.FrameTimingMetric
import androidx.benchmark.macro.MacrobenchmarkConfig
import androidx.benchmark.macro.MacrobenchmarkRule
import androidx.test.filters.LargeTest
@@ -36,7 +36,7 @@
@LargeTest
@SdkSuppress(minSdkVersion = 29)
@RunWith(Parameterized::class)
-class JankMetricValidation(
+class FrameTimingMetricValidation(
private val compilationMode: CompilationMode
) {
@get:Rule
@@ -54,7 +54,7 @@
fun start() {
val config = MacrobenchmarkConfig(
packageName = PACKAGE_NAME,
- metrics = listOf(JankMetric()),
+ metrics = listOf(FrameTimingMetric()),
compilationMode = compilationMode,
iterations = 10
)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/StartupUtils.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/StartupUtils.kt
index e8c152e..bc58b13 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/StartupUtils.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/StartupUtils.kt
@@ -48,4 +48,4 @@
intent.setPackage(TARGET_PACKAGE)
setupIntent(intent)
launchIntentAndWait(intent)
-}
\ No newline at end of file
+}
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 2d211f9..50b0812 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -100,10 +100,20 @@
data class MacrobenchmarkConfig(
val packageName: String,
- val metrics: List<Metric>,
+ var metrics: List<Metric>,
val compilationMode: CompilationMode = CompilationMode.SpeedProfile(),
val iterations: Int
-)
+) {
+ init {
+ val metricSet = metrics.toSet()
+ val hasStartupMetric = metricSet.any { it is StartupTimingMetric }
+ if (hasStartupMetric) {
+ val metrics = metrics.toMutableList()
+ metrics += PerfettoMetric()
+ this.metrics = metrics.toList()
+ }
+ }
+}
/**
* macrobenchmark test entrypoint, which doesn't depend on JUnit.
@@ -141,24 +151,23 @@
val metricResults = List(config.iterations) { iteration ->
setupBlock(scope, isFirstRun)
isFirstRun = false
- try {
- perfettoCollector.start()
- config.metrics.forEach {
- it.start()
+ perfettoCollector.captureTrace(uniqueName, iteration) { tracePath ->
+ try {
+ config.metrics.forEach {
+ it.start()
+ }
+ measureBlock(scope)
+ } finally {
+ config.metrics.forEach {
+ it.stop()
+ }
}
- measureBlock(scope)
- } finally {
- config.metrics.forEach {
- it.stop()
- }
- perfettoCollector.stop(uniqueName, iteration)
+ config.metrics
+ // capture list of Map<String,Long> per metric
+ .map { it.getMetrics(config.packageName, tracePath) }
+ // merge into one map
+ .reduce { sum, element -> sum + element }
}
-
- config.metrics
- // capture list of Map<String,Long> per metric
- .map { it.getMetrics(config.packageName) }
- // merge into one map
- .reduce { sum, element -> sum + element }
}.mergeToMetricResults()
InstrumentationResults.instrumentationReport {
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
index aee2877..70aa835 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -16,6 +16,9 @@
package androidx.benchmark.macro
+import android.util.Log
+import androidx.benchmark.perfetto.PerfettoResultsParser.parseResult
+import androidx.benchmark.perfetto.PerfettoTraceParser
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.helpers.CpuUsageHelper
@@ -31,14 +34,13 @@
abstract fun start()
abstract fun stop()
-
/**
* After stopping, collect metrics
*
* TODO: takes package for package level filtering, but probably want a
* general config object coming into [start].
*/
- abstract fun getMetrics(packageName: String): Map<String, Long>
+ abstract fun getMetrics(packageName: String, tracePath: String): Map<String, Long>
}
class StartupTimingMetric : Metric() {
@@ -56,7 +58,7 @@
helper.stopCollecting()
}
- override fun getMetrics(packageName: String): Map<String, Long> {
+ override fun getMetrics(packageName: String, tracePath: String): Map<String, Long> {
return helper.getMetrics(packageName)
}
}
@@ -81,12 +83,12 @@
helper.stopCollecting()
}
- override fun getMetrics(packageName: String): Map<String, Long> {
+ override fun getMetrics(packageName: String, tracePath: String): Map<String, Long> {
return helper.metrics
}
}
-class JankMetric : Metric() {
+class FrameTimingMetric : Metric() {
private lateinit var packageName: String
private val helper = JankCollectionHelper()
@@ -146,7 +148,7 @@
"slow_bmp_upload" to "slowBitmapUploadFrameCount",
"slow_issue_draw_cmds" to "slowIssueDrawCommandsFrameCount",
"total_frames" to "totalFrameCount",
- "janky_frames_percent" to "jankyFramePercent",
+ "janky_frames_percent" to "jankyFramePercent"
)
/**
@@ -157,10 +159,10 @@
"frameTime90thPercentileMs",
"frameTime95thPercentileMs",
"frameTime99thPercentileMs",
- "totalFrameCount",
+ "totalFrameCount"
)
- override fun getMetrics(packageName: String): Map<String, Long> {
+ override fun getMetrics(packageName: String, tracePath: String): Map<String, Long> {
return helper.metrics
.map {
val prefix = "gfxinfo_${packageName}_"
@@ -181,6 +183,48 @@
}
/**
+ * Only does startup metrics now. Will need to expand scope.
+ */
+internal class PerfettoMetric : Metric() {
+ private lateinit var packageName: String
+ private lateinit var device: UiDevice
+ private lateinit var parser: PerfettoTraceParser
+
+ override fun configure(config: MacrobenchmarkConfig) {
+ packageName = config.packageName
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ device = instrumentation.device()
+ parser = PerfettoTraceParser()
+ }
+
+ override fun start() {
+ parser.copyTraceProcessorShell()
+ }
+
+ override fun stop() {
+ }
+
+ override fun getMetrics(packageName: String, tracePath: String): Map<String, Long> {
+ val path = parser.shellFile?.absolutePath
+ return if (path != null) {
+ // TODO: Construct `METRICS` based on the config.
+ val command = "$path --run-metric $METRICS $tracePath --metrics-output=json"
+ Log.d(TAG, "Executing command $command")
+ val json = device.executeShellCommand(command)
+ Log.d(TAG, "Trace Processor result \n\n $json")
+ parseResult(json, packageName)
+ } else {
+ emptyMap()
+ }
+ }
+
+ companion object {
+ private const val TAG = "PerfettoMetric"
+ private const val METRICS = "android_startup"
+ }
+}
+
+/**
* Not public, as this needs clarified metric names
*/
internal class TotalPssMetric : Metric() {
@@ -198,7 +242,7 @@
helper.stopCollecting()
}
- override fun getMetrics(packageName: String): Map<String, Long> {
+ override fun getMetrics(packageName: String, tracePath: String): Map<String, Long> {
return helper.metrics
}
}
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/PerfettoCaptureWrapper.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/PerfettoCaptureWrapper.kt
index 840dee5..39d26a4 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/PerfettoCaptureWrapper.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/PerfettoCaptureWrapper.kt
@@ -19,6 +19,7 @@
import android.os.Build
import android.util.Log
import androidx.benchmark.perfetto.PerfettoCapture
+import androidx.benchmark.perfetto.PerfettoHelper
import androidx.benchmark.perfetto.destinationPath
import androidx.benchmark.perfetto.reportAdditionalFileToCopy
@@ -27,14 +28,26 @@
*/
class PerfettoCaptureWrapper {
private var capture: PerfettoCapture? = null
-
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
capture = PerfettoCapture()
}
}
- fun start(): Boolean {
+ fun <T> captureTrace(
+ benchmarkName: String,
+ iteration: Int,
+ block: (String) -> T
+ ): T {
+ try {
+ start()
+ return block(PerfettoHelper.getPerfettoTmpOutputFilePath())
+ } finally {
+ stop(benchmarkName, iteration)
+ }
+ }
+
+ private fun start(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.d(TAG, "Recording perfetto trace")
capture?.start()
@@ -42,19 +55,14 @@
return true
}
- fun stop(benchmarkName: String, iteration: Int): Boolean {
+ private fun stop(benchmarkName: String, iteration: Int): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val iterString = iteration.toString().padStart(3, '0')
val traceName = "${benchmarkName}_iter$iterString.trace"
-
val destination = destinationPath(traceName).absolutePath
capture?.stop(destination)
reportAdditionalFileToCopy("perfetto_trace_$iterString", destination)
}
return true
}
-
- companion object {
- private const val TAG = "PerfettoCollector"
- }
}
diff --git a/benchmark/perfetto/build.gradle b/benchmark/perfetto/build.gradle
index 8210bdd..652a512 100644
--- a/benchmark/perfetto/build.gradle
+++ b/benchmark/perfetto/build.gradle
@@ -17,6 +17,7 @@
import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
import androidx.build.Publish
+import androidx.build.SupportConfigKt
plugins {
id("AndroidXPlugin")
@@ -30,6 +31,12 @@
// lower minSdkVersion to enable optional usage, based on API level.
minSdkVersion 18
}
+ sourceSets {
+ main.assets.srcDirs += new File(
+ SupportConfigKt.getPrebuiltsRoot(project),
+ "androidx/traceprocessor/trace_processor_shell"
+ )
+ }
}
dependencies {
@@ -45,6 +52,7 @@
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
}
+
androidx {
name = "Android Benchmark - Perfetto"
publish = Publish.NONE
diff --git a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
index 77e056a..1e95916 100644
--- a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
+++ b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
@@ -16,7 +16,6 @@
package androidx.benchmark.perfetto
-import android.content.Context
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.test.platform.app.InstrumentationRegistry
@@ -51,16 +50,13 @@
* TODO: provide configuration options
*/
fun start() {
- val context: Context = InstrumentationRegistry.getInstrumentation().context
-
+ val context = InstrumentationRegistry.getInstrumentation().context
// Write textproto asset to external files dir, so it can be read by shell
// TODO: use binary proto (which will also give us rooted 28 support)
val configBytes = context.resources.openRawResource(R.raw.trace_config).readBytes()
val textProtoFile = File(context.getExternalFilesDir(null), "trace_config.textproto")
-
try {
textProtoFile.writeBytes(configBytes)
-
// Start tracing
if (!helper.startCollecting(textProtoFile.absolutePath, true)) {
// TODO: move internal failures to be exceptions
@@ -83,4 +79,4 @@
throw IllegalStateException("Unable to store perfetto trace")
}
}
-}
\ No newline at end of file
+}
diff --git a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.java b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.java
index 6060a4d..fa60360 100644
--- a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.java
+++ b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.java
@@ -19,7 +19,10 @@
import android.os.SystemClock;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
@@ -31,9 +34,12 @@
/**
* PerfettoHelper is used to start and stop the perfetto tracing and move the
* output perfetto trace file to destination folder.
+ *
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(28)
-class PerfettoHelper {
+public class PerfettoHelper {
private static final String LOG_TAG = PerfettoHelper.class.getSimpleName();
// Command to start the perfetto tracing in the background.
// perfetto -b -c /data/misc/perfetto-traces/trace_config.pb -o
@@ -51,7 +57,8 @@
// Check if perfetto is stopped every 5 secs.
private static final long PERFETTO_KILL_WAIT_TIME = 5000;
- private UiDevice mUIDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ private final UiDevice mUIDevice =
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
/**
* Start the perfetto tracing in background using the given config file.
@@ -64,7 +71,7 @@
* @param isTextProtoConfig true if the config file is textproto format otherwise false.
* @return true if trace collection started successfully otherwise return false.
*/
- public boolean startCollecting(String configFilePath, boolean isTextProtoConfig) {
+ public boolean startCollecting(@Nullable String configFilePath, boolean isTextProtoConfig) {
if (configFilePath == null || configFilePath.isEmpty()) {
Log.e(LOG_TAG, "Perfetto config file name is null or empty.");
return false;
@@ -115,7 +122,7 @@
* @param destinationFile file to copy the perfetto output trace.
* @return true if the trace collection is successful otherwise false.
*/
- public boolean stopCollecting(long waitTimeInMsecs, String destinationFile) {
+ public boolean stopCollecting(long waitTimeInMsecs, @NonNull String destinationFile) {
// Wait for the dump interval before stopping the trace.
Log.i(LOG_TAG, String.format(
"Waiting for %d msecs before stopping perfetto.", waitTimeInMsecs));
@@ -190,13 +197,22 @@
}
/**
+ * @return the {@link String} path to the temporary output file used to store the trace file
+ * during collection.
+ */
+ @NonNull
+ public static String getPerfettoTmpOutputFilePath() {
+ return PERFETTO_TMP_OUTPUT_FILE;
+ }
+
+ /**
* Copy the temporary perfetto trace output file from /data/misc/perfetto-traces/ to given
* destinationFile.
*
* @param destinationFile file to copy the perfetto output trace.
* @return true if the trace file copied successfully otherwise false.
*/
- private boolean copyFileOutput(String destinationFile) {
+ private boolean copyFileOutput(@NonNull String destinationFile) {
Path path = Paths.get(destinationFile);
String destDirectory = path.getParent().toString();
// Check if the directory already exists
diff --git a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoResultsParser.kt b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoResultsParser.kt
new file mode 100644
index 0000000..dc0d611
--- /dev/null
+++ b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoResultsParser.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.perfetto
+
+import org.json.JSONArray
+import org.json.JSONObject
+
+object PerfettoResultsParser {
+ fun parseResult(jsonTrace: String, packageName: String): Map<String, Long> {
+ val map = mutableMapOf<String, Long>()
+ val json = JSONObject(jsonTrace)
+ val androidStartup = json.optJSONObject(ANDROID_STARTUP)
+ if (androidStartup != null) {
+ val startup = androidStartup.optJSONArray(STARTUP)
+ if (startup != null && startup.length() > 0) {
+ parseStartupResult(startup, packageName, map)
+ }
+ }
+ return map
+ }
+
+ private fun parseStartupResult(
+ json: JSONArray,
+ packageName: String,
+ map: MutableMap<String, Long>
+ ) {
+ val length = json.length()
+ for (i in 0 until length) {
+ val startupResult = json.getJSONObject(i)
+ val targetPackageName = startupResult.optString(PACKAGE_NAME)
+ if (packageName == targetPackageName) {
+ val firstFrameMetric = startupResult.optJSONObject(TO_FIRST_FRAME)
+ if (firstFrameMetric != null) {
+ val duration = firstFrameMetric.optDouble(DUR_MS, 0.0)
+ map[STARTUP_MS] = duration.toLong()
+ }
+ }
+ }
+ }
+
+ private const val ANDROID_STARTUP = "android_startup"
+ private const val STARTUP = "startup"
+ private const val PACKAGE_NAME = "package_name"
+ private const val TO_FIRST_FRAME = "to_first_frame"
+ private const val DUR_MS = "dur_ms"
+ private const val TIME_ACTIVITY_START = "time_activity_start"
+ private const val TIME_ACTIVITY_RESUME = "time_activity_resume"
+
+ // Metric Keys
+ private const val STARTUP_MS = "perfetto_startupMs"
+}
diff --git a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoTraceParser.kt b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoTraceParser.kt
new file mode 100644
index 0000000..785be51
--- /dev/null
+++ b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoTraceParser.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.perfetto
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.File
+
+/**
+ * Enables parsing perfetto traces on-device on Q+ devices.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21)
+class PerfettoTraceParser {
+
+ /**
+ * The actual [File] path to the `trace_processor_shell`.
+ */
+ var shellFile: File? = null
+
+ /**
+ * Copies `trace_processor_shell` and enables parsing of the perfetto trace files.
+ */
+ fun copyTraceProcessorShell() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val context: Context = instrumentation.context
+ shellFile = File(context.cacheDir, "trace_processor_shell")
+ // TODO: support other ABIs
+ if (Build.SUPPORTED_64_BIT_ABIS.isEmpty()) {
+ throw IllegalStateException("Unsupported ABI")
+ }
+ // Write the trace_processor_shell to the external directory so we can process
+ // perfetto metrics on device.
+ val shellFile = shellFile
+ if (shellFile != null && !shellFile.exists()) {
+ val created = shellFile.createNewFile()
+ shellFile.setWritable(true)
+ shellFile.setExecutable(true, false)
+ if (!created) {
+ throw IllegalStateException("Unable to create new file $shellFile")
+ }
+ shellFile.outputStream().use {
+ // TODO: Copy the file based on the ABI
+ context.assets.open("trace_processor_shell_aarch64").copyTo(it)
+ }
+ }
+ }
+}
diff --git a/biometric/biometric-ktx/api/current.txt b/biometric/biometric-ktx/api/current.txt
index d2293f0..1342c5a8 100644
--- a/biometric/biometric-ktx/api/current.txt
+++ b/biometric/biometric-ktx/api/current.txt
@@ -1,22 +1,46 @@
// Signature format: 4.0
package androidx.biometric.auth {
+ public final class AuthPromptErrorException extends java.lang.Exception {
+ ctor public AuthPromptErrorException(int errorCode, CharSequence errorMessage);
+ method public int getErrorCode();
+ method public CharSequence getErrorMessage();
+ property public final int errorCode;
+ property public final CharSequence errorMessage;
+ }
+
+ public final class AuthPromptFailureException extends java.lang.Exception {
+ ctor public AuthPromptFailureException();
+ }
+
public final class Class2BiometricAuthExtensionsKt {
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class2BiometricAuthPrompt, androidx.biometric.auth.AuthPromptHost host, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2Biometrics(androidx.fragment.app.FragmentActivity, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2Biometrics(androidx.fragment.app.Fragment, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricAuthentication(androidx.fragment.app.FragmentActivity, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricAuthentication(androidx.fragment.app.Fragment, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
public final class Class2BiometricOrCredentialAuthExtensionsKt {
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt, androidx.biometric.auth.AuthPromptHost host, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2BiometricsOrCredentials(androidx.fragment.app.FragmentActivity, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2BiometricsOrCredentials(androidx.fragment.app.Fragment, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricOrCredentialAuthentication(androidx.fragment.app.FragmentActivity, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricOrCredentialAuthentication(androidx.fragment.app.Fragment, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
public final class Class3BiometricAuthExtensionsKt {
- method public static androidx.biometric.auth.AuthPrompt startClass3BiometricAuthentication(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
- method public static androidx.biometric.auth.AuthPrompt startClass3BiometricAuthentication(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class3BiometricAuthPrompt, androidx.biometric.auth.AuthPromptHost host, androidx.biometric.BiometricPrompt.CryptoObject? crypto, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static androidx.biometric.auth.AuthPrompt authenticateWithClass3Biometrics(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticateWithClass3Biometrics(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static androidx.biometric.auth.AuthPrompt authenticateWithClass3Biometrics(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticateWithClass3Biometrics(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
}
public final class Class3BiometricOrCredentialAuthExtensionsKt {
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticate(androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt, androidx.biometric.auth.AuthPromptHost host, androidx.biometric.BiometricPrompt.CryptoObject? crypto, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticateWithClass3BiometricsOrCredentials(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticateWithClass3BiometricsOrCredentials(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method @RequiresApi(android.os.Build.VERSION_CODES.R) public static androidx.biometric.auth.AuthPrompt startClass3BiometricOrCredentialAuthentication(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method @RequiresApi(android.os.Build.VERSION_CODES.R) public static androidx.biometric.auth.AuthPrompt startClass3BiometricOrCredentialAuthentication(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
diff --git a/biometric/biometric-ktx/api/public_plus_experimental_current.txt b/biometric/biometric-ktx/api/public_plus_experimental_current.txt
index d2293f0..1342c5a8 100644
--- a/biometric/biometric-ktx/api/public_plus_experimental_current.txt
+++ b/biometric/biometric-ktx/api/public_plus_experimental_current.txt
@@ -1,22 +1,46 @@
// Signature format: 4.0
package androidx.biometric.auth {
+ public final class AuthPromptErrorException extends java.lang.Exception {
+ ctor public AuthPromptErrorException(int errorCode, CharSequence errorMessage);
+ method public int getErrorCode();
+ method public CharSequence getErrorMessage();
+ property public final int errorCode;
+ property public final CharSequence errorMessage;
+ }
+
+ public final class AuthPromptFailureException extends java.lang.Exception {
+ ctor public AuthPromptFailureException();
+ }
+
public final class Class2BiometricAuthExtensionsKt {
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class2BiometricAuthPrompt, androidx.biometric.auth.AuthPromptHost host, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2Biometrics(androidx.fragment.app.FragmentActivity, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2Biometrics(androidx.fragment.app.Fragment, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricAuthentication(androidx.fragment.app.FragmentActivity, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricAuthentication(androidx.fragment.app.Fragment, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
public final class Class2BiometricOrCredentialAuthExtensionsKt {
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt, androidx.biometric.auth.AuthPromptHost host, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2BiometricsOrCredentials(androidx.fragment.app.FragmentActivity, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2BiometricsOrCredentials(androidx.fragment.app.Fragment, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricOrCredentialAuthentication(androidx.fragment.app.FragmentActivity, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricOrCredentialAuthentication(androidx.fragment.app.Fragment, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
public final class Class3BiometricAuthExtensionsKt {
- method public static androidx.biometric.auth.AuthPrompt startClass3BiometricAuthentication(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
- method public static androidx.biometric.auth.AuthPrompt startClass3BiometricAuthentication(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class3BiometricAuthPrompt, androidx.biometric.auth.AuthPromptHost host, androidx.biometric.BiometricPrompt.CryptoObject? crypto, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static androidx.biometric.auth.AuthPrompt authenticateWithClass3Biometrics(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticateWithClass3Biometrics(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static androidx.biometric.auth.AuthPrompt authenticateWithClass3Biometrics(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticateWithClass3Biometrics(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
}
public final class Class3BiometricOrCredentialAuthExtensionsKt {
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticate(androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt, androidx.biometric.auth.AuthPromptHost host, androidx.biometric.BiometricPrompt.CryptoObject? crypto, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticateWithClass3BiometricsOrCredentials(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticateWithClass3BiometricsOrCredentials(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method @RequiresApi(android.os.Build.VERSION_CODES.R) public static androidx.biometric.auth.AuthPrompt startClass3BiometricOrCredentialAuthentication(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method @RequiresApi(android.os.Build.VERSION_CODES.R) public static androidx.biometric.auth.AuthPrompt startClass3BiometricOrCredentialAuthentication(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
diff --git a/biometric/biometric-ktx/api/restricted_current.txt b/biometric/biometric-ktx/api/restricted_current.txt
index d2293f0..1342c5a8 100644
--- a/biometric/biometric-ktx/api/restricted_current.txt
+++ b/biometric/biometric-ktx/api/restricted_current.txt
@@ -1,22 +1,46 @@
// Signature format: 4.0
package androidx.biometric.auth {
+ public final class AuthPromptErrorException extends java.lang.Exception {
+ ctor public AuthPromptErrorException(int errorCode, CharSequence errorMessage);
+ method public int getErrorCode();
+ method public CharSequence getErrorMessage();
+ property public final int errorCode;
+ property public final CharSequence errorMessage;
+ }
+
+ public final class AuthPromptFailureException extends java.lang.Exception {
+ ctor public AuthPromptFailureException();
+ }
+
public final class Class2BiometricAuthExtensionsKt {
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class2BiometricAuthPrompt, androidx.biometric.auth.AuthPromptHost host, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2Biometrics(androidx.fragment.app.FragmentActivity, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2Biometrics(androidx.fragment.app.Fragment, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricAuthentication(androidx.fragment.app.FragmentActivity, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricAuthentication(androidx.fragment.app.Fragment, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
public final class Class2BiometricOrCredentialAuthExtensionsKt {
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt, androidx.biometric.auth.AuthPromptHost host, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2BiometricsOrCredentials(androidx.fragment.app.FragmentActivity, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static suspend Object? authenticateWithClass2BiometricsOrCredentials(androidx.fragment.app.Fragment, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricOrCredentialAuthentication(androidx.fragment.app.FragmentActivity, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method public static androidx.biometric.auth.AuthPrompt startClass2BiometricOrCredentialAuthentication(androidx.fragment.app.Fragment, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
public final class Class3BiometricAuthExtensionsKt {
- method public static androidx.biometric.auth.AuthPrompt startClass3BiometricAuthentication(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
- method public static androidx.biometric.auth.AuthPrompt startClass3BiometricAuthentication(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticate(androidx.biometric.auth.Class3BiometricAuthPrompt, androidx.biometric.auth.AuthPromptHost host, androidx.biometric.BiometricPrompt.CryptoObject? crypto, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static androidx.biometric.auth.AuthPrompt authenticateWithClass3Biometrics(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticateWithClass3Biometrics(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method public static androidx.biometric.auth.AuthPrompt authenticateWithClass3Biometrics(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
+ method public static suspend Object? authenticateWithClass3Biometrics(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, CharSequence negativeButtonText, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
}
public final class Class3BiometricOrCredentialAuthExtensionsKt {
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticate(androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt, androidx.biometric.auth.AuthPromptHost host, androidx.biometric.BiometricPrompt.CryptoObject? crypto, kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticateWithClass3BiometricsOrCredentials(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) public static suspend Object? authenticateWithClass3BiometricsOrCredentials(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional kotlin.coroutines.Continuation<? super androidx.biometric.BiometricPrompt.AuthenticationResult> p);
method @RequiresApi(android.os.Build.VERSION_CODES.R) public static androidx.biometric.auth.AuthPrompt startClass3BiometricOrCredentialAuthentication(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
method @RequiresApi(android.os.Build.VERSION_CODES.R) public static androidx.biometric.auth.AuthPrompt startClass3BiometricOrCredentialAuthentication(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.CryptoObject? crypto, CharSequence title, optional CharSequence? subtitle, optional CharSequence? description, optional boolean confirmationRequired, optional java.util.concurrent.Executor? executor, androidx.biometric.auth.AuthPromptCallback callback);
}
diff --git a/biometric/biometric-ktx/build.gradle b/biometric/biometric-ktx/build.gradle
index 570ffa1..4dc85dc 100755
--- a/biometric/biometric-ktx/build.gradle
+++ b/biometric/biometric-ktx/build.gradle
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
+
import androidx.build.LibraryGroups
import androidx.build.Publish
+import static androidx.build.dependencies.DependenciesKt.getKOTLIN_COROUTINES_CORE
+import static androidx.build.dependencies.DependenciesKt.getKOTLIN_STDLIB
+
plugins {
id("AndroidXPlugin")
@@ -27,6 +30,7 @@
dependencies {
api(KOTLIN_STDLIB)
+ api(KOTLIN_COROUTINES_CORE)
api(project(":biometric:biometric"))
}
@@ -36,4 +40,4 @@
mavenGroup = LibraryGroups.BIOMETRIC
inceptionYear = "2020"
description = "Kotlin extensions for the Biometric Library."
-}
\ No newline at end of file
+}
diff --git a/biometric/biometric-ktx/samples/build.gradle b/biometric/biometric-ktx/samples/build.gradle
new file mode 100644
index 0000000..bf95a76
--- /dev/null
+++ b/biometric/biometric-ktx/samples/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+import androidx.build.LibraryVersions
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+dependencies {
+ compileOnly(project(":annotation:annotation-sampled"))
+ implementation(project(":biometric:biometric-ktx"))
+}
+
+androidx {
+ name = "AndroidX Biometric Samples"
+ type = LibraryType.SAMPLES
+ mavenGroup = LibraryGroups.BIOMETRIC
+ inceptionYear = "2021"
+ description = "Contains the sample code for the AndroidX Biometric library"
+}
diff --git a/biometric/biometric-ktx/samples/lint-baseline.xml b/biometric/biometric-ktx/samples/lint-baseline.xml
new file mode 100644
index 0000000..8f1aa4b
--- /dev/null
+++ b/biometric/biometric-ktx/samples/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+
+</issues>
diff --git a/biometric/biometric-ktx/samples/src/main/AndroidManifest.xml b/biometric/biometric-ktx/samples/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1dacecc
--- /dev/null
+++ b/biometric/biometric-ktx/samples/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.biometric.samples"/>
diff --git a/biometric/biometric-ktx/samples/src/main/java/androidx/biometric/samples/auth/CoroutineSamples.kt b/biometric/biometric-ktx/samples/src/main/java/androidx/biometric/samples/auth/CoroutineSamples.kt
new file mode 100644
index 0000000..4b7aeed
--- /dev/null
+++ b/biometric/biometric-ktx/samples/src/main/java/androidx/biometric/samples/auth/CoroutineSamples.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.biometric.samples.auth
+
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import androidx.annotation.Sampled
+import androidx.biometric.BiometricPrompt
+import androidx.biometric.auth.AuthPromptErrorException
+import androidx.biometric.auth.AuthPromptFailureException
+import androidx.biometric.auth.AuthPromptHost
+import androidx.biometric.auth.Class2BiometricAuthPrompt
+import androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt
+import androidx.biometric.auth.Class3BiometricAuthPrompt
+import androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt
+import androidx.biometric.auth.authenticate
+import androidx.fragment.app.Fragment
+import java.nio.charset.Charset
+import java.security.KeyStore
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.SecretKey
+
+// Stubbed definitions for samples
+private const val KEYSTORE_INSTANCE = "AndroidKeyStore"
+private const val KEY_NAME = "mySecretKey"
+private const val title = ""
+private const val subtitle = ""
+private const val description = ""
+private const val negativeButtonText = ""
+private fun sendEncryptedPayload(payload: ByteArray?): ByteArray? = payload
+
+@Sampled
+suspend fun Fragment.class2BiometricAuth() {
+ val payload = "A message to encrypt".toByteArray(Charset.defaultCharset())
+
+ // Construct AuthPrompt with localized Strings to be displayed to UI.
+ val authPrompt = Class2BiometricAuthPrompt.Builder(title, negativeButtonText).apply {
+ setSubtitle(subtitle)
+ setDescription(description)
+ setConfirmationRequired(true)
+ }.build()
+
+ try {
+ val authResult = authPrompt.authenticate(AuthPromptHost(this))
+
+ // Encrypt a payload using the result of crypto-based auth.
+ val encryptedPayload = authResult.cryptoObject?.cipher?.doFinal(payload)
+
+ // Use the encrypted payload somewhere interesting.
+ sendEncryptedPayload(encryptedPayload)
+ } catch (e: AuthPromptErrorException) {
+ // Handle irrecoverable error during authentication.
+ // Possible values for AuthPromptErrorException.errorCode are listed in the @IntDef,
+ // androidx.biometric.BiometricPrompt.AuthenticationError.
+ } catch (e: AuthPromptFailureException) {
+ // Handle auth failure due biometric credentials being rejected.
+ }
+}
+
+@Sampled
+suspend fun Fragment.class2BiometricOrCredentialAuth() {
+ val payload = "A message to encrypt".toByteArray(Charset.defaultCharset())
+
+ // Construct AuthPrompt with localized Strings to be displayed to UI.
+ val authPrompt = Class2BiometricOrCredentialAuthPrompt.Builder(title).apply {
+ setSubtitle(subtitle)
+ setDescription(description)
+ setConfirmationRequired(true)
+ }.build()
+
+ try {
+ val authResult = authPrompt.authenticate(AuthPromptHost(this))
+
+ // Encrypt a payload using the result of crypto-based auth.
+ val encryptedPayload = authResult.cryptoObject?.cipher?.doFinal(payload)
+
+ // Use the encrypted payload somewhere interesting.
+ sendEncryptedPayload(encryptedPayload)
+ } catch (e: AuthPromptErrorException) {
+ // Handle irrecoverable error during authentication.
+ // Possible values for AuthPromptErrorException.errorCode are listed in the @IntDef,
+ // androidx.biometric.BiometricPrompt.AuthenticationError.
+ } catch (e: AuthPromptFailureException) {
+ // Handle auth failure due biometric credentials being rejected.
+ }
+}
+
+@Sampled
+@Suppress("UnsafeNewApiCall", "NewApi")
+suspend fun Fragment.class3BiometricAuth() {
+ // To use Class3 authentication, we need to create a CryptoObject.
+ // First create a spec for the key to be generated.
+ val keyPurpose = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
+ val keySpec = KeyGenParameterSpec.Builder(KEY_NAME, keyPurpose).apply {
+ setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ setUserAuthenticationRequired(true)
+
+ // Require authentication for each use of the key.
+ val timeout = 0
+ // Set the key type according to the allowed auth types.
+ val keyType =
+ KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
+ setUserAuthenticationParameters(timeout, keyType)
+ }.build()
+
+ // Generate and store the key in the Android keystore.
+ KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_INSTANCE).run {
+ init(keySpec)
+ generateKey()
+ }
+
+ // Prepare the crypto object to use for authentication.
+ val cipher = Cipher.getInstance(
+ "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/" +
+ KeyProperties.ENCRYPTION_PADDING_PKCS7
+ ).apply {
+ val keyStore = KeyStore.getInstance(KEYSTORE_INSTANCE).apply { load(null) }
+ init(Cipher.ENCRYPT_MODE, keyStore.getKey(KEY_NAME, null) as SecretKey)
+ }
+
+ val cryptoObject = BiometricPrompt.CryptoObject(cipher)
+ val payload = "A message to encrypt".toByteArray(Charset.defaultCharset())
+
+ // Construct AuthPrompt with localized Strings to be displayed to UI.
+ val authPrompt = Class3BiometricAuthPrompt.Builder(title, negativeButtonText).apply {
+ setSubtitle(subtitle)
+ setDescription(description)
+ setConfirmationRequired(true)
+ }.build()
+
+ try {
+ val authResult = authPrompt.authenticate(AuthPromptHost(this), cryptoObject)
+
+ // Encrypt a payload using the result of crypto-based auth.
+ val encryptedPayload = authResult.cryptoObject?.cipher?.doFinal(payload)
+
+ // Use the encrypted payload somewhere interesting.
+ sendEncryptedPayload(encryptedPayload)
+ } catch (e: AuthPromptErrorException) {
+ // Handle irrecoverable error during authentication.
+ // Possible values for AuthPromptErrorException.errorCode are listed in the @IntDef,
+ // androidx.biometric.BiometricPrompt.AuthenticationError.
+ } catch (e: AuthPromptFailureException) {
+ // Handle auth failure due biometric credentials being rejected.
+ }
+}
+
+@Sampled
+@Suppress("UnsafeNewApiCall", "NewApi")
+suspend fun Fragment.class3BiometricOrCredentialAuth() {
+ // To use Class3 authentication, we need to create a CryptoObject.
+ // First create a spec for the key to be generated.
+ val keyPurpose = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
+ val keySpec = KeyGenParameterSpec.Builder(KEY_NAME, keyPurpose).apply {
+ setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ setUserAuthenticationRequired(true)
+
+ // Require authentication for each use of the key.
+ val timeout = 0
+ // Set the key type according to the allowed auth types.
+ val keyType =
+ KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
+ setUserAuthenticationParameters(timeout, keyType)
+ }.build()
+
+ // Generate and store the key in the Android keystore.
+ KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_INSTANCE).run {
+ init(keySpec)
+ generateKey()
+ }
+
+ // Prepare the crypto object to use for authentication.
+ val cipher = Cipher.getInstance(
+ "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/" +
+ KeyProperties.ENCRYPTION_PADDING_PKCS7
+ ).apply {
+ val keyStore = KeyStore.getInstance(KEYSTORE_INSTANCE).apply { load(null) }
+ init(Cipher.ENCRYPT_MODE, keyStore.getKey(KEY_NAME, null) as SecretKey)
+ }
+
+ val cryptoObject = BiometricPrompt.CryptoObject(cipher)
+ val payload = "A message to encrypt".toByteArray(Charset.defaultCharset())
+
+ // Construct AuthPrompt with localized Strings to be displayed to UI.
+ val authPrompt = Class3BiometricOrCredentialAuthPrompt.Builder(title).apply {
+ setSubtitle(subtitle)
+ setDescription(description)
+ setConfirmationRequired(true)
+ }.build()
+
+ try {
+ val authResult = authPrompt.authenticate(AuthPromptHost(this), cryptoObject)
+
+ // Encrypt a payload using the result of crypto-based auth.
+ val encryptedPayload = authResult.cryptoObject?.cipher?.doFinal(payload)
+
+ // Use the encrypted payload somewhere interesting.
+ sendEncryptedPayload(encryptedPayload)
+ } catch (e: AuthPromptErrorException) {
+ // Handle irrecoverable error during authentication.
+ // Possible values for AuthPromptErrorException.errorCode are listed in the @IntDef,
+ // androidx.biometric.BiometricPrompt.AuthenticationError.
+ } catch (e: AuthPromptFailureException) {
+ // Handle auth failure due biometric credentials being rejected.
+ }
+}
diff --git a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/AuthPromptErrorException.kt b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/AuthPromptErrorException.kt
new file mode 100644
index 0000000..c1daef7
--- /dev/null
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/AuthPromptErrorException.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.biometric.auth
+/**
+ * Thrown when an unrecoverable error has been encountered and authentication has stopped.
+ *
+ * @param errorCode An integer ID associated with the error.
+ * @param errorMessage A human-readable string that describes the error.
+ */
+public class AuthPromptErrorException(
+ public val errorCode: Int,
+ public val errorMessage: CharSequence
+) : Exception(errorMessage.toString())
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.java b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/AuthPromptFailureException.kt
similarity index 68%
copy from navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.java
copy to biometric/biometric-ktx/src/main/java/androidx/biometric/auth/AuthPromptFailureException.kt
index f9f0fbb..6a1c34e 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.java
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/AuthPromptFailureException.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package androidx.navigation;
+package androidx.biometric.auth
/**
- * An interface marking generated Args classes.
+ * Thrown when an authentication attempt by the user has been rejected, e.g., the user's
+ * biometrics were not recognized.
*/
-public interface NavArgs {
-}
+public class AuthPromptFailureException : Exception()
diff --git a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricAuthExtensions.kt b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricAuthExtensions.kt
index 4d67b8c..4c43052 100755
--- a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricAuthExtensions.kt
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricAuthExtensions.kt
@@ -15,11 +15,44 @@
*/
package androidx.biometric.auth
+import androidx.biometric.BiometricPrompt.AuthenticationResult
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.Executor
/**
+ * Shows an authentication prompt to the user.
+ *
+ * @param host A wrapper for the component that will host the prompt.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class2BiometricAuthPrompt.authenticate(AuthPromptHost, AuthPromptCallback)
+ *
+ * @sample androidx.biometric.samples.auth.class2BiometricAuth
+ */
+public suspend fun Class2BiometricAuthPrompt.authenticate(
+ host: AuthPromptHost,
+): AuthenticationResult {
+ return suspendCancellableCoroutine { continuation ->
+ val authPrompt = startAuthentication(
+ host,
+ Runnable::run,
+ CoroutineAuthPromptCallback(continuation)
+ )
+
+ continuation.invokeOnCancellation {
+ authPrompt.cancelAuthentication()
+ }
+ }
+}
+
+/**
* Prompts the user to authenticate with a **Class 2** biometric (e.g. fingerprint, face, or iris).
*
* Note that **Class 3** biometrics are guaranteed to meet the requirements for **Class 2** and thus
@@ -68,6 +101,44 @@
* @param subtitle An optional subtitle to be displayed on the prompt.
* @param description An optional description to be displayed on the prompt.
* @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class2BiometricAuthPrompt
+ */
+public suspend fun FragmentActivity.authenticateWithClass2Biometrics(
+ title: CharSequence,
+ negativeButtonText: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass2BiometricAuthPrompt(
+ title,
+ negativeButtonText,
+ subtitle,
+ description,
+ confirmationRequired,
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this))
+}
+
+/**
+ * Prompts the user to authenticate with a **Class 2** biometric (e.g. fingerprint, face, or iris).
+ *
+ * Note that **Class 3** biometrics are guaranteed to meet the requirements for **Class 2** and thus
+ * will also be accepted.
+ *
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label for the negative button on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
* @param executor An executor for [callback] methods. If `null`, these will run on the main thread.
* @param callback The object that will receive and process authentication events.
* @return An [AuthPrompt] handle to the shown prompt.
@@ -96,6 +167,61 @@
}
/**
+ * Prompts the user to authenticate with a **Class 2** biometric (e.g. fingerprint, face, or iris).
+ *
+ * Note that **Class 3** biometrics are guaranteed to meet the requirements for **Class 2** and thus
+ * will also be accepted.
+ *
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label for the negative button on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class2BiometricAuthPrompt
+ */
+public suspend fun Fragment.authenticateWithClass2Biometrics(
+ title: CharSequence,
+ negativeButtonText: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass2BiometricAuthPrompt(
+ title,
+ negativeButtonText,
+ subtitle,
+ description,
+ confirmationRequired,
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this))
+}
+
+/**
+ * Creates a [Class2BiometricAuthPrompt] with the given parameters.
+ */
+private fun buildClass2BiometricAuthPrompt(
+ title: CharSequence,
+ negativeButtonText: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): Class2BiometricAuthPrompt = Class2BiometricAuthPrompt.Builder(title, negativeButtonText)
+ .apply {
+ subtitle?.let { setSubtitle(it) }
+ description?.let { setDescription(it) }
+ setConfirmationRequired(confirmationRequired)
+ }
+ .build()
+
+/**
* Creates a [Class2BiometricAuthPrompt] with the given parameters and starts authentication.
*/
private fun startClass2BiometricAuthenticationInternal(
@@ -108,15 +234,17 @@
executor: Executor? = null,
callback: AuthPromptCallback
): AuthPrompt {
- val prompt = Class2BiometricAuthPrompt.Builder(title, negativeButtonText).apply {
- subtitle?.let { setSubtitle(it) }
- description?.let { setDescription(it) }
- setConfirmationRequired(confirmationRequired)
- }.build()
+ val prompt = buildClass2BiometricAuthPrompt(
+ title,
+ negativeButtonText,
+ subtitle,
+ description,
+ confirmationRequired
+ )
return if (executor == null) {
prompt.startAuthentication(host, callback)
} else {
prompt.startAuthentication(host, executor, callback)
}
-}
\ No newline at end of file
+}
diff --git a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthExtensions.kt b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthExtensions.kt
index 0b3802f..8c772e3 100755
--- a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthExtensions.kt
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthExtensions.kt
@@ -15,11 +15,47 @@
*/
package androidx.biometric.auth
+import androidx.biometric.BiometricPrompt.AuthenticationResult
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.Executor
/**
+ * Shows an authentication prompt to the user.
+ *
+ * @param host A wrapper for the component that will host the prompt.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class2BiometricOrCredentialAuthPrompt.authenticate(
+ * AuthPromptHost,
+ * AuthPromptCallback
+ * )
+ *
+ * @sample androidx.biometric.samples.auth.class2BiometricOrCredentialAuth
+ */
+public suspend fun Class2BiometricOrCredentialAuthPrompt.authenticate(
+ host: AuthPromptHost,
+): AuthenticationResult {
+ return suspendCancellableCoroutine { continuation ->
+ val authPrompt = startAuthentication(
+ host,
+ Runnable::run,
+ CoroutineAuthPromptCallback(continuation)
+ )
+
+ continuation.invokeOnCancellation {
+ authPrompt.cancelAuthentication()
+ }
+ }
+}
+
+/**
* Prompts the user to authenticate with a **Class 2** biometric (e.g. fingerprint, face, or iris)
* or the screen lock credential (i.e. PIN, pattern, or password) for the device.
*
@@ -66,6 +102,42 @@
* @param subtitle An optional subtitle to be displayed on the prompt.
* @param description An optional description to be displayed on the prompt.
* @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class2BiometricOrCredentialAuthPrompt
+ */
+public suspend fun FragmentActivity.authenticateWithClass2BiometricsOrCredentials(
+ title: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass2BiometricOrCredentialAuthPrompt(
+ title,
+ subtitle,
+ description,
+ confirmationRequired,
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this))
+}
+
+/**
+ * Prompts the user to authenticate with a **Class 2** biometric (e.g. fingerprint, face, or iris)
+ * or the screen lock credential (i.e. PIN, pattern, or password) for the device.
+ *
+ * Note that **Class 3** biometrics are guaranteed to meet the requirements for **Class 2** and thus
+ * will also be accepted.
+ *
+ * @param title The title to be displayed on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
* @param executor An executor for [callback] methods. If `null`, these will run on the main thread.
* @param callback The object that will receive and process authentication events.
* @return An [AuthPrompt] handle to the shown prompt.
@@ -92,6 +164,57 @@
}
/**
+ * Prompts the user to authenticate with a **Class 2** biometric (e.g. fingerprint, face, or iris)
+ * or the screen lock credential (i.e. PIN, pattern, or password) for the device.
+ *
+ * Note that **Class 3** biometrics are guaranteed to meet the requirements for **Class 2** and thus
+ * will also be accepted.
+ *
+ * @param title The title to be displayed on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ * @return An [AuthPrompt] handle to the shown prompt.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class2BiometricOrCredentialAuthPrompt
+ */
+public suspend fun Fragment.authenticateWithClass2BiometricsOrCredentials(
+ title: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass2BiometricOrCredentialAuthPrompt(
+ title,
+ subtitle,
+ description,
+ confirmationRequired,
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this))
+}
+
+/**
+ * Creates a [Class2BiometricOrCredentialAuthPrompt] with the given parameters.
+ */
+private fun buildClass2BiometricOrCredentialAuthPrompt(
+ title: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): Class2BiometricOrCredentialAuthPrompt = Class2BiometricOrCredentialAuthPrompt.Builder(title)
+ .apply {
+ subtitle?.let { setSubtitle(it) }
+ description?.let { setDescription(it) }
+ setConfirmationRequired(confirmationRequired)
+ }
+ .build()
+
+/**
* Creates a [Class2BiometricOrCredentialAuthPrompt] with the given parameters and starts
* authentication.
*/
@@ -104,15 +227,16 @@
executor: Executor? = null,
callback: AuthPromptCallback
): AuthPrompt {
- val prompt = Class2BiometricOrCredentialAuthPrompt.Builder(title).apply {
- subtitle?.let { setSubtitle(it) }
- description?.let { setDescription(it) }
- setConfirmationRequired(confirmationRequired)
- }.build()
+ val prompt = buildClass2BiometricOrCredentialAuthPrompt(
+ title,
+ subtitle,
+ description,
+ confirmationRequired,
+ )
return if (executor == null) {
prompt.startAuthentication(host, callback)
} else {
prompt.startAuthentication(host, executor, callback)
}
-}
\ No newline at end of file
+}
diff --git a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricAuthExtensions.kt b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricAuthExtensions.kt
index cd7a639..0e24d45 100755
--- a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricAuthExtensions.kt
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricAuthExtensions.kt
@@ -15,12 +15,48 @@
*/
package androidx.biometric.auth
-import androidx.biometric.BiometricPrompt
+import androidx.biometric.BiometricPrompt.AuthenticationResult
+import androidx.biometric.BiometricPrompt.CryptoObject
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.Executor
/**
+ * Shows an authentication prompt to the user.
+ *
+ * @param host A wrapper for the component that will host the prompt.
+ * @param crypto A cryptographic object to be associated with this authentication.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class3BiometricAuthPrompt.authenticate(AuthPromptHost, AuthPromptCallback)
+ *
+ * @sample androidx.biometric.samples.auth.class3BiometricAuth
+ */
+public suspend fun Class3BiometricAuthPrompt.authenticate(
+ host: AuthPromptHost,
+ crypto: CryptoObject?,
+): AuthenticationResult {
+ return suspendCancellableCoroutine { continuation ->
+ val authPrompt = startAuthentication(
+ host,
+ crypto,
+ Runnable::run,
+ CoroutineAuthPromptCallback(continuation)
+ )
+
+ continuation.invokeOnCancellation {
+ authPrompt.cancelAuthentication()
+ }
+ }
+}
+
+/**
* Prompts the user to authenticate with a **Class 3** biometric (e.g. fingerprint, face, or iris).
*
* @param crypto A cryptographic object to be associated with this authentication.
@@ -35,8 +71,8 @@
*
* @see Class3BiometricAuthPrompt
*/
-public fun FragmentActivity.startClass3BiometricAuthentication(
- crypto: BiometricPrompt.CryptoObject?,
+public fun FragmentActivity.authenticateWithClass3Biometrics(
+ crypto: CryptoObject?,
title: CharSequence,
negativeButtonText: CharSequence,
subtitle: CharSequence? = null,
@@ -67,14 +103,51 @@
* @param subtitle An optional subtitle to be displayed on the prompt.
* @param description An optional description to be displayed on the prompt.
* @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class3BiometricAuthPrompt
+ */
+public suspend fun FragmentActivity.authenticateWithClass3Biometrics(
+ crypto: CryptoObject?,
+ title: CharSequence,
+ negativeButtonText: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass3BiometricAuthPrompt(
+ title,
+ negativeButtonText,
+ subtitle,
+ description,
+ confirmationRequired
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this), crypto)
+}
+
+/**
+ * Prompts the user to authenticate with a **Class 3** biometric (e.g. fingerprint, face, or iris).
+ *
+ * @param crypto A cryptographic object to be associated with this authentication.
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label for the negative button on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
* @param executor An executor for [callback] methods. If `null`, these will run on the main thread.
* @param callback The object that will receive and process authentication events.
* @return An [AuthPrompt] handle to the shown prompt.
*
* @see Class3BiometricAuthPrompt
*/
-public fun Fragment.startClass3BiometricAuthentication(
- crypto: BiometricPrompt.CryptoObject?,
+public fun Fragment.authenticateWithClass3Biometrics(
+ crypto: CryptoObject?,
title: CharSequence,
negativeButtonText: CharSequence,
subtitle: CharSequence? = null,
@@ -97,11 +170,65 @@
}
/**
+ * Prompts the user to authenticate with a **Class 3** biometric (e.g. fingerprint, face, or iris).
+ *
+ * @param crypto A cryptographic object to be associated with this authentication.
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label for the negative button on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class3BiometricAuthPrompt
+ */
+public suspend fun Fragment.authenticateWithClass3Biometrics(
+ crypto: CryptoObject?,
+ title: CharSequence,
+ negativeButtonText: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass3BiometricAuthPrompt(
+ title,
+ negativeButtonText,
+ subtitle,
+ description,
+ confirmationRequired
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this), crypto)
+}
+
+/**
+ * Creates a [Class3BiometricAuthPrompt] with the given parameters.
+ */
+private fun buildClass3BiometricAuthPrompt(
+ title: CharSequence,
+ negativeButtonText: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): Class3BiometricAuthPrompt = Class3BiometricAuthPrompt.Builder(title, negativeButtonText)
+ .apply {
+ subtitle?.let { setSubtitle(it) }
+ description?.let { setDescription(it) }
+ setConfirmationRequired(confirmationRequired)
+ }
+ .build()
+
+/**
* Creates a [Class3BiometricAuthPrompt] with the given parameters and starts authentication.
*/
private fun startClass3BiometricAuthenticationInternal(
host: AuthPromptHost,
- crypto: BiometricPrompt.CryptoObject?,
+ crypto: CryptoObject?,
title: CharSequence,
negativeButtonText: CharSequence,
subtitle: CharSequence? = null,
@@ -110,15 +237,17 @@
executor: Executor? = null,
callback: AuthPromptCallback
): AuthPrompt {
- val prompt = Class3BiometricAuthPrompt.Builder(title, negativeButtonText).apply {
- subtitle?.let { setSubtitle(it) }
- description?.let { setDescription(it) }
- setConfirmationRequired(confirmationRequired)
- }.build()
+ val prompt = buildClass3BiometricAuthPrompt(
+ title,
+ negativeButtonText,
+ subtitle,
+ description,
+ confirmationRequired
+ )
return if (executor == null) {
prompt.startAuthentication(host, crypto, callback)
} else {
prompt.startAuthentication(host, crypto, executor, callback)
}
-}
\ No newline at end of file
+}
diff --git a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthExtensions.kt b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthExtensions.kt
index 1376e36..ec27ec6 100755
--- a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthExtensions.kt
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthExtensions.kt
@@ -17,12 +17,52 @@
import android.os.Build
import androidx.annotation.RequiresApi
-import androidx.biometric.BiometricPrompt
+import androidx.biometric.BiometricPrompt.AuthenticationResult
+import androidx.biometric.BiometricPrompt.CryptoObject
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.Executor
/**
+ * Shows an authentication prompt to the user.
+ *
+ * @param host A wrapper for the component that will host the prompt.
+ * @param crypto A cryptographic object to be associated with this authentication.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class3BiometricOrCredentialAuthPrompt.authenticate(
+ * AuthPromptHost,
+ * AuthPromptCallback
+ * )
+ *
+ * @sample androidx.biometric.samples.auth.class3BiometricOrCredentialAuth
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public suspend fun Class3BiometricOrCredentialAuthPrompt.authenticate(
+ host: AuthPromptHost,
+ crypto: CryptoObject?,
+): AuthenticationResult {
+ return suspendCancellableCoroutine { continuation ->
+ val authPrompt = startAuthentication(
+ host,
+ crypto,
+ Runnable::run,
+ CoroutineAuthPromptCallback(continuation)
+ )
+
+ continuation.invokeOnCancellation {
+ authPrompt.cancelAuthentication()
+ }
+ }
+}
+
+/**
* Prompts the user to authenticate with a **Class 3** biometric (e.g. fingerprint, face, or iris)
* or the screen lock credential (i.e. PIN, pattern, or password) for the device.
*
@@ -39,7 +79,7 @@
*/
@RequiresApi(Build.VERSION_CODES.R)
public fun FragmentActivity.startClass3BiometricOrCredentialAuthentication(
- crypto: BiometricPrompt.CryptoObject?,
+ crypto: CryptoObject?,
title: CharSequence,
subtitle: CharSequence? = null,
description: CharSequence? = null,
@@ -68,6 +108,42 @@
* @param subtitle An optional subtitle to be displayed on the prompt.
* @param description An optional description to be displayed on the prompt.
* @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class3BiometricOrCredentialAuthPrompt
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public suspend fun FragmentActivity.authenticateWithClass3BiometricsOrCredentials(
+ crypto: CryptoObject?,
+ title: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass3BiometricOrCredentialAuthPrompt(
+ title,
+ subtitle,
+ description,
+ confirmationRequired
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this), crypto)
+}
+
+/**
+ * Prompts the user to authenticate with a **Class 3** biometric (e.g. fingerprint, face, or iris)
+ * or the screen lock credential (i.e. PIN, pattern, or password) for the device.
+ *
+ * @param crypto A cryptographic object to be associated with this authentication.
+ * @param title The title to be displayed on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
* @param executor An executor for [callback] methods. If `null`, these will run on the main thread.
* @param callback The object that will receive and process authentication events.
* @return An [AuthPrompt] handle to the shown prompt.
@@ -76,7 +152,7 @@
*/
@RequiresApi(Build.VERSION_CODES.R)
public fun Fragment.startClass3BiometricOrCredentialAuthentication(
- crypto: BiometricPrompt.CryptoObject?,
+ crypto: CryptoObject?,
title: CharSequence,
subtitle: CharSequence? = null,
description: CharSequence? = null,
@@ -97,13 +173,65 @@
}
/**
+ * Prompts the user to authenticate with a **Class 3** biometric (e.g. fingerprint, face, or iris)
+ * or the screen lock credential (i.e. PIN, pattern, or password) for the device.
+ *
+ * @param crypto A cryptographic object to be associated with this authentication.
+ * @param title The title to be displayed on the prompt.
+ * @param subtitle An optional subtitle to be displayed on the prompt.
+ * @param description An optional description to be displayed on the prompt.
+ * @param confirmationRequired Whether user confirmation should be required for passive biometrics.
+ *
+ * @return [AuthenticationResult] for a successful authentication.
+ *
+ * @throws AuthPromptErrorException when an unrecoverable error has been encountered and
+ * authentication has stopped.
+ * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
+ *
+ * @see Class3BiometricOrCredentialAuthPrompt
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public suspend fun Fragment.authenticateWithClass3BiometricsOrCredentials(
+ crypto: CryptoObject?,
+ title: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): AuthenticationResult {
+ val authPrompt = buildClass3BiometricOrCredentialAuthPrompt(
+ title,
+ subtitle,
+ description,
+ confirmationRequired,
+ )
+
+ return authPrompt.authenticate(AuthPromptHost(this), crypto)
+}
+
+/**
+ * Creates a [Class3BiometricOrCredentialAuthPrompt] with the given parameters.
+ */
+private fun buildClass3BiometricOrCredentialAuthPrompt(
+ title: CharSequence,
+ subtitle: CharSequence? = null,
+ description: CharSequence? = null,
+ confirmationRequired: Boolean = true,
+): Class3BiometricOrCredentialAuthPrompt = Class3BiometricOrCredentialAuthPrompt.Builder(title)
+ .apply {
+ subtitle?.let { setSubtitle(it) }
+ description?.let { setDescription(it) }
+ setConfirmationRequired(confirmationRequired)
+ }
+ .build()
+
+/**
* Creates a [Class3BiometricOrCredentialAuthPrompt] with the given parameters and starts
* authentication.
*/
@RequiresApi(Build.VERSION_CODES.R)
private fun startClass3BiometricOrCredentialAuthenticationInternal(
host: AuthPromptHost,
- crypto: BiometricPrompt.CryptoObject?,
+ crypto: CryptoObject?,
title: CharSequence,
subtitle: CharSequence? = null,
description: CharSequence? = null,
@@ -111,15 +239,16 @@
executor: Executor? = null,
callback: AuthPromptCallback
): AuthPrompt {
- val prompt = Class3BiometricOrCredentialAuthPrompt.Builder(title).apply {
- subtitle?.let { setSubtitle(it) }
- description?.let { setDescription(it) }
- setConfirmationRequired(confirmationRequired)
- }.build()
+ val prompt = buildClass3BiometricOrCredentialAuthPrompt(
+ title,
+ subtitle,
+ description,
+ confirmationRequired
+ )
return if (executor == null) {
prompt.startAuthentication(host, crypto, callback)
} else {
prompt.startAuthentication(host, crypto, executor, callback)
}
-}
\ No newline at end of file
+}
diff --git a/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/CoroutineAuthPromptCallback.kt b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/CoroutineAuthPromptCallback.kt
new file mode 100644
index 0000000..fb6de45
--- /dev/null
+++ b/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/CoroutineAuthPromptCallback.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.biometric.auth
+
+import androidx.biometric.BiometricPrompt.AuthenticationResult
+import androidx.fragment.app.FragmentActivity
+import kotlinx.coroutines.CancellableContinuation
+import kotlin.coroutines.resumeWithException
+
+/**
+ * Implementation of [AuthPromptCallback] used to transform callback results for coroutine APIs.
+ */
+internal class CoroutineAuthPromptCallback(
+ private val continuation: CancellableContinuation<AuthenticationResult>
+) : AuthPromptCallback() {
+ override fun onAuthenticationError(
+ activity: FragmentActivity?,
+ errorCode: Int,
+ errString: CharSequence
+ ) {
+ continuation.resumeWithException(AuthPromptErrorException(errorCode, errString))
+ }
+
+ override fun onAuthenticationSucceeded(
+ activity: FragmentActivity?,
+ result: AuthenticationResult
+ ) {
+ continuation.resumeWith(Result.success(result))
+ }
+
+ override fun onAuthenticationFailed(activity: FragmentActivity?) {
+ continuation.resumeWithException(AuthPromptFailureException())
+ }
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
index 4f83a98..c126640 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
@@ -371,14 +371,12 @@
return BIOMETRIC_ERROR_NO_HARDWARE;
}
- // No authenticators are enrolled if the device is not secured.
- if (!mInjector.isDeviceSecuredWithCredential()) {
- return BIOMETRIC_ERROR_NONE_ENROLLED;
- }
-
- // Credential authentication is always possible if the device is secured.
+ // Credential authentication is always possible if the device is secured. Conversely, no
+ // form of authentication is possible if the device is not secured.
if (AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)) {
- return BIOMETRIC_SUCCESS;
+ return mInjector.isDeviceSecuredWithCredential()
+ ? BIOMETRIC_SUCCESS
+ : BIOMETRIC_ERROR_NONE_ENROLLED;
}
// The class of some non-fingerprint biometrics can be checked on API 29.
@@ -393,7 +391,7 @@
// Having fingerprint hardware is a prerequisite, since BiometricPrompt internally
// calls FingerprintManager#getErrorString() on API 28 (b/151443237).
return mInjector.isFingerprintHardwarePresent()
- ? canAuthenticateWithFingerprintOrUnknown()
+ ? canAuthenticateWithFingerprintOrUnknownBiometric()
: BIOMETRIC_ERROR_NO_HARDWARE;
}
@@ -443,7 +441,7 @@
}
// If all else fails, check if fingerprint authentication is available.
- return canAuthenticateWithFingerprintOrUnknown();
+ return canAuthenticateWithFingerprintOrUnknownBiometric();
}
/**
@@ -465,14 +463,23 @@
}
/**
- * Checks if the user can authenticate with fingerprint, falling back to
- * {@link #BIOMETRIC_STATUS_UNKNOWN} for any error condition.
+ * Checks if the user can authenticate with fingerprint or with a biometric sensor for which
+ * there is no platform method to check availability.
*
- * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint, or
- * {@link #BIOMETRIC_STATUS_UNKNOWN} otherwise.
+ * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint. Otherwise,
+ * returns an error code indicating why the user can't authenticate, or
+ * {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
*/
@AuthenticationStatus
- private int canAuthenticateWithFingerprintOrUnknown() {
+ private int canAuthenticateWithFingerprintOrUnknownBiometric() {
+ // If the device is not secured, authentication is definitely not possible. Use
+ // FingerprintManager to distinguish between the "no hardware" and "none enrolled" cases.
+ if (!mInjector.isDeviceSecuredWithCredential()) {
+ return canAuthenticateWithFingerprint();
+ }
+
+ // Check for definite availability of fingerprint. Otherwise, return "unknown" to allow for
+ // non-fingerprint biometrics (e.g. iris) that may be available via BiometricPrompt.
return canAuthenticateWithFingerprint() == BIOMETRIC_SUCCESS
? BIOMETRIC_SUCCESS
: BIOMETRIC_STATUS_UNKNOWN;
diff --git a/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java b/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
index abbd6f0..d359008 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
@@ -240,39 +240,78 @@
@Test
@Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
- public void testCanAuthenticate_ReturnsError_WhenDeviceNotSecured_OnApi29() {
+ public void testCanAuthenticate_ReturnsError_WhenUnsecured_BiometricOnly_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
mock(android.hardware.biometrics.BiometricManager.class);
- when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_SUCCESS);
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+ when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+ when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
final BiometricManager.Injector injector = new TestInjector.Builder()
.setBiometricManager(frameworkBiometricManager)
.setDeviceSecurable(true)
- .setFingerprintHardwarePresent(true)
+ .setFingerprintHardwarePresent(false)
.build();
final BiometricManager biometricManager = new BiometricManager(injector);
final int authenticators = Authenticators.BIOMETRIC_WEAK;
assertThat(biometricManager.canAuthenticate(authenticators))
+ .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
+ }
+
+ @Test
+ @Config(maxSdk = Build.VERSION_CODES.P)
+ public void testCanAuthenticate_ReturnsError_WhenUnsecured_BiometricOnly_OnApi28AndBelow() {
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+ when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+
+ final BiometricManager.Injector injector = new TestInjector.Builder()
+ .setFingerprintManager(mFingerprintManager)
+ .setDeviceSecurable(true)
+ .setFingerprintHardwarePresent(false)
+ .build();
+
+ final BiometricManager biometricManager = new BiometricManager(injector);
+ final int authenticators = Authenticators.BIOMETRIC_WEAK;
+ assertThat(biometricManager.canAuthenticate(authenticators))
+ .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ public void testCanAuthenticate_ReturnsError_WhenUnsecured_CredentialAllowed_OnApi29() {
+ final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+ mock(android.hardware.biometrics.BiometricManager.class);
+ when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+ when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+
+ final BiometricManager.Injector injector = new TestInjector.Builder()
+ .setBiometricManager(frameworkBiometricManager)
+ .setDeviceSecurable(true)
+ .setFingerprintHardwarePresent(false)
+ .build();
+
+ final BiometricManager biometricManager = new BiometricManager(injector);
+ final int authenticators = Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL;
+ assertThat(biometricManager.canAuthenticate(authenticators))
.isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
}
@Test
@Config(maxSdk = Build.VERSION_CODES.P)
- public void testCanAuthenticate_ReturnsError_WhenDeviceNotSecured_OnApi28AndBelow() {
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+ public void testCanAuthenticate_ReturnsError_WhenUnsecured_CredentialAllowed_OnApi28AndBelow() {
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+ when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
final BiometricManager.Injector injector = new TestInjector.Builder()
.setFingerprintManager(mFingerprintManager)
.setDeviceSecurable(true)
- .setFingerprintHardwarePresent(true)
+ .setFingerprintHardwarePresent(false)
.build();
final BiometricManager biometricManager = new BiometricManager(injector);
- final int authenticators = Authenticators.BIOMETRIC_WEAK;
+ final int authenticators = Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL;
assertThat(biometricManager.canAuthenticate(authenticators))
.isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
}
diff --git a/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/AuthPromptTestActivity.kt b/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/AuthPromptTestActivity.kt
index 896f04e8..0e92e2a 100755
--- a/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/AuthPromptTestActivity.kt
+++ b/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/AuthPromptTestActivity.kt
@@ -26,7 +26,7 @@
import androidx.biometric.auth.AuthPromptCallback
import androidx.biometric.auth.startClass2BiometricAuthentication
import androidx.biometric.auth.startClass2BiometricOrCredentialAuthentication
-import androidx.biometric.auth.startClass3BiometricAuthentication
+import androidx.biometric.auth.authenticateWithClass3Biometrics
import androidx.biometric.auth.startClass3BiometricOrCredentialAuthentication
import androidx.biometric.auth.startCredentialAuthentication
import androidx.biometric.integration.testapp.R.string.biometric_prompt_description
@@ -191,7 +191,7 @@
)
R.id.class3_biometric_button ->
- startClass3BiometricAuthentication(
+ authenticateWithClass3Biometrics(
crypto = createCryptoOrNull(),
title = title,
subtitle = subtitle,
diff --git a/biometric/settings.gradle b/biometric/settings.gradle
index 697db78..7a96db8 100644
--- a/biometric/settings.gradle
+++ b/biometric/settings.gradle
@@ -20,6 +20,7 @@
setupPlayground(this, "..")
selectProjectsFromAndroidX({ name ->
if (name.startsWith(":biometric")) return true
+ if (name == ":annotation:annotation-sampled") return true
return false
})
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
index 629c6f6..1663e7b 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
@@ -129,6 +129,24 @@
}
@Test
+ public void exceedMaxImagesWithoutClosing_doNotCrash() throws InterruptedException {
+ // Arrange.
+ int queueDepth = 3;
+ Semaphore semaphore = new Semaphore(0);
+ ImageAnalysis useCase = new ImageAnalysis.Builder()
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_BLOCK_PRODUCER)
+ .setImageQueueDepth(queueDepth)
+ .build();
+ useCase.setAnalyzer(CameraXExecutors.newHandlerExecutor(mHandler),
+ image -> semaphore.release());
+ // Act.
+ mCamera = CameraUtil.createCameraAndAttachUseCase(mContext,
+ CameraSelector.DEFAULT_FRONT_CAMERA, useCase);
+ // Assert: waiting for images does not crash.
+ assertThat(semaphore.tryAcquire(queueDepth + 1, /*timeout=*/1, TimeUnit.SECONDS)).isFalse();
+ }
+
+ @Test
public void canSupportGuaranteedSizeFront()
throws InterruptedException, CameraInfoUnavailableException {
// CameraSelector.LENS_FACING_FRONT/LENS_FACING_BACK are defined as constant int 0 and 1.
diff --git a/camera/camera-core/lint-baseline.xml b/camera/camera-core/lint-baseline.xml
index 65899e9..1df7dc7 100644
--- a/camera/camera-core/lint-baseline.xml
+++ b/camera/camera-core/lint-baseline.xml
@@ -334,50 +334,6 @@
<issue
id="BanSynchronizedMethods"
message="Use of synchronized methods is not recommended"
- errorLine1=" @Override"
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java"
- line="73"
- column="5"/>
- </issue>
-
- <issue
- id="BanSynchronizedMethods"
- message="Use of synchronized methods is not recommended"
- errorLine1=" @Override"
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java"
- line="82"
- column="5"/>
- </issue>
-
- <issue
- id="BanSynchronizedMethods"
- message="Use of synchronized methods is not recommended"
- errorLine1=" /**"
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java"
- line="91"
- column="5"/>
- </issue>
-
- <issue
- id="BanSynchronizedMethods"
- message="Use of synchronized methods is not recommended"
- errorLine1=" /**"
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java"
- line="102"
- column="5"/>
- </issue>
-
- <issue
- id="BanSynchronizedMethods"
- message="Use of synchronized methods is not recommended"
errorLine1=" @Override"
errorLine2=" ^">
<location
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 9554cff..97735cb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -215,7 +215,6 @@
tryUpdateRelativeRotation();
- mImageAnalysisAbstractAnalyzer.open();
imageReaderProxy.setOnImageAvailableListener(mImageAnalysisAbstractAnalyzer,
backgroundExecutor);
@@ -233,7 +232,8 @@
sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
clearPipeline();
-
+ // Clear cache so app won't get a outdated image.
+ mImageAnalysisAbstractAnalyzer.clearCache();
// Ensure the attached camera has not changed before resetting.
// TODO(b/143915543): Ensure this never gets called by a camera that is not attached
// to this use case so we don't need to do this check.
@@ -256,8 +256,6 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
void clearPipeline() {
Threads.checkMainThread();
- mImageAnalysisAbstractAnalyzer.close();
-
if (mDeferrableSurface != null) {
mDeferrableSurface.close();
mDeferrableSurface = null;
@@ -272,7 +270,6 @@
public void clearAnalyzer() {
synchronized (mAnalysisLock) {
mImageAnalysisAbstractAnalyzer.setAnalyzer(null, null);
- mImageAnalysisAbstractAnalyzer.close();
if (mSubscribedAnalyzer != null) {
notifyInactive();
}
@@ -363,7 +360,6 @@
*/
public void setAnalyzer(@NonNull Executor executor, @NonNull Analyzer analyzer) {
synchronized (mAnalysisLock) {
- mImageAnalysisAbstractAnalyzer.open();
mImageAnalysisAbstractAnalyzer.setAnalyzer(executor, image -> {
if (getViewPortCropRect() != null) {
image.setCropRect(getViewPortCropRect());
@@ -430,6 +426,7 @@
@Override
public void onDetached() {
clearPipeline();
+ mImageAnalysisAbstractAnalyzer.detach();
}
/**
@@ -460,14 +457,7 @@
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
public void onAttached() {
- synchronized (mAnalysisLock) {
- // The use case should be reused so that mSubscribedAnalyzer is not null but
- // mImageAnalysisAbstractAnalyzer is closed. Re-open mImageAnalysisAbstractAnalyzer
- // after the use case is attached to make it work again.
- if (mSubscribedAnalyzer != null && mImageAnalysisAbstractAnalyzer.isClosed()) {
- mImageAnalysisAbstractAnalyzer.open();
- }
- }
+ mImageAnalysisAbstractAnalyzer.attach();
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java
index 01f33b4..19777ad 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java
@@ -17,6 +17,7 @@
package androidx.camera.core;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.impl.ImageReaderProxy;
import androidx.camera.core.impl.utils.futures.Futures;
@@ -26,7 +27,6 @@
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Abstract Analyzer that wraps around {@link ImageAnalysis.Analyzer} and implements
@@ -37,6 +37,8 @@
*/
abstract class ImageAnalysisAbstractAnalyzer implements ImageReaderProxy.OnImageAvailableListener {
+ private static final String TAG = "ImageAnalysisAnalyzer";
+
// Member variables from ImageAnalysis.
@GuardedBy("mAnalyzerLock")
private ImageAnalysis.Analyzer mSubscribedAnalyzer;
@@ -44,16 +46,48 @@
@GuardedBy("mAnalyzerLock")
private Executor mUserExecutor;
+ // Lock that synchronizes the access to mSubscribedAnalyzer/mUserExecutor to prevent mismatch.
private final Object mAnalyzerLock = new Object();
- // Flag that reflects the state of ImageAnalysis.
- private AtomicBoolean mIsClosed;
+ // Flag that reflects the attaching state of the holding ImageAnalysis object.
+ protected boolean mIsAttached = true;
- ImageAnalysisAbstractAnalyzer() {
- mIsClosed = new AtomicBoolean(false);
+ @Override
+ public void onImageAvailable(@NonNull ImageReaderProxy imageReaderProxy) {
+ try {
+ ImageProxy imageProxy = acquireImage(imageReaderProxy);
+ if (imageProxy != null) {
+ onValidImageAvailable(imageProxy);
+ }
+ } catch (IllegalStateException e) {
+ // This happens if image is not closed in STRATEGY_BLOCK_PRODUCER mode. Catch the
+ // exception and fail with an error log.
+ // TODO(b/175851631): it may also happen when STRATEGY_KEEP_ONLY_LATEST not closing
+ // the cached image properly. We are unclear why it happens but catching the
+ // exception should improve the situation by not crashing.
+ Logger.e(TAG, "Failed to acquire image.", e);
+ }
}
/**
+ * Implemented by children to acquireImage via {@link ImageReaderProxy#acquireLatestImage()} or
+ * {@link ImageReaderProxy#acquireNextImage()}.
+ */
+ @Nullable
+ abstract ImageProxy acquireImage(@NonNull ImageReaderProxy imageReaderProxy);
+
+ /**
+ * Called when a new valid {@link ImageProxy} becomes available via
+ * {@link ImageReaderProxy.OnImageAvailableListener}.
+ */
+ abstract void onValidImageAvailable(@NonNull ImageProxy imageProxy);
+
+ /**
+ * Called by {@link ImageAnalysis} to release cached images.
+ */
+ abstract void clearCache();
+
+ /**
* Analyzes a {@link ImageProxy} using the wrapped {@link ImageAnalysis.Analyzer}.
*
* <p> The analysis will run on the executor provided by {@link #setAnalyzer(Executor,
@@ -76,9 +110,9 @@
if (analyzer != null && executor != null) {
// When the analyzer exists and ImageAnalysis is active.
future = CallbackToFutureAdapter.getFuture(
- completer -> {
+ completer -> {
executor.execute(() -> {
- if (!isClosed()) {
+ if (mIsAttached) {
ImageInfo imageInfo = ImmutableImageInfo.create(
imageProxy.getImageInfo().getTagBundle(),
imageProxy.getImageInfo().getTimestamp(),
@@ -87,15 +121,15 @@
analyzer.analyze(new SettableImageProxy(imageProxy, imageInfo));
completer.set(null);
} else {
- completer.setException(new OperationCanceledException("Closed "
- + "before analysis"));
+ completer.setException(new OperationCanceledException(
+ "ImageAnalysis is detached"));
}
});
- return "analyzeImage";
+ return "analyzeImage";
});
} else {
- future = Futures.immediateFailedFuture(new OperationCanceledException("No analyzer "
- + "or executor currently set."));
+ future = Futures.immediateFailedFuture(new OperationCanceledException(
+ "No analyzer or executor currently set."));
}
return future;
@@ -108,6 +142,9 @@
void setAnalyzer(@Nullable Executor userExecutor,
@Nullable ImageAnalysis.Analyzer subscribedAnalyzer) {
synchronized (mAnalyzerLock) {
+ if (subscribedAnalyzer == null) {
+ clearCache();
+ }
mSubscribedAnalyzer = subscribedAnalyzer;
mUserExecutor = userExecutor;
}
@@ -116,19 +153,15 @@
/**
* Initialize the callback.
*/
- void open() {
- mIsClosed.set(false);
+ void attach() {
+ mIsAttached = true;
}
/**
* Closes the callback so that it will stop posting to analyzer.
*/
- void close() {
- mIsClosed.set(true);
+ void detach() {
+ mIsAttached = false;
+ clearCache();
}
-
- boolean isClosed() {
- return mIsClosed.get();
- }
-
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java
index 4a43004..df579f5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java
@@ -17,6 +17,7 @@
package androidx.camera.core;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.camera.core.impl.ImageReaderProxy;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
@@ -31,14 +32,16 @@
*/
final class ImageAnalysisBlockingAnalyzer extends ImageAnalysisAbstractAnalyzer {
+ @Nullable
@Override
- public void onImageAvailable(@NonNull ImageReaderProxy imageReaderProxy) {
- ImageProxy image = imageReaderProxy.acquireNextImage();
- if (image == null) {
- return;
- }
+ ImageProxy acquireImage(@NonNull ImageReaderProxy imageReaderProxy) {
+ // Use acquireNextImage() so it never drops older images.
+ return imageReaderProxy.acquireNextImage();
+ }
- ListenableFuture<Void> analyzeFuture = analyzeImage(image);
+ @Override
+ void onValidImageAvailable(@NonNull ImageProxy imageProxy) {
+ ListenableFuture<Void> analyzeFuture = analyzeImage(imageProxy);
// Callback to close the image only after analysis complete regardless of success
Futures.addCallback(analyzeFuture, new FutureCallback<Void>() {
@@ -49,8 +52,13 @@
@Override
public void onFailure(Throwable t) {
- image.close();
+ imageProxy.close();
}
}, CameraXExecutors.directExecutor());
}
+
+ @Override
+ void clearCache() {
+ // no-op. The blocking analyzer does not cache images.
+ }
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java
index a9a3151..792b3b8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java
@@ -18,17 +18,15 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.camera.core.impl.ImageReaderProxy;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
/**
* OnImageAvailableListener with non-blocking behavior. Analyzes images in a non-blocking way by
@@ -38,65 +36,33 @@
*/
final class ImageAnalysisNonBlockingAnalyzer extends ImageAnalysisAbstractAnalyzer {
- private static final String TAG = "NonBlockingCallback";
-
// The executor for managing cached image.
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Executor mBackgroundExecutor;
+ private final Object mLock = new Object();
+
// The cached image when analyzer is busy. Image removed from cache must be closed by 1) closing
// it directly or 2) re-posting it to close it eventually.
- @GuardedBy("this")
- private ImageProxy mCachedImage;
+ @GuardedBy("mLock")
+ @Nullable
+ @VisibleForTesting
+ ImageProxy mCachedImage;
- // Timestamp of the last image posted to user callback thread.
- private final AtomicLong mPostedImageTimestamp;
-
- private final AtomicReference<CacheAnalyzingImageProxy> mPostedImage;
+ // The latest unclosed image sent to the app.
+ @GuardedBy("mLock")
+ @Nullable
+ private CacheAnalyzingImageProxy mPostedImage;
ImageAnalysisNonBlockingAnalyzer(Executor executor) {
mBackgroundExecutor = executor;
- mPostedImage = new AtomicReference<>();
- mPostedImageTimestamp = new AtomicLong();
- open();
}
+ @Nullable
@Override
- public void onImageAvailable(@NonNull ImageReaderProxy imageReaderProxy) {
- ImageProxy imageProxy = imageReaderProxy.acquireLatestImage();
- if (imageProxy == null) {
- return;
- }
- analyze(imageProxy);
- }
-
- @Override
- synchronized void open() {
- super.open();
- if (mCachedImage != null) {
- mCachedImage.close();
- mCachedImage = null;
- }
- }
-
- @Override
- synchronized void close() {
- super.close();
- if (mCachedImage != null) {
- mCachedImage.close();
- mCachedImage = null;
- }
- }
-
- /**
- * Removes cached image from cache and analyze it.
- */
- synchronized void analyzeCachedImage() {
- if (mCachedImage != null) {
- ImageProxy cachedImage = mCachedImage;
- mCachedImage = null;
- analyze(cachedImage);
- }
+ ImageProxy acquireImage(@NonNull ImageReaderProxy imageReaderProxy) {
+ // Use acquireLatestImage() so older images should be released.
+ return imageReaderProxy.acquireLatestImage();
}
/**
@@ -105,60 +71,82 @@
*
* @param imageProxy the incoming image frame.
*/
- private synchronized void analyze(@NonNull ImageProxy imageProxy) {
- if (isClosed()) {
- imageProxy.close();
- return;
- }
+ @Override
+ void onValidImageAvailable(@NonNull ImageProxy imageProxy) {
+ synchronized (mLock) {
+ if (!mIsAttached) {
+ imageProxy.close();
+ return;
+ }
+ if (mPostedImage != null) {
+ // There is unclosed image held by the app. The incoming image has to wait.
- CacheAnalyzingImageProxy postedImage = mPostedImage.get();
- if (postedImage != null
- && imageProxy.getImageInfo().getTimestamp() <= mPostedImageTimestamp.get()) {
- // Discard image that is in wrong order. Reposted cached image can be in this state.
- imageProxy.close();
- return;
- }
+ if (imageProxy.getImageInfo().getTimestamp()
+ <= mPostedImage.getImageInfo().getTimestamp()) {
+ // Discard the incoming image that is in the wrong order. Cached image can be
+ // in this state.
+ imageProxy.close();
+ } else {
+ // Otherwise cache the incoming image and repost it later.
+ if (mCachedImage != null) {
+ mCachedImage.close();
+ }
+ mCachedImage = imageProxy;
+ }
+ return;
+ }
- if (postedImage != null && !postedImage.isClosed()) {
- // If the posted image hasn't been closed, cache the new image.
+ // Post the incoming image to app.
+ final CacheAnalyzingImageProxy newPostedImage = new CacheAnalyzingImageProxy(imageProxy,
+ this);
+ mPostedImage = newPostedImage;
+ Futures.addCallback(analyzeImage(newPostedImage), new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ // No-op. If the post is successful, app should close it.
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ // Close the image if we didn't post it to user.
+ newPostedImage.close();
+ }
+ }, CameraXExecutors.directExecutor());
+ }
+ }
+
+ @Override
+ void clearCache() {
+ synchronized (mLock) {
if (mCachedImage != null) {
mCachedImage.close();
+ mCachedImage = null;
}
- mCachedImage = imageProxy;
- return;
}
-
- final CacheAnalyzingImageProxy newPostedImage = new CacheAnalyzingImageProxy(imageProxy,
- this);
- mPostedImage.set(newPostedImage);
- mPostedImageTimestamp.set(newPostedImage.getImageInfo().getTimestamp());
-
- ListenableFuture<Void> analyzeFuture = analyzeImage(newPostedImage);
-
- // Callback to close the image only after analysis complete regardless of success
- Futures.addCallback(analyzeFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(Void result) {
- // No-op. Keep dropping the images until user closes the current one.
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Close the image if we didn't post it to user.
- newPostedImage.close();
- }
- }, CameraXExecutors.directExecutor());
}
/**
- * An {@link ImageProxy} which will trigger analysis of the cached ImageProxy if it exists.
+ * Removes cached image from cache and analyze it.
+ */
+ void analyzeCachedImage() {
+ synchronized (mLock) {
+ mPostedImage = null;
+ if (mCachedImage != null) {
+ ImageProxy cachedImage = mCachedImage;
+ mCachedImage = null;
+ onValidImageAvailable(cachedImage);
+ }
+ }
+ }
+
+ /**
+ * An {@link ImageProxy} that analyze cached image on close.
*/
static class CacheAnalyzingImageProxy extends ForwardingImageProxy {
- // So that if the user holds onto the ImageProxy instance the analyzer can still be GC'ed
- WeakReference<ImageAnalysisNonBlockingAnalyzer> mNonBlockingAnalyzerWeakReference;
-
- private boolean mClosed = false;
+ // WeakReference so that if the app holds onto the ImageProxy instance the analyzer can
+ // still be GCed.
+ final WeakReference<ImageAnalysisNonBlockingAnalyzer> mNonBlockingAnalyzerWeakReference;
/**
* Creates a new instance which wraps the given image.
@@ -172,16 +160,11 @@
mNonBlockingAnalyzerWeakReference = new WeakReference<>(nonBlockingAnalyzer);
addOnImageCloseListener((imageProxy) -> {
- mClosed = true;
ImageAnalysisNonBlockingAnalyzer analyzer = mNonBlockingAnalyzerWeakReference.get();
if (analyzer != null) {
analyzer.mBackgroundExecutor.execute(analyzer::analyzeCachedImage);
}
});
}
-
- boolean isClosed() {
- return mClosed;
- }
}
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java
index 7f647f5..3f48a51 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@@ -46,6 +45,9 @@
import java.util.concurrent.atomic.AtomicInteger;
+/**
+ * Unit tests for {@link ImageAnalysisNonBlockingAnalyzer}
+ */
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@@ -78,43 +80,36 @@
mImageAnalysisNonBlockingAnalyzer.setAnalyzer(CameraXExecutors.mainThreadExecutor(),
mAnalyzer);
mImageAnalysisNonBlockingAnalyzer.setRelativeRotation(ROTATION.get());
+ mImageAnalysisNonBlockingAnalyzer.attach();
}
@Test
- public void imageClosedAfterAnalyzerClosed() {
- mImageAnalysisNonBlockingAnalyzer.close();
-
+ public void imageClosedAfterAnalyzerDetached() {
+ // Arrange.
+ mImageAnalysisNonBlockingAnalyzer.detach();
+ // Act.
mImageAnalysisNonBlockingAnalyzer.onImageAvailable(mImageReaderProxy);
-
+ shadowOf(getMainLooper()).idle();
+ // Assert.
verify(mImageProxy, times(1)).close();
}
@Test
- public void analysisNotRunAfterAnalyzerClosed() {
- mImageAnalysisNonBlockingAnalyzer.close();
-
+ public void imageNotClosedWhenAnalyzerAttached() {
+ // Act.
mImageAnalysisNonBlockingAnalyzer.onImageAvailable(mImageReaderProxy);
-
- verifyZeroInteractions(mAnalyzer);
- }
-
- @Test
- public void imageNotClosedWhenAnalyzerOpen() {
- mImageAnalysisNonBlockingAnalyzer.open();
-
- mImageAnalysisNonBlockingAnalyzer.onImageAvailable(mImageReaderProxy);
-
+ shadowOf(getMainLooper()).idle();
+ // Assert.
verify(mImageProxy, never()).close();
}
@Test
- public void analysisRunWhenAnalyzerOpen() {
- mImageAnalysisNonBlockingAnalyzer.open();
-
+ public void analysisRunWhenAnalyzerAttached() {
+ // Act.
mImageAnalysisNonBlockingAnalyzer.onImageAvailable(mImageReaderProxy);
-
shadowOf(getMainLooper()).idle();
+ // Assert.
ArgumentCaptor<ImageProxy> imageProxyArgumentCaptor =
ArgumentCaptor.forClass(ImageProxy.class);
verify(mAnalyzer, times(1)).analyze(
@@ -129,15 +124,33 @@
}
@Test
- public void imageClosedWhenAnalyzerNull() {
- mImageAnalysisNonBlockingAnalyzer.setAnalyzer(CameraXExecutors.mainThreadExecutor(), null);
- mImageAnalysisNonBlockingAnalyzer.open();
+ public void setAnalyzerNull_incomingImageClosed() {
+ // Arrange.
+ mImageAnalysisNonBlockingAnalyzer.setAnalyzer(null, null);
+ // Act.
mImageAnalysisNonBlockingAnalyzer.onImageAvailable(mImageReaderProxy);
+ shadowOf(getMainLooper()).idle();
+ // Assert.
+ verify(mImageProxy).close();
+ }
- final ArgumentCaptor<ImageAnalysisNonBlockingAnalyzer.CacheAnalyzingImageProxy>
- imageProxyToAnalyze = ArgumentCaptor.forClass(
- ImageAnalysisNonBlockingAnalyzer.CacheAnalyzingImageProxy.class);
- verify(mImageAnalysisNonBlockingAnalyzer).analyzeImage(imageProxyToAnalyze.capture());
- assertThat(imageProxyToAnalyze.getValue().isClosed()).isTrue();
+ @Test
+ public void closeAnalyzer_cachedImageBecomesNull() {
+ // Arrange.
+ mImageAnalysisNonBlockingAnalyzer.mCachedImage = mImageProxy;
+ // Act.
+ mImageAnalysisNonBlockingAnalyzer.setAnalyzer(null, null);
+ // Assert.
+ assertThat(mImageAnalysisNonBlockingAnalyzer.mCachedImage).isNull();
+ }
+
+ @Test
+ public void detachAnalyzer_cachedImageBecomesNull() {
+ // Arrange.
+ mImageAnalysisNonBlockingAnalyzer.mCachedImage = mImageProxy;
+ // Act.
+ mImageAnalysisNonBlockingAnalyzer.detach();
+ // Assert.
+ assertThat(mImageAnalysisNonBlockingAnalyzer.mCachedImage).isNull();
}
}
diff --git a/car/app/app-aaos/api/current.txt b/car/app/app-aaos/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/car/app/app-aaos/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/car/app/app-aaos/api/public_plus_experimental_current.txt b/car/app/app-aaos/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/car/app/app-aaos/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/car/app/app-aaos/api/res-current.txt b/car/app/app-aaos/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/car/app/app-aaos/api/res-current.txt
diff --git a/car/app/app-aaos/api/restricted_current.txt b/car/app/app-aaos/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/car/app/app-aaos/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/car/app/app-aaos/build.gradle b/car/app/app-aaos/build.gradle
new file mode 100644
index 0000000..87a2082
--- /dev/null
+++ b/car/app/app-aaos/build.gradle
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+}
+
+dependencies {
+ // Add dependencies here
+}
+
+androidx {
+ name = "Android for Cars App Library AAOS Extension"
+ type = LibraryType.PUBLISHED_LIBRARY
+ mavenGroup = LibraryGroups.CAR_APP
+ inceptionYear = "2021"
+ description = "AAOS specific funationaltiy to build navigation, parking, and charging apps for cars"
+}
diff --git a/car/app/app-aaos/lint-baseline.xml b/car/app/app-aaos/lint-baseline.xml
new file mode 100644
index 0000000..8f1aa4b
--- /dev/null
+++ b/car/app/app-aaos/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+
+</issues>
diff --git a/car/app/app-aaos/src/androidTest/AndroidManifest.xml b/car/app/app-aaos/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..3a56865
--- /dev/null
+++ b/car/app/app-aaos/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.car.app.aaos.test">
+
+</manifest>
diff --git a/car/app/app-aaos/src/main/AndroidManifest.xml b/car/app/app-aaos/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c562b9f
--- /dev/null
+++ b/car/app/app-aaos/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.car.app.aaos">
+
+</manifest>
\ No newline at end of file
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 6312144..3b3a9a4 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -172,7 +172,7 @@
method public androidx.car.app.model.CarText? getTitle();
method public int getType();
method public boolean isStandard();
- method public androidx.car.app.model.Action.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Action.Builder newBuilder();
method public static String typeToString(int);
field public static final androidx.car.app.model.Action APP_ICON;
field public static final androidx.car.app.model.Action BACK;
@@ -236,7 +236,7 @@
method public androidx.core.graphics.drawable.IconCompat? getIcon();
method public androidx.car.app.model.CarColor? getTint();
method public int getType();
- method public androidx.car.app.model.CarIcon.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.CarIcon.Builder newBuilder();
field public static final androidx.car.app.model.CarIcon ALERT;
field public static final androidx.car.app.model.CarIcon APP_ICON;
field public static final androidx.car.app.model.CarIcon BACK;
@@ -251,6 +251,7 @@
public static final class CarIcon.Builder {
ctor public CarIcon.Builder(androidx.core.graphics.drawable.IconCompat);
+ ctor public CarIcon.Builder(androidx.car.app.model.CarIcon);
method public androidx.car.app.model.CarIcon build();
method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor?);
}
@@ -457,13 +458,14 @@
public class Metadata {
method public static androidx.car.app.model.Metadata.Builder builder();
method public androidx.car.app.model.Place? getPlace();
- method public androidx.car.app.model.Metadata.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Metadata.Builder newBuilder();
method public static androidx.car.app.model.Metadata ofPlace(androidx.car.app.model.Place);
field public static final androidx.car.app.model.Metadata EMPTY_METADATA;
}
public static final class Metadata.Builder {
ctor public Metadata.Builder();
+ ctor public Metadata.Builder(androidx.car.app.model.Metadata);
method public androidx.car.app.model.Metadata build();
method public androidx.car.app.model.Metadata.Builder setPlace(androidx.car.app.model.Place?);
}
@@ -532,11 +534,12 @@
method public static androidx.car.app.model.Place.Builder builder(androidx.car.app.model.LatLng);
method public androidx.car.app.model.LatLng getLatLng();
method public androidx.car.app.model.PlaceMarker? getMarker();
- method public androidx.car.app.model.Place.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Place.Builder newBuilder();
}
public static final class Place.Builder {
ctor public Place.Builder(androidx.car.app.model.LatLng);
+ ctor public Place.Builder(androidx.car.app.model.Place);
method public androidx.car.app.model.Place build();
method public androidx.car.app.model.Place.Builder setLatLng(androidx.car.app.model.LatLng);
method public androidx.car.app.model.Place.Builder setMarker(androidx.car.app.model.PlaceMarker?);
@@ -929,7 +932,6 @@
method public androidx.car.app.model.CarIcon? getLanesImage();
method public androidx.car.app.navigation.model.Maneuver? getManeuver();
method public androidx.car.app.model.CarText? getRoad();
- method public androidx.car.app.navigation.model.Step.Builder newBuilder();
}
public static final class Step.Builder {
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 6312144..3b3a9a4 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -172,7 +172,7 @@
method public androidx.car.app.model.CarText? getTitle();
method public int getType();
method public boolean isStandard();
- method public androidx.car.app.model.Action.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Action.Builder newBuilder();
method public static String typeToString(int);
field public static final androidx.car.app.model.Action APP_ICON;
field public static final androidx.car.app.model.Action BACK;
@@ -236,7 +236,7 @@
method public androidx.core.graphics.drawable.IconCompat? getIcon();
method public androidx.car.app.model.CarColor? getTint();
method public int getType();
- method public androidx.car.app.model.CarIcon.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.CarIcon.Builder newBuilder();
field public static final androidx.car.app.model.CarIcon ALERT;
field public static final androidx.car.app.model.CarIcon APP_ICON;
field public static final androidx.car.app.model.CarIcon BACK;
@@ -251,6 +251,7 @@
public static final class CarIcon.Builder {
ctor public CarIcon.Builder(androidx.core.graphics.drawable.IconCompat);
+ ctor public CarIcon.Builder(androidx.car.app.model.CarIcon);
method public androidx.car.app.model.CarIcon build();
method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor?);
}
@@ -457,13 +458,14 @@
public class Metadata {
method public static androidx.car.app.model.Metadata.Builder builder();
method public androidx.car.app.model.Place? getPlace();
- method public androidx.car.app.model.Metadata.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Metadata.Builder newBuilder();
method public static androidx.car.app.model.Metadata ofPlace(androidx.car.app.model.Place);
field public static final androidx.car.app.model.Metadata EMPTY_METADATA;
}
public static final class Metadata.Builder {
ctor public Metadata.Builder();
+ ctor public Metadata.Builder(androidx.car.app.model.Metadata);
method public androidx.car.app.model.Metadata build();
method public androidx.car.app.model.Metadata.Builder setPlace(androidx.car.app.model.Place?);
}
@@ -532,11 +534,12 @@
method public static androidx.car.app.model.Place.Builder builder(androidx.car.app.model.LatLng);
method public androidx.car.app.model.LatLng getLatLng();
method public androidx.car.app.model.PlaceMarker? getMarker();
- method public androidx.car.app.model.Place.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Place.Builder newBuilder();
}
public static final class Place.Builder {
ctor public Place.Builder(androidx.car.app.model.LatLng);
+ ctor public Place.Builder(androidx.car.app.model.Place);
method public androidx.car.app.model.Place build();
method public androidx.car.app.model.Place.Builder setLatLng(androidx.car.app.model.LatLng);
method public androidx.car.app.model.Place.Builder setMarker(androidx.car.app.model.PlaceMarker?);
@@ -929,7 +932,6 @@
method public androidx.car.app.model.CarIcon? getLanesImage();
method public androidx.car.app.navigation.model.Maneuver? getManeuver();
method public androidx.car.app.model.CarText? getRoad();
- method public androidx.car.app.navigation.model.Step.Builder newBuilder();
}
public static final class Step.Builder {
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 6312144..3b3a9a4 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -172,7 +172,7 @@
method public androidx.car.app.model.CarText? getTitle();
method public int getType();
method public boolean isStandard();
- method public androidx.car.app.model.Action.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Action.Builder newBuilder();
method public static String typeToString(int);
field public static final androidx.car.app.model.Action APP_ICON;
field public static final androidx.car.app.model.Action BACK;
@@ -236,7 +236,7 @@
method public androidx.core.graphics.drawable.IconCompat? getIcon();
method public androidx.car.app.model.CarColor? getTint();
method public int getType();
- method public androidx.car.app.model.CarIcon.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.CarIcon.Builder newBuilder();
field public static final androidx.car.app.model.CarIcon ALERT;
field public static final androidx.car.app.model.CarIcon APP_ICON;
field public static final androidx.car.app.model.CarIcon BACK;
@@ -251,6 +251,7 @@
public static final class CarIcon.Builder {
ctor public CarIcon.Builder(androidx.core.graphics.drawable.IconCompat);
+ ctor public CarIcon.Builder(androidx.car.app.model.CarIcon);
method public androidx.car.app.model.CarIcon build();
method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor?);
}
@@ -457,13 +458,14 @@
public class Metadata {
method public static androidx.car.app.model.Metadata.Builder builder();
method public androidx.car.app.model.Place? getPlace();
- method public androidx.car.app.model.Metadata.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Metadata.Builder newBuilder();
method public static androidx.car.app.model.Metadata ofPlace(androidx.car.app.model.Place);
field public static final androidx.car.app.model.Metadata EMPTY_METADATA;
}
public static final class Metadata.Builder {
ctor public Metadata.Builder();
+ ctor public Metadata.Builder(androidx.car.app.model.Metadata);
method public androidx.car.app.model.Metadata build();
method public androidx.car.app.model.Metadata.Builder setPlace(androidx.car.app.model.Place?);
}
@@ -532,11 +534,12 @@
method public static androidx.car.app.model.Place.Builder builder(androidx.car.app.model.LatLng);
method public androidx.car.app.model.LatLng getLatLng();
method public androidx.car.app.model.PlaceMarker? getMarker();
- method public androidx.car.app.model.Place.Builder newBuilder();
+ method @Deprecated public androidx.car.app.model.Place.Builder newBuilder();
}
public static final class Place.Builder {
ctor public Place.Builder(androidx.car.app.model.LatLng);
+ ctor public Place.Builder(androidx.car.app.model.Place);
method public androidx.car.app.model.Place build();
method public androidx.car.app.model.Place.Builder setLatLng(androidx.car.app.model.LatLng);
method public androidx.car.app.model.Place.Builder setMarker(androidx.car.app.model.PlaceMarker?);
@@ -929,7 +932,6 @@
method public androidx.car.app.model.CarIcon? getLanesImage();
method public androidx.car.app.navigation.model.Maneuver? getManeuver();
method public androidx.car.app.model.CarText? getRoad();
- method public androidx.car.app.navigation.model.Step.Builder newBuilder();
}
public static final class Step.Builder {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Action.java b/car/app/app/src/main/java/androidx/car/app/model/Action.java
index 3989dad..35d2146 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Action.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Action.java
@@ -144,8 +144,12 @@
/**
* Returns a {@link Builder} instance configured with the same data as this {@link Action}
* instance.
+ *
+ * @deprecated use constructor.
*/
+ // TODO(b/177484889): remove once host is changed to use new public ctor.
@NonNull
+ @Deprecated
public Builder newBuilder() {
return new Builder(this);
}
@@ -405,7 +409,13 @@
public Builder() {
}
- Builder(Action action) {
+ /**
+ * Returns a {@link Builder} instance configured with the same data as the given
+ * {@link Action} instance.
+ *
+ * @throws NullPointerException if {@code icon} is {@code null}.
+ */
+ Builder(@NonNull Action action) {
mTitle = action.getTitle();
mIcon = action.getIcon();
mBackgroundColor = action.getBackgroundColor();
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java b/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
index 008ba2a..b858168 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
@@ -212,7 +212,10 @@
/**
* Returns a {@link Builder} instance configured with the same data as this {@link CarIcon}
* instance.
+ * @deprecated use constructor.
*/
+ // TODO(b/177484889): remove once host is changed to use new public ctor.
+ @Deprecated
@NonNull
public Builder newBuilder() {
return new Builder(this);
@@ -427,7 +430,14 @@
mTint = null;
}
- Builder(@NonNull CarIcon carIcon) {
+ /**
+ * Returns a {@link Builder} instance configured with the same data as the given
+ * {@link CarIcon} instance.
+ *
+ * @throws NullPointerException if {@code icon} is {@code null}.
+ */
+ public Builder(@NonNull CarIcon carIcon) {
+ requireNonNull(carIcon);
mType = carIcon.getType();
mIcon = carIcon.getIcon();
mTint = carIcon.getTint();
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Metadata.java b/car/app/app/src/main/java/androidx/car/app/model/Metadata.java
index f5e152b..8894213 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Metadata.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Metadata.java
@@ -51,7 +51,12 @@
return new Builder().setPlace(requireNonNull(place)).build();
}
- /** Returns a new {@link Builder} with the data from this {@link Metadata} instance. */
+ /**
+ * Returns a new {@link Builder} with the data from this {@link Metadata} instance.
+ * @deprecated use constructor.
+ */
+ // TODO(b/177484889): remove once host is changed to use new public ctor.
+ @Deprecated
@NonNull
public Builder newBuilder() {
return new Builder(this);
@@ -117,8 +122,13 @@
public Builder() {
}
- Builder(Metadata metadata) {
- this.mPlace = metadata.getPlace();
+ /**
+ * Returns a new {@link Builder} with the data from the given {@link Metadata} instance.
+ *
+ * @throws NullPointerException if {@code icon} is {@code null}.
+ */
+ public Builder(@NonNull Metadata metadata) {
+ this.mPlace = requireNonNull(metadata).getPlace();
}
}
}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Place.java b/car/app/app/src/main/java/androidx/car/app/model/Place.java
index 2932e24..771f4f6 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Place.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Place.java
@@ -45,8 +45,13 @@
return new Builder(requireNonNull(latLng));
}
- /** Returns a {@link Builder} instance with the same data as this {@link Place} instance. */
+ /**
+ * Returns a {@link Builder} instance with the same data as this {@link Place} instance.
+ * @deprecated use constructor.
+ */
+ // TODO(b/177484889): remove once host is changed to use new public ctor.
@NonNull
+ @Deprecated
public Builder newBuilder() {
return new Builder(this);
}
@@ -113,7 +118,12 @@
mLatLng = latLng;
}
- Builder(Place place) {
+ /**
+ * Returns a {@link Builder} instance with the same data as the given {@link Place}
+ * instance.
+ */
+ public Builder(@NonNull Place place) {
+ requireNonNull(place);
mLatLng = place.getLatLng();
mMarker = place.getMarker();
}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
index 8d9c01d..0745a1b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
@@ -19,6 +19,8 @@
import static androidx.annotation.RestrictTo.Scope;
+import static java.util.Objects.requireNonNull;
+
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
@@ -59,13 +61,13 @@
*/
@NonNull
public static final ActionsConstraints ACTIONS_CONSTRAINTS_SIMPLE =
- ACTIONS_CONSTRAINTS_CONSERVATIVE.newBuilder().setMaxCustomTitles(1).build();
+ new ActionsConstraints.Builder(ACTIONS_CONSTRAINTS_CONSERVATIVE).setMaxCustomTitles(
+ 1).build();
/** Constraints for navigation templates. */
@NonNull
public static final ActionsConstraints ACTIONS_CONSTRAINTS_NAVIGATION =
- ACTIONS_CONSTRAINTS_CONSERVATIVE
- .newBuilder()
+ new ActionsConstraints.Builder(ACTIONS_CONSTRAINTS_CONSERVATIVE)
.setMaxActions(4)
.setMaxCustomTitles(1)
.addRequiredActionType(Action.TYPE_CUSTOM)
@@ -84,16 +86,6 @@
return new Builder();
}
- /**
- * Returns a new builder that contains the same data as this {@link ActionsConstraints}
- * instance.
- */
- @VisibleForTesting
- @NonNull
- public Builder newBuilder() {
- return new Builder(this);
- }
-
/** Returns the max number of actions allowed. */
public int getMaxActions() {
return mMaxActions;
@@ -239,7 +231,14 @@
public Builder() {
}
- Builder(ActionsConstraints constraints) {
+ /**
+ * Returns a new builder that contains the same data as the given {@link ActionsConstraints}
+ * instance.
+ *
+ * @throws NullPointerException if {@code latLng} is {@code null}.
+ */
+ public Builder(@NonNull ActionsConstraints constraints) {
+ requireNonNull(constraints);
this.mMaxActions = constraints.getMaxActions();
this.mMaxCustomTitles = constraints.getMaxCustomTitles();
this.mRequiredActionTypes.addAll(constraints.getRequiredActionTypes());
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
index 685cf14..e88c148 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
@@ -16,6 +16,8 @@
package androidx.car.app.model.constraints;
+import static java.util.Objects.requireNonNull;
+
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.car.app.model.CarIcon;
@@ -67,7 +69,7 @@
/** The constraints for a full-width row in a list (simple + toggle support). */
@NonNull
public static final RowConstraints ROW_CONSTRAINTS_FULL_LIST =
- ROW_CONSTRAINTS_SIMPLE.newBuilder().setToggleAllowed(true).build();
+ new RowConstraints.Builder(ROW_CONSTRAINTS_SIMPLE).setToggleAllowed(true).build();
private final int mMaxTextLinesPerRow;
private final int mMaxActionsExclusive;
@@ -85,14 +87,6 @@
return new Builder();
}
- /**
- * Returns a new builder that contains the same data as this {@link RowConstraints} instance.
- */
- @NonNull
- public Builder newBuilder() {
- return new Builder(this);
- }
-
/** Returns whether the row can have a click listener associated with it. */
public boolean isOnClickListenerAllowed() {
return mIsOnClickListenerAllowed;
@@ -225,10 +219,17 @@
}
/** Returns an empty {@link Builder} instance. */
- Builder() {
+ public Builder() {
}
- Builder(RowConstraints constraints) {
+ /**
+ * Returns a new builder that contains the same data as the given {@link RowConstraints}
+ * instance.
+ *
+ * @throws NullPointerException if {@code latLng} is {@code null}.
+ */
+ public Builder(@NonNull RowConstraints constraints) {
+ requireNonNull(constraints);
mIsOnClickListenerAllowed = constraints.isOnClickListenerAllowed();
mMaxTextLines = constraints.getMaxTextLinesPerRow();
mMaxActionsExclusive = constraints.getMaxActionsExclusive();
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
index d0b1464..e4bbcf5 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
@@ -21,6 +21,8 @@
import static androidx.car.app.model.constraints.RowConstraints.ROW_CONSTRAINTS_PANE;
import static androidx.car.app.model.constraints.RowConstraints.ROW_CONSTRAINTS_SIMPLE;
+import static java.util.Objects.requireNonNull;
+
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.car.app.model.Action;
@@ -50,8 +52,7 @@
/** Default constraints for heterogeneous pane of items, full width. */
@NonNull
public static final RowListConstraints ROW_LIST_CONSTRAINTS_PANE =
- ROW_LIST_CONSTRAINTS_CONSERVATIVE
- .newBuilder()
+ new RowListConstraints.Builder(ROW_LIST_CONSTRAINTS_CONSERVATIVE)
.setMaxActions(2)
.setRowConstraints(ROW_CONSTRAINTS_PANE)
.setAllowSelectableLists(false)
@@ -60,16 +61,14 @@
/** Default constraints for uniform lists of items, no toggles. */
@NonNull
public static final RowListConstraints ROW_LIST_CONSTRAINTS_SIMPLE =
- ROW_LIST_CONSTRAINTS_CONSERVATIVE
- .newBuilder()
+ new RowListConstraints.Builder(ROW_LIST_CONSTRAINTS_CONSERVATIVE)
.setRowConstraints(ROW_CONSTRAINTS_SIMPLE)
.build();
/** Default constraints for the route preview card. */
@NonNull
public static final RowListConstraints ROW_LIST_CONSTRAINTS_ROUTE_PREVIEW =
- ROW_LIST_CONSTRAINTS_CONSERVATIVE
- .newBuilder()
+ new RowListConstraints.Builder(ROW_LIST_CONSTRAINTS_CONSERVATIVE)
.setRowConstraints(ROW_CONSTRAINTS_SIMPLE)
.setAllowSelectableLists(true)
.build();
@@ -77,8 +76,7 @@
/** Default constraints for uniform lists of items, full width (simple + toggle support). */
@NonNull
public static final RowListConstraints ROW_LIST_CONSTRAINTS_FULL_LIST =
- ROW_LIST_CONSTRAINTS_CONSERVATIVE
- .newBuilder()
+ new RowListConstraints.Builder(ROW_LIST_CONSTRAINTS_CONSERVATIVE)
.setRowConstraints(ROW_CONSTRAINTS_FULL_LIST)
.setAllowSelectableLists(true)
.build();
@@ -94,12 +92,6 @@
return new Builder();
}
- /** Return a a new builder for this {@link RowListConstraints} instance. */
- @NonNull
- public Builder newBuilder() {
- return new Builder(this);
- }
-
/** Returns the maximum number of actions allowed to be added alongside the list. */
public int getMaxActions() {
return mMaxActions;
@@ -220,7 +212,13 @@
public Builder() {
}
- Builder(RowListConstraints constraints) {
+ /**
+ * Return a a new builder for the given {@link RowListConstraints} instance.
+ *
+ * @throws NullPointerException if {@code latLng} is {@code null}.
+ */
+ public Builder(@NonNull RowListConstraints constraints) {
+ requireNonNull(constraints);
this.mMaxActions = constraints.getMaxActions();
this.mRowConstraints = constraints.getRowConstraints();
this.mAllowSelectableLists = constraints.isAllowSelectableLists();
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
index 867f254..dbdc5b4 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
@@ -71,34 +71,42 @@
}
/**
- * Returns a new {@link Builder} instance configured with the same data as this {@link Step}
- * instance.
+ * Returns the maneuver to be performed on this step or {@code null} if this step doesn't
+ * involve a maneuver.
*/
- @NonNull
- public Builder newBuilder() {
- return new Builder(this);
- }
-
@Nullable
public Maneuver getManeuver() {
return mManeuver;
}
+ /**
+ * Returns a list of {@link Lane} that contains information of the road lanes at the point
+ * where the driver should execute this step.
+ */
@NonNull
public List<Lane> getLanes() {
return CollectionUtils.emptyIfNull(mLanes);
}
+ /**
+ * Returns the image representing all the lanes or {@code null} if no lanes image is available.
+ */
@Nullable
public CarIcon getLanesImage() {
return mLanesImage;
}
+ /**
+ * Returns the text description of this maneuver.
+ */
@Nullable
public CarText getCue() {
return mCue;
}
+ /**
+ * Returns the text description of the road for the step or {@code null} if unknown.
+ */
@Nullable
public CarText getRoad() {
return mRoad;
@@ -179,12 +187,10 @@
* Constructs a new builder of {@link Step} with a cue.
*
* <p>A cue must always be set when the step is created and is used as a fallback when
- * {@link
- * Maneuver} is not set or is unavailable.
+ * {@link Maneuver} is not set or is unavailable.
*
* <p>Some cluster displays do not support UTF-8 encoded characters, in which case
- * unsupported
- * characters will not be displayed properly.
+ * unsupported characters will not be displayed properly.
*
* @throws NullPointerException if {@code cue} is {@code null}.
* @see Builder#setCue(CharSequence)
@@ -193,19 +199,9 @@
this.mCue = CarText.create(requireNonNull(cue));
}
- Builder(Step step) {
- this.mManeuver = step.getManeuver();
- this.mLanes.clear();
- this.mLanes.addAll(step.getLanes());
- this.mLanesImage = step.getLanesImage();
- this.mCue = requireNonNull(step.getCue());
- this.mRoad = step.getRoad();
- }
-
/**
* Sets the maneuver to be performed on this step or {@code null} if this step doesn't
- * involve a
- * maneuver.
+ * involve a maneuver.
*/
@NonNull
public Builder setManeuver(@Nullable Maneuver maneuver) {
@@ -312,8 +308,7 @@
* Sets a text description of the road for the step or {@code null} if unknown.
*
* <p>This value is primarily used for vehicle cluster and heads-up displays and may not
- * appear
- * in the navigation template.
+ * appear in the navigation template.
*
* <p>For example, a {@link Step} for a left turn might provide "State Street" for the road.
*
diff --git a/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java b/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
index 414e404..8232f95 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
@@ -135,34 +135,6 @@
}
@Test
- public void create_invalidSetOnBackThrows() {
- assertThrows(
- IllegalStateException.class,
- () -> Action.BACK.newBuilder().setOnClickListener(() -> {
- }).build());
- assertThrows(
- IllegalStateException.class,
- () -> Action.BACK.newBuilder().setTitle("BACK").build());
- assertThrows(
- IllegalStateException.class,
- () -> Action.BACK.newBuilder().setIcon(CarIcon.ALERT).build());
- }
-
- @Test
- public void create_invalidSetOnAppIconThrows() {
- assertThrows(
- IllegalStateException.class,
- () -> Action.APP_ICON.newBuilder().setOnClickListener(() -> {
- }).build());
- assertThrows(
- IllegalStateException.class,
- () -> Action.APP_ICON.newBuilder().setTitle("APP").build());
- assertThrows(
- IllegalStateException.class,
- () -> Action.APP_ICON.newBuilder().setIcon(CarIcon.ALERT).build());
- }
-
- @Test
public void equals() {
String title = "foo";
CarIcon icon = CarIcon.ALERT;
diff --git a/car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java b/car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java
index 79f15fb..ef80907 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java
@@ -77,7 +77,7 @@
@Test
public void newBuilder_fromStandard() {
- CarIcon carIcon = BACK.newBuilder().setTint(GREEN).build();
+ CarIcon carIcon = new CarIcon.Builder(BACK).setTint(GREEN).build();
assertThat(carIcon.getType()).isEqualTo(TYPE_BACK);
assertThat(carIcon.getTint()).isEqualTo(GREEN);
@@ -144,6 +144,6 @@
@Test
public void notEquals() {
- assertThat(BACK.newBuilder().setTint(GREEN).build()).isNotEqualTo(BACK);
+ assertThat(new CarIcon.Builder(BACK).setTint(GREEN).build()).isNotEqualTo(BACK);
}
}
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index df76239..89beb5f 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -36,18 +36,26 @@
}
public final class AnimateAsStateKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.DpOffset> animateAsState-4E4yWWY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateBoundsAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateDpAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateFloatAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateIntAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateIntOffsetAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateIntSizeAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateOffsetAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRectAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSizeAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValueAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
}
public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index df76239..89beb5f 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -36,18 +36,26 @@
}
public final class AnimateAsStateKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.DpOffset> animateAsState-4E4yWWY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateBoundsAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateDpAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateFloatAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateIntAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateIntOffsetAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateIntSizeAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateOffsetAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRectAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSizeAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValueAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
}
public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 6f1cc23b..cafee25 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -36,18 +36,26 @@
}
public final class AnimateAsStateKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.DpOffset> animateAsState-4E4yWWY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateBoundsAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateDpAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateFloatAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateIntAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateIntOffsetAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateIntSizeAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateOffsetAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRectAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSizeAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValueAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
}
public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt
index d2a677c..31eeda6 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt
@@ -19,7 +19,11 @@
import androidx.annotation.Sampled
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.TwoWayConverter
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateValueAsState
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.animateIntOffsetAsState
+import androidx.compose.animation.core.animateOffsetAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
@@ -45,7 +49,7 @@
// This [animateState] returns a State<Float> object. The value of the State object is
// being updated by animation. (This method is overloaded for different parameter types.)
// Here we use the returned [State] object as a property delegate.
- val alpha: Float by animateAsState(if (visible) 1f else 0f)
+ val alpha: Float by animateFloatAsState(if (visible) 1f else 0f)
Box(modifier = Modifier.background(Color.Red).alpha(alpha))
}
}
@@ -69,9 +73,9 @@
// Animates a custom type value to the given target value, using a [TwoWayConverter]. The
// converter tells the animation system how to convert the custom type from and to
// [AnimationVector], so that it can be animated.
- val animSize: MySize by animateAsState<MySize, AnimationVector2D>(
+ val animSize: MySize by animateValueAsState(
mySize,
- TwoWayConverter(
+ TwoWayConverter<MySize, AnimationVector2D>(
convertToVector = { AnimationVector2D(it.width.value, it.height.value) },
convertFromVector = { MySize(it.v1.dp, it.v2.dp) }
)
@@ -86,7 +90,7 @@
@Composable
fun HeightAnimation(collapsed: Boolean) {
// Animates a height of [Dp] type to different target values based on the [collapsed] flag.
- val height: Dp by animateAsState(if (collapsed) 10.dp else 20.dp)
+ val height: Dp by animateDpAsState(if (collapsed) 10.dp else 20.dp)
Box(Modifier.fillMaxWidth().height(height).background(color = Color.Red))
}
}
@@ -98,15 +102,16 @@
@Composable
fun OffsetAnimation(selected: Boolean) {
// Animates the offset depending on the selected flag.
- // [animateAsState] returns a State<Offset> object. The value of the State object is
+ // [animateOffsetAsState] returns a State<Offset> object. The value of the State object is
// updated by the animation. Here we use that State<Offset> as a property delegate.
- val offset: Offset by animateAsState(
+ val offset: Offset by animateOffsetAsState(
if (selected) Offset(0f, 0f) else Offset(20f, 20f)
)
- // In this example, animateAsState returns a State<IntOffset>. The value of the returned
+ // In this example, animateIntOffsetAsState returns a State<IntOffset>. The value of the
+ // returned
// State object is updated by the animation.
- val intOffset: IntOffset by animateAsState(
+ val intOffset: IntOffset by animateIntOffsetAsState(
if (selected) IntOffset(0, 0) else IntOffset(50, 50)
)
}
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/InfiniteTransitionSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/InfiniteTransitionSamples.kt
index d01dc7c..1977ca9 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/InfiniteTransitionSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/InfiniteTransitionSamples.kt
@@ -83,7 +83,8 @@
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
- Modifier.align(Alignment.Center)
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.Center)
.graphicsLayer(
scaleX = scale,
scaleY = scale
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt
index 43d0a9e..01fb138 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt
@@ -93,7 +93,8 @@
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
- Modifier.align(Alignment.Center)
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.Center)
.graphicsLayer(
scaleX = 3.0f,
scaleY = 3.0f,
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
index e58f68a..31b6963 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
@@ -16,7 +16,7 @@
package androidx.compose.animation.core
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.dispatch.withFrameNanos
@@ -31,7 +31,6 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.Bounds
-import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,7 +59,7 @@
var enabled by mutableStateOf(false)
rule.setContent {
Box {
- val animationValue by animateAsState(
+ val animationValue by animateDpAsState(
if (enabled) 50.dp else 250.dp, myTween()
)
// TODO: Properly test this with a deterministic clock when the test framework is
@@ -93,7 +92,7 @@
rule.setContent {
Box {
// Animate from 250f to 50f when enable flips to true
- val animationValue by animateAsState(
+ val animationValue by animateFloatAsState(
if (enabled) 50f else 250f, tween(200, easing = FastOutLinearInEasing)
)
// TODO: Properly test this with a deterministic clock when the test framework is
@@ -135,20 +134,7 @@
var enabled by mutableStateOf(false)
rule.setContent {
Box {
- val vectorValue by animateAsState(
- if (enabled) endVal else startVal,
- tween()
- )
-
- val positionValue by animateAsState(
- if (enabled)
- DpOffset.VectorConverter.convertFromVector(endVal)
- else
- DpOffset.VectorConverter.convertFromVector(startVal),
- tween()
- )
-
- val sizeValue by animateAsState(
+ val sizeValue by animateSizeAsState(
if (enabled)
Size.VectorConverter.convertFromVector(endVal)
else
@@ -156,7 +142,7 @@
tween()
)
- val pxPositionValue by animateAsState(
+ val pxPositionValue by animateOffsetAsState(
if (enabled)
Offset.VectorConverter.convertFromVector(endVal)
else
@@ -175,13 +161,8 @@
lerp(startVal.v2, endVal.v2, playTime / 100f)
)
- assertEquals(expect, vectorValue)
assertEquals(Size.VectorConverter.convertFromVector(expect), sizeValue)
assertEquals(
- DpOffset.VectorConverter.convertFromVector(expect),
- positionValue
- )
- assertEquals(
Offset.VectorConverter.convertFromVector(expect),
pxPositionValue
)
@@ -210,12 +191,7 @@
var enabled by mutableStateOf(false)
rule.setContent {
Box {
- val vectorValue by animateAsState(
- if (enabled) endVal else startVal,
- tween()
- )
-
- val boundsValue by animateAsState(
+ val boundsValue by animateBoundsAsState(
if (enabled)
Bounds.VectorConverter.convertFromVector(endVal)
else
@@ -223,7 +199,7 @@
tween()
)
- val pxBoundsValue by animateAsState(
+ val pxBoundsValue by animateRectAsState(
if (enabled)
Rect.VectorConverter.convertFromVector(endVal)
else
@@ -246,7 +222,6 @@
lerp(startVal.v4, endVal.v4, fraction)
)
- assertEquals(expect, vectorValue)
assertEquals(
Bounds.VectorConverter.convertFromVector(expect),
boundsValue
@@ -280,12 +255,8 @@
var enabled by mutableStateOf(false)
rule.setContent {
Box {
- val vectorValue by animateAsState(
- if (enabled) endVal else startVal,
- tween()
- )
- val boundsValue by animateAsState(
+ val boundsValue by animateBoundsAsState(
if (enabled)
Bounds.VectorConverter.convertFromVector(endVal)
else
@@ -306,7 +277,6 @@
lerp(startVal.v4, endVal.v4, fraction)
)
- assertEquals(expect, vectorValue)
assertEquals(
Bounds.VectorConverter.convertFromVector(expect),
boundsValue
@@ -327,7 +297,7 @@
var enabled by mutableStateOf(false)
rule.setContent {
Box {
- val value by animateAsState(
+ val value by animateColorAsState(
if (enabled) Color.Cyan else Color.Black,
TweenSpec(
durationMillis = 100,
@@ -358,36 +328,29 @@
fun visibilityThresholdTest() {
val specForFloat = FloatSpringSpec(visibilityThreshold = 0.01f)
- val specForVector = FloatSpringSpec(visibilityThreshold = 0.5f)
val specForOffset = FloatSpringSpec(visibilityThreshold = 0.5f)
val specForBounds = FloatSpringSpec(visibilityThreshold = 0.1f)
var enabled by mutableStateOf(false)
rule.setContent {
Box {
- val vectorValue by animateAsState(
- if (enabled) AnimationVector(100f) else AnimationVector(0f),
- visibilityThreshold = AnimationVector(0.5f)
- )
-
- val offsetValue by animateAsState(
+ val offsetValue by animateOffsetAsState(
if (enabled)
Offset(100f, 100f)
else
Offset(0f, 0f)
)
- val boundsValue by animateAsState(
+ val boundsValue by animateBoundsAsState(
if (enabled)
Bounds(100.dp, 100.dp, 100.dp, 100.dp)
else
Bounds(0.dp, 0.dp, 0.dp, 0.dp)
)
- val floatValue by animateAsState(if (enabled) 100f else 0f)
+ val floatValue by animateFloatAsState(if (enabled) 100f else 0f)
val durationForFloat = specForFloat.getDurationMillis(0f, 100f, 0f)
- val durationForVector = specForVector.getDurationMillis(0f, 100f, 0f)
val durationForOffset = specForOffset.getDurationMillis(0f, 100f, 0f)
val durationForBounds = specForBounds.getDurationMillis(0f, 100f, 0f)
@@ -400,13 +363,6 @@
val expectFloat = specForFloat.getValue(playTime, 0f, 100f, 0f)
assertEquals("play time: $playTime", expectFloat, floatValue)
- if (playTime < durationForVector) {
- val expectVector = specForVector.getValue(playTime, 0f, 100f, 0f)
- assertEquals(AnimationVector(expectVector), vectorValue)
- } else {
- assertEquals(AnimationVector(100f), vectorValue)
- }
-
if (playTime < durationForOffset) {
val expectOffset = specForOffset.getValue(playTime, 0f, 100f, 0f)
assertEquals(Offset(expectOffset, expectOffset), offsetValue)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
index 386ddc0..932f7fa 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
@@ -27,7 +27,6 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.Bounds
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -40,10 +39,10 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
- * updated by the animation until the animation finishes.
+ * [animateFloatAsState] returns a [State] object. The value of the state object will continuously
+ * be updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateFloatAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedFloat][androidx.compose.animation.animatedFloat] for cancelable
* animations.
*
@@ -58,7 +57,7 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateFloatAsState(
targetValue: Float,
animationSpec: AnimationSpec<Float> = defaultAnimation,
visibilityThreshold: Float = 0.01f,
@@ -87,6 +86,27 @@
return animationState
}
+@Composable
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateFloatAsState(targetValue, animationSpec, visibilityThreshold, " +
+ "finishedListener)",
+ "androidx.compose.animation.core.animateFloatAsState"
+ )
+)
+fun animateAsState(
+ targetValue: Float,
+ animationSpec: AnimationSpec<Float> = defaultAnimation,
+ visibilityThreshold: Float = 0.01f,
+ finishedListener: ((Float) -> Unit)? = null
+): State<Float> = animateFloatAsState(
+ targetValue,
+ animationSpec,
+ visibilityThreshold,
+ finishedListener
+)
+
/**
* Fire-and-forget animation function for [Dp]. This Composable function is overloaded for
* different parameter types such as [Float], [Color][androidx.compose.ui.graphics.Color], [Offset],
@@ -94,10 +114,10 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateDpAsState] returns a [State] object. The value of the state object will continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateDpAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
@@ -110,14 +130,12 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateDpAsState(
targetValue: Dp,
- animationSpec: AnimationSpec<Dp> = remember {
- spring(visibilityThreshold = Dp.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
- return animateAsState(
+ return animateValueAsState(
targetValue,
Dp.VectorConverter,
animationSpec,
@@ -125,41 +143,21 @@
)
}
-/**
- * Fire-and-forget animation function for [DpOffset]. This Composable function is overloaded for
- * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
- * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
- * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
- * course to animate towards the new target value.
- *
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
- * updated by the animation until the animation finishes.
- *
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
- * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
- * animations.
- *
- * val position: DpOffset by animateAsState(
- * if (selected) DpOffset(0.dp, 0.dp) else DpOffset(20.dp, 20.dp))
- *
- * @param targetValue Target value of the animation
- * @param animationSpec The animation that will be used to change the value through time. Physics
- * animation will be used by default.
- * @param finishedListener An optional end listener to get notified when the animation is finished.
- * @return A [State] object, the value of which is updated by animation.
- */
+private val dpDefaultSpring = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateDpAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateDpAsState"
+ )
+)
@Composable
fun animateAsState(
- targetValue: DpOffset,
- animationSpec: AnimationSpec<DpOffset> = remember {
- spring(visibilityThreshold = DpOffset.VisibilityThreshold)
- },
- finishedListener: ((DpOffset) -> Unit)? = null
-): State<DpOffset> {
- return animateAsState(
- targetValue, DpOffset.VectorConverter, animationSpec, finishedListener = finishedListener
- )
-}
+ targetValue: Dp,
+ animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
+ finishedListener: ((Dp) -> Unit)? = null
+): State<Dp> = animateDpAsState(targetValue, animationSpec, finishedListener)
/**
* Fire-and-forget animation function for [Size]. This Composable function is overloaded for
@@ -168,14 +166,14 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateSizeAsState] returns a [State] object. The value of the state object will continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateSizeAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
- * val size: Size by animateAsState(
+ * val size: Size by animateSizeAsState(
* if (selected) Size(20f, 20f) else Size(10f, 10f))
*
* @param targetValue Target value of the animation
@@ -185,14 +183,12 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateSizeAsState(
targetValue: Size,
- animationSpec: AnimationSpec<Size> = remember {
- spring(visibilityThreshold = Size.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<Size> = sizeDefaultSpring,
finishedListener: ((Size) -> Unit)? = null
): State<Size> {
- return animateAsState(
+ return animateValueAsState(
targetValue,
Size.VectorConverter,
animationSpec,
@@ -200,6 +196,22 @@
)
}
+private val sizeDefaultSpring = spring(visibilityThreshold = Size.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateSizeAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateSizeAsState"
+ )
+)
+@Composable
+fun animateAsState(
+ targetValue: Size,
+ animationSpec: AnimationSpec<Size> = sizeDefaultSpring,
+ finishedListener: ((Size) -> Unit)? = null
+): State<Size> = animateSizeAsState(targetValue, animationSpec, finishedListener)
+
/**
* Fire-and-forget animation function for [Bounds]. This Composable function is overloaded for
* different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
@@ -207,14 +219,15 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateBoundsAsState] returns a [State] object. The value of the state object will
+ * continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateBoundsAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
- * val bounds: Bounds by animateAsState(
+ * val bounds: Bounds by animateBoundsAsState(
* if (collapsed) Bounds(0.dp, 0.dp, 10.dp, 20.dp) else Bounds(0.dp, 0.dp, 100.dp, 200.dp))
*
* @param targetValue Target value of the animation
@@ -224,14 +237,12 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateBoundsAsState(
targetValue: Bounds,
- animationSpec: AnimationSpec<Bounds> = remember {
- spring(visibilityThreshold = Bounds.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<Bounds> = boundsDefaultSpring,
finishedListener: ((Bounds) -> Unit)? = null
): State<Bounds> {
- return animateAsState(
+ return animateValueAsState(
targetValue,
Bounds.VectorConverter,
animationSpec,
@@ -239,6 +250,22 @@
)
}
+private val boundsDefaultSpring = spring(visibilityThreshold = Bounds.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateBoundsAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateBoundsAsState"
+ )
+)
+@Composable
+fun animateAsState(
+ targetValue: Bounds,
+ animationSpec: AnimationSpec<Bounds> = boundsDefaultSpring,
+ finishedListener: ((Bounds) -> Unit)? = null
+): State<Bounds> = animateBoundsAsState(targetValue, animationSpec, finishedListener)
+
/**
* Fire-and-forget animation function for [Offset]. This Composable function is overloaded for
* different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Float],
@@ -246,10 +273,10 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
- * updated by the animation until the animation finishes.
+ * [animateOffsetAsState] returns a [State] object. The value of the state object will
+ * continuously be updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateOffsetAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
@@ -262,18 +289,32 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateOffsetAsState(
targetValue: Offset,
- animationSpec: AnimationSpec<Offset> = remember {
- spring(visibilityThreshold = Offset.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<Offset> = offsetDefaultSpring,
finishedListener: ((Offset) -> Unit)? = null
): State<Offset> {
- return animateAsState(
+ return animateValueAsState(
targetValue, Offset.VectorConverter, animationSpec, finishedListener = finishedListener
)
}
+private val offsetDefaultSpring = spring(visibilityThreshold = Offset.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateOffsetAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateOffsetAsState"
+ )
+)
+@Composable
+fun animateAsState(
+ targetValue: Offset,
+ animationSpec: AnimationSpec<Offset> = offsetDefaultSpring,
+ finishedListener: ((Offset) -> Unit)? = null
+): State<Offset> = animateOffsetAsState(targetValue, animationSpec, finishedListener)
+
/**
* Fire-and-forget animation function for [Rect]. This Composable function is overloaded for
* different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
@@ -281,14 +322,14 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateRectAsState] returns a [State] object. The value of the state object will continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateRectAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
- * val bounds: Rect by animateAsState(
+ * val bounds: Rect by animateRectAsState(
* if (enabled) Rect(0f, 0f, 100f, 100f) else Rect(8f, 8f, 80f, 80f))
*
* @param targetValue Target value of the animation
@@ -298,18 +339,32 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateRectAsState(
targetValue: Rect,
- animationSpec: AnimationSpec<Rect> = remember {
- spring(visibilityThreshold = Rect.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<Rect> = rectDefaultSpring,
finishedListener: ((Rect) -> Unit)? = null
): State<Rect> {
- return animateAsState(
+ return animateValueAsState(
targetValue, Rect.VectorConverter, animationSpec, finishedListener = finishedListener
)
}
+private val rectDefaultSpring = spring(visibilityThreshold = Rect.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateRectAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateRectAsState"
+ )
+)
+@Composable
+fun animateAsState(
+ targetValue: Rect,
+ animationSpec: AnimationSpec<Rect> = rectDefaultSpring,
+ finishedListener: ((Rect) -> Unit)? = null
+): State<Rect> = animateRectAsState(targetValue, animationSpec, finishedListener)
+
/**
* Fire-and-forget animation function for [Int]. This Composable function is overloaded for
* different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
@@ -317,10 +372,10 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateIntAsState] returns a [State] object. The value of the state object will continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateIntAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
@@ -331,16 +386,32 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateIntAsState(
targetValue: Int,
- animationSpec: AnimationSpec<Int> = remember { spring(visibilityThreshold = 1) },
+ animationSpec: AnimationSpec<Int> = intDefaultSpring,
finishedListener: ((Int) -> Unit)? = null
): State<Int> {
- return animateAsState(
+ return animateValueAsState(
targetValue, Int.VectorConverter, animationSpec, finishedListener = finishedListener
)
}
+private val intDefaultSpring = spring(visibilityThreshold = Int.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateIntAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateIntAsState"
+ )
+)
+@Composable
+fun animateAsState(
+ targetValue: Int,
+ animationSpec: AnimationSpec<Int> = intDefaultSpring,
+ finishedListener: ((Int) -> Unit)? = null
+): State<Int> = animateIntAsState(targetValue, animationSpec, finishedListener)
+
/**
* Fire-and-forget animation function for [IntOffset]. This Composable function is overloaded for
* different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
@@ -348,12 +419,12 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
- * updated by the animation until the animation finishes.
+ * [animateIntOffsetAsState] returns a [State] object. The value of the state object will
+ * continuously be updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
- * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
- * animations.
+ * Note, [animateIntOffsetAsState] cannot be canceled/stopped without removing this composable
+ * function from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for
+ * cancelable animations.
*
* @sample androidx.compose.animation.core.samples.AnimateOffsetSample
*
@@ -364,18 +435,32 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateIntOffsetAsState(
targetValue: IntOffset,
- animationSpec: AnimationSpec<IntOffset> = remember {
- spring(visibilityThreshold = IntOffset.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<IntOffset> = intOffsetDefaultSpring,
finishedListener: ((IntOffset) -> Unit)? = null
): State<IntOffset> {
- return animateAsState(
+ return animateValueAsState(
targetValue, IntOffset.VectorConverter, animationSpec, finishedListener = finishedListener
)
}
+private val intOffsetDefaultSpring = spring(visibilityThreshold = IntOffset.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateIntOffsetAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateIntOffsetAsState"
+ )
+)
+@Composable
+fun animateAsState(
+ targetValue: IntOffset,
+ animationSpec: AnimationSpec<IntOffset> = intOffsetDefaultSpring,
+ finishedListener: ((IntOffset) -> Unit)? = null
+): State<IntOffset> = animateIntOffsetAsState(targetValue, animationSpec, finishedListener)
+
/**
* Fire-and-forget animation function for [IntSize]. This Composable function is overloaded for
* different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
@@ -383,10 +468,10 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateIntSizeAsState] returns a [State] object. The value of the state object will continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateIntSizeAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
@@ -397,56 +482,31 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun animateAsState(
+fun animateIntSizeAsState(
targetValue: IntSize,
- animationSpec: AnimationSpec<IntSize> = remember {
- spring(visibilityThreshold = IntSize.VisibilityThreshold)
- },
+ animationSpec: AnimationSpec<IntSize> = intSizeDefaultSpring,
finishedListener: ((IntSize) -> Unit)? = null
): State<IntSize> {
- return animateAsState(
+ return animateValueAsState(
targetValue, IntSize.VectorConverter, animationSpec, finishedListener = finishedListener
)
}
-/**
- * Fire-and-forget animation function for [AnimationVector]. This Composable function is overloaded
- * for different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset]
- * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
- * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
- * course to animate towards the new target value.
- *
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
- * updated by the animation until the animation finishes.
- *
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
- * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
- * animations.
- *
- * @param targetValue Target value of the animation
- * @param animationSpec The animation that will be used to change the value through time. Physics
- * animation will be used by default.
- * @param visibilityThreshold An optional threshold to define when the animation value can be
- * considered close enough to the targetValue to end the animation.
- * @param finishedListener An optional end listener to get notified when the animation is finished.
- * @return A [State] object, the value of which is updated by animation.
- */
-@Composable
-fun <T : AnimationVector> animateAsState(
- targetValue: T,
- animationSpec: AnimationSpec<T> = remember {
- spring(visibilityThreshold = visibilityThreshold)
- },
- visibilityThreshold: T? = null,
- finishedListener: ((T) -> Unit)? = null
-): State<T> {
- return animateAsState(
- targetValue,
- remember { TwoWayConverter<T, T>({ it }, { it }) },
- animationSpec,
- finishedListener = finishedListener
+private val intSizeDefaultSpring = spring(visibilityThreshold = IntSize.VisibilityThreshold)
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateIntSizeAsState(targetValue, animationSpec, finishedListener)",
+ "androidx.compose.animation.core.animateIntSizeAsState"
)
-}
+)
+@Composable
+fun animateAsState(
+ targetValue: IntSize,
+ animationSpec: AnimationSpec<IntSize> = intSizeDefaultSpring,
+ finishedListener: ((IntSize) -> Unit)? = null
+): State<IntSize> = animateIntSizeAsState(targetValue, animationSpec, finishedListener)
/**
* Fire-and-forget animation function for any value. This Composable function is overloaded for
@@ -455,10 +515,10 @@
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * [animateValueAsState] returns a [State] object. The value of the state object will continuously be
* updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateValueAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
* animations.
*
@@ -475,7 +535,7 @@
* @return A [State] object, the value of which is updated by animation.
*/
@Composable
-fun <T, V : AnimationVector> animateAsState(
+fun <T, V : AnimationVector> animateValueAsState(
targetValue: T,
typeConverter: TwoWayConverter<T, V>,
animationSpec: AnimationSpec<T> = remember {
@@ -499,4 +559,25 @@
listener?.invoke(animationState.value)
}
return animationState
-}
\ No newline at end of file
+}
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith(
+ "animateValueAsState(targetValue, typeConverter, animationSpec, visibilityThreshold, " +
+ "finishedListener)",
+ "androidx.compose.animation.core.animateValueAsState"
+ )
+)
+@Composable
+fun <T, V : AnimationVector> animateAsState(
+ targetValue: T,
+ typeConverter: TwoWayConverter<T, V>,
+ animationSpec: AnimationSpec<T> = remember {
+ spring(visibilityThreshold = visibilityThreshold)
+ },
+ visibilityThreshold: T? = null,
+ finishedListener: ((T) -> Unit)? = null
+): State<T> = animateValueAsState(
+ targetValue, typeConverter, animationSpec, visibilityThreshold, finishedListener
+)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
index 200c127..021d350 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
@@ -114,3 +114,5 @@
*/
val Bounds.Companion.VisibilityThreshold: Bounds
get() = boundsVisibilityThreshold
+
+// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
\ No newline at end of file
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index f2827a8..2c04394 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -139,16 +139,15 @@
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
- method @Deprecated @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
- method @Deprecated @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateColorAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
}
public final class TransitionKt {
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index f2827a8..2c04394 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -139,16 +139,15 @@
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
- method @Deprecated @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
- method @Deprecated @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateColorAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
}
public final class TransitionKt {
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index f2827a8..2c04394 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -139,16 +139,15 @@
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
- method @Deprecated @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
- method @Deprecated @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
method @Deprecated @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateColorAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
}
public final class TransitionKt {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt
index e0aa04d..689c1c3 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt
@@ -52,7 +52,8 @@
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
- Modifier.align(Alignment.Center)
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.Center)
.graphicsLayer(
scaleX = 3.0f,
scaleY = 3.0f,
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteTransitionDemo.kt
index efc6be4..34e6bb1 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteTransitionDemo.kt
@@ -60,6 +60,7 @@
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
+ null,
Modifier.align(Alignment.Center)
.graphicsLayer(
scaleX = scale,
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
index 95ff665..bbcd355 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
@@ -18,7 +18,7 @@
import androidx.compose.animation.Animatable
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.background
@@ -38,7 +38,7 @@
@Composable
fun SingleValueAnimationDemo() {
val enabled = remember { mutableStateOf(true) }
- val alpha: Float by animateAsState(if (enabled.value) 1f else 0.5f)
+ val alpha: Float by animateFloatAsState(if (enabled.value) 1f else 0.5f)
val color = myAnimate(
if (enabled.value) Color.Green else Color.Magenta,
spring()
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
index 6f506e3..474b404 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
@@ -19,7 +19,7 @@
import androidx.annotation.Sampled
import androidx.compose.animation.Animatable
import androidx.compose.animation.animate
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.TwoWayConverter
@@ -126,7 +126,7 @@
// Animates to primary or secondary color, depending on whether [primary] is true
// [animateState] returns the current animation value in a State<Color> in this example. We
// use the State<Color> object as a property delegate here.
- val color: Color by animateAsState(
+ val color: Color by animateColorAsState(
if (primary) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
)
Box(modifier = Modifier.background(color))
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
index 7ed33cb..959558b 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
@@ -170,7 +170,11 @@
modifier = with(ColumnScope) { Modifier.align(Alignment.CenterHorizontally) }
) {
Row(Modifier.padding(start = 12.dp, end = 12.dp)) {
- Icon(Icons.Default.Favorite, Modifier.align(Alignment.CenterVertically))
+ Icon(
+ Icons.Default.Favorite,
+ contentDescription = "Favorite",
+ modifier = Modifier.align(Alignment.CenterVertically)
+ )
AnimatedVisibility(
expanded,
modifier = Modifier.align(Alignment.CenterVertically)
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
index 5de481d..5fb5489 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
@@ -29,7 +29,7 @@
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.VisibilityThreshold
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateValueAsState
import androidx.compose.animation.core.animateDecay
import androidx.compose.animation.core.animateTo
import androidx.compose.animation.core.isFinished
@@ -50,10 +50,8 @@
import androidx.compose.ui.platform.AmbientAnimationClock
import androidx.compose.ui.unit.Bounds
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.Position
private val defaultAnimation = spring<Float>()
@@ -75,10 +73,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateFloatAsState",
ReplaceWith(
- "animateAsState(target, animSpec, visibilityThreshold, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateFloatAsState(target, animSpec, visibilityThreshold, endListener).value",
+ "androidx.compose.animation.core.animateFloatAsState"
)
)
@Composable
@@ -127,16 +125,16 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateColorAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.animateAsState"
+ "animateColorAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.animateColorAsState"
)
)
@Composable
fun animate(
target: Color,
- animSpec: AnimationSpec<Color> = remember { spring() },
+ animSpec: AnimationSpec<Color> = colorDefaultSpring,
endListener: ((Color) -> Unit)? = null
): Color {
val converter = remember(target.colorSpace) { (Color.VectorConverter)(target.colorSpace) }
@@ -159,10 +157,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateDpAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateDpAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateDpAsState"
)
)
@Composable
@@ -177,42 +175,6 @@
}
/**
- * Fire-and-forget animation [Composable] for [Position]. Once such an animation is created, it will
- * be positionally memoized, like other @[Composable]s. To trigger the animation, or alter the
- * course of the animation, simply supply a different [target] to the [Composable].
- *
- * Note, [animateTo] is for simple animations that cannot be canceled. For cancellable animations
- * see [animatedValue].
- *
- * val position : Position = animate(
- * if (selected) Position(0.dp, 0.dp) else Position(20.dp, 20.dp))
- *
- * @param target Target value of the animation
- * @param animSpec The animation that will be used to change the value through time. Physics
- * animation will be used by default.
- * @param endListener An optional end listener to get notified when the animation is finished.
- */
-@Deprecated(
- "animate has been replaced with animateAsState",
- ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
- )
-)
-@Composable
-fun animate(
- target: Position,
- animSpec: AnimationSpec<Position> = remember {
- spring(visibilityThreshold = Position.VisibilityThreshold)
- },
- endListener: ((Position) -> Unit)? = null
-): Position {
- return animate(
- target, Position.VectorConverter, animSpec, endListener = endListener
- )
-}
-
-/**
* Fire-and-forget animation [Composable] for [Size]. Once such an animation is created, it will be
* positionally memoized, like other @[Composable]s. To trigger the animation, or alter the
* course of the animation, simply supply a different [target] to the [Composable].
@@ -229,10 +191,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateSizeAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateSizeAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateSizeAsState"
)
)
@Composable
@@ -263,10 +225,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateBoundsAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateBoundsAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateBoundsAsState"
)
)
@Composable
@@ -301,10 +263,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateOffsetAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateOffsetAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateOffsetAsState"
)
)
@Composable
@@ -337,10 +299,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateRectAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateRectAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateRectAsState"
)
)
@Composable
@@ -370,10 +332,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateIntAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateIntAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateIntAsState"
)
)
@Composable
@@ -403,10 +365,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateIntOffsetAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateIntOffsetAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateIntOffsetAsState"
)
)
@Composable
@@ -436,10 +398,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateIntSizeAsState",
ReplaceWith(
- "animateAsState(target, animSpec, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateIntSizeAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateIntSizeAsState"
)
)
@Composable
@@ -456,45 +418,6 @@
}
/**
- * Fire-and-forget animation [Composable] for [AnimationVector]. Once such an animation is created,
- * it will be positionally memoized, like other @[Composable]s. To trigger the animation, or alter
- * the course of the animation, simply supply a different [target] to the [Composable].
- *
- * Note, [animateTo] is for simple animations that cannot be canceled. For cancellable animations
- * see [animatedValue].
- *
- * @param target Target value of the animation
- * @param animSpec The animation that will be used to change the value through time. Physics
- * animation will be used by default.
- * @param visibilityThreshold An optional threshold to define when the animation value can be
- * considered close enough to the target to end the animation.
- * @param endListener An optional end listener to get notified when the animation is finished.
- */
-@Deprecated(
- "animate has been replaced with animateAsState",
- ReplaceWith(
- "animateAsState(target, animSpec, visibilityThreshold, endListener).value",
- "androidx.compose.animation.core.animateAsState"
- )
-)
-@Composable
-fun <T : AnimationVector> animate(
- target: T,
- animSpec: AnimationSpec<T> = remember {
- spring(visibilityThreshold = visibilityThreshold)
- },
- visibilityThreshold: T? = null,
- endListener: ((T) -> Unit)? = null
-): T {
- return animate(
- target,
- remember { TwoWayConverter<T, T>({ it }, { it }) },
- animSpec,
- endListener = endListener
- )
-}
-
-/**
* Fire-and-forget animation [Composable] for any value. Once such an animation is created, it
* will be positionally memoized, like other @[Composable]s. To trigger the animation, or alter
* the course of the animation, simply supply a different [target] to the [Composable].
@@ -514,10 +437,10 @@
* @param endListener An optional end listener to get notified when the animation is finished.
*/
@Deprecated(
- "animate has been replaced with animateAsState",
+ "animate has been replaced with animateValueAsState",
ReplaceWith(
- "animateAsState(target, converter, animSpec, visibilityThreshold, endListener).value",
- "androidx.compose.animation.core.animateAsState"
+ "animateValueAsState(target, converter, animSpec, visibilityThreshold, endListener).value",
+ "androidx.compose.animation.core.animateValueAsState"
)
)
@Composable
@@ -551,15 +474,15 @@
/**
* Fire-and-forget animation function for [Color]. This Composable function is overloaded for
- * different parameter types such as [Dp], [Float], [Int], [Size], [Offset], [DpOffset],
+ * different parameter types such as [Dp], [Float], [Int], [Size], [Offset],
* etc. When the provided [targetValue] is changed, the animation will run automatically. If there
* is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
* course to animate towards the new target value.
*
- * [animateAsState] returns a [State] object. The value of the state object will continuously be
- * updated by the animation until the animation finishes.
+ * [animateColorAsState] returns a [State] object. The value of the state object will
+ * continuously be updated by the animation until the animation finishes.
*
- * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * Note, [animateColorAsState] cannot be canceled/stopped without removing this composable function
* from the tree. See [animatedColor][androidx.compose.animation.animatedColor] for cancelable
* animations.
*
@@ -571,19 +494,32 @@
* @param finishedListener An optional end listener to get notified when the animation is finished.
*/
@Composable
-fun animateAsState(
+fun animateColorAsState(
targetValue: Color,
- animationSpec: AnimationSpec<Color> = remember { spring() },
+ animationSpec: AnimationSpec<Color> = colorDefaultSpring,
finishedListener: ((Color) -> Unit)? = null
): State<Color> {
val converter = remember(targetValue.colorSpace) {
(Color.VectorConverter)(targetValue.colorSpace)
}
- return animateAsState(
+ return animateValueAsState(
targetValue, converter, animationSpec, finishedListener = finishedListener
)
}
+private val colorDefaultSpring = spring<Color>()
+
+@Deprecated(
+ "animateAsState has been yet again renamed",
+ ReplaceWith("animateColorAsState(targetValue, animationSpec, finishedListener)")
+)
+@Composable
+fun animateAsState(
+ targetValue: Color,
+ animationSpec: AnimationSpec<Color> = colorDefaultSpring,
+ finishedListener: ((Color) -> Unit)? = null
+): State<Color> = animateColorAsState(targetValue, animationSpec, finishedListener)
+
/**
* This [Animatable] function creates a Color value holder that automatically
* animates its value when the value is changed via [animateTo]. [Animatable] supports value
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
index 1bcfea1..a56eef7 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
@@ -15,7 +15,7 @@
*/
package androidx.compose.desktop.examples.example1
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.TweenSpec
import androidx.compose.desktop.AppWindow
import androidx.compose.desktop.DesktopMaterialTheme
@@ -117,7 +117,7 @@
TopAppBar(
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(Icons.Outlined.Home)
+ Icon(Icons.Outlined.Home, "Home")
Text(title)
}
}
@@ -137,7 +137,7 @@
IconButton(
>
) {
- Icon(Icons.Filled.Menu, Modifier.size(ButtonDefaults.IconSize))
+ Icon(Icons.Filled.Menu, "Menu", Modifier.size(ButtonDefaults.IconSize))
}
}
},
@@ -397,11 +397,13 @@
Row {
Image(
imageResource("androidx/compose/desktop/example/circus.jpg"),
+ "Localized description",
Modifier.size(200.dp)
)
Icon(
vectorXmlResource("androidx/compose/desktop/example/ic_baseline_deck_24.xml"),
+ "Localized description",
Modifier.size(100.dp).align(Alignment.CenterVertically),
tint = Color.Blue.copy(alpha = 0.5f)
)
@@ -416,7 +418,7 @@
}
val enabled = remember { mutableStateOf(true) }
- val color by animateAsState(
+ val color by animateColorAsState(
if (enabled.value) Color.Green else Color.Red,
animationSpec = TweenSpec(durationMillis = 2000)
)
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
index 7a1d75e..256536c 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
@@ -45,11 +45,12 @@
/**
* Horizontally places the layout children.
*
- * @param totalSize Available space that can be occupied by the children.
- * @param sizes An array of sizes of all children.
+ * @param totalSize Available space that can be occupied by the children, in pixels.
+ * @param sizes An array of sizes of all children, in pixels.
* @param layoutDirection A layout direction, left-to-right or right-to-left, of the parent
* layout that should be taken into account when determining positions of the children.
- * @param outPositions An array of the size of [sizes] that returns the calculated positions.
+ * @param outPositions An array of the size of [sizes] that returns the calculated
+ * positions relative to the left, in pixels.
*/
fun Density.arrange(
totalSize: Int,
@@ -72,9 +73,10 @@
/**
* Vertically places the layout children.
*
- * @param totalSize Available space that can be occupied by the children.
- * @param sizes An array of sizes of all children.
- * @param outPositions An array of the size of [sizes] that returns the calculated positions.
+ * @param totalSize Available space that can be occupied by the children, in pixels.
+ * @param sizes An array of sizes of all children, in pixels.
+ * @param outPositions An array of the size of [sizes] that returns the calculated
+ * positions relative to the top, in pixels.
*/
fun Density.arrange(
totalSize: Int,
@@ -98,7 +100,8 @@
/**
* Place children horizontally such that they are as close as possible to the beginning of the
- * main axis.
+ * horizontal axis (left if the layout direction is LTR, right otherwise).
+ * Visually: 123#### for LTR and ####321.
*/
@Stable
val Start = object : Horizontal {
@@ -114,11 +117,14 @@
placeRightOrBottom(totalSize, sizes, outPositions)
outPositions.reverse()
}
+
+ override fun toString() = "Arrangement#Start"
}
/**
* Place children horizontally such that they are as close as possible to the end of the main
* axis.
+ * Visually: ####123 for LTR and 321#### for RTL.
*/
@Stable
val End = object : Horizontal {
@@ -134,11 +140,14 @@
placeLeftOrTop(sizes, outPositions)
outPositions.reverse()
}
+
+ override fun toString() = "Arrangement#End"
}
/**
* Place children vertically such that they are as close as possible to the top of the main
* axis.
+ * Visually: (top) 123#### (bottom)
*/
@Stable
val Top = object : Vertical {
@@ -147,11 +156,14 @@
sizes: IntArray,
outPositions: IntArray
) = placeLeftOrTop(sizes, outPositions)
+
+ override fun toString() = "Arrangement#Top"
}
/**
* Place children vertically such that they are as close as possible to the bottom of the main
* axis.
+ * Visually: (top) ####123 (bottom)
*/
@Stable
val Bottom = object : Vertical {
@@ -160,10 +172,13 @@
sizes: IntArray,
outPositions: IntArray
) = placeRightOrBottom(totalSize, sizes, outPositions)
+
+ override fun toString() = "Arrangement#Start"
}
/**
* Place children such that they are as close as possible to the middle of the main axis.
+ * Visually: ##123## for LTR and ##321## for RTL.
*/
@Stable
val Center = object : HorizontalOrVertical {
@@ -187,11 +202,14 @@
sizes: IntArray,
outPositions: IntArray
) = placeCenter(totalSize, sizes, outPositions)
+
+ override fun toString() = "Arrangement#Center"
}
/**
* Place children such that they are spaced evenly across the main axis, including free
* space before the first child and after the last child.
+ * Visually: #1#2#3# for LTR and #3#2#1# for RTL.
*/
@Stable
val SpaceEvenly = object : HorizontalOrVertical {
@@ -215,11 +233,14 @@
sizes: IntArray,
outPositions: IntArray
) = placeSpaceEvenly(totalSize, sizes, outPositions)
+
+ override fun toString() = "Arrangement#SpaceEvenly"
}
/**
* Place children such that they are spaced evenly across the main axis, without free
* space before the first child or after the last child.
+ * Visually: 1##2##3 for LTR or 3##2##1 for RTL.
*/
@Stable
val SpaceBetween = object : HorizontalOrVertical {
@@ -243,12 +264,15 @@
sizes: IntArray,
outPositions: IntArray
) = placeSpaceBetween(totalSize, sizes, outPositions)
+
+ override fun toString() = "Arrangement#SpaceBetween"
}
/**
* Place children such that they are spaced evenly across the main axis, including free
* space before the first child and after the last child, but half the amount of space
* existing otherwise between two consecutive children.
+ * Visually: #1##2##3# for LTR and #3##2##1# for RTL
*/
@Stable
val SpaceAround = object : HorizontalOrVertical {
@@ -272,6 +296,8 @@
sizes: IntArray,
outPositions: IntArray
) = placeSpaceAround(totalSize, sizes, outPositions)
+
+ override fun toString() = "Arrangement#SpaceAround"
}
/**
@@ -345,6 +371,8 @@
*
* Unlike [Arrangement.Start], when the layout direction is RTL, the children will not be
* mirrored and as such children will appear in the order they are composed inside the [Row].
+ *
+ * Visually: 123####
*/
@Stable
val Left = object : Horizontal {
@@ -354,6 +382,8 @@
layoutDirection: LayoutDirection,
outPositions: IntArray
) = placeLeftOrTop(sizes, outPositions)
+
+ override fun toString() = "AbsoluteArrangement#Left"
}
/**
@@ -361,6 +391,8 @@
*
* Unlike [Arrangement.Center], when the layout direction is RTL, the children will not be
* mirrored and as such children will appear in the order they are composed inside the [Row].
+ *
+ * Visually: ##123##
*/
@Stable
val Center = object : Horizontal {
@@ -370,6 +402,8 @@
layoutDirection: LayoutDirection,
outPositions: IntArray
) = placeCenter(totalSize, sizes, outPositions)
+
+ override fun toString() = "AbsoluteArrangement#Center"
}
/**
@@ -378,6 +412,8 @@
*
* Unlike [Arrangement.End], when the layout direction is RTL, the children will not be
* mirrored and as such children will appear in the order they are composed inside the [Row].
+ *
+ * Visually: ####123
*/
@Stable
val Right = object : Horizontal {
@@ -387,6 +423,8 @@
layoutDirection: LayoutDirection,
outPositions: IntArray
) = placeRightOrBottom(totalSize, sizes, outPositions)
+
+ override fun toString() = "AbsoluteArrangement#Right"
}
/**
@@ -395,6 +433,8 @@
*
* Unlike [Arrangement.SpaceBetween], when the layout direction is RTL, the children will not be
* mirrored and as such children will appear in the order they are composed inside the [Row].
+ *
+ * Visually: 1##2##3
*/
@Stable
val SpaceBetween = object : Horizontal {
@@ -404,6 +444,8 @@
layoutDirection: LayoutDirection,
outPositions: IntArray
) = placeSpaceBetween(totalSize, sizes, outPositions)
+
+ override fun toString() = "AbsoluteArrangement#SpaceBetween"
}
/**
@@ -412,6 +454,8 @@
*
* Unlike [Arrangement.SpaceEvenly], when the layout direction is RTL, the children will not be
* mirrored and as such children will appear in the order they are composed inside the [Row].
+ *
+ * Visually: #1#2#3#
*/
@Stable
val SpaceEvenly = object : Horizontal {
@@ -421,6 +465,8 @@
layoutDirection: LayoutDirection,
outPositions: IntArray
) = placeSpaceEvenly(totalSize, sizes, outPositions)
+
+ override fun toString() = "AbsoluteArrangement#SpaceEvenly"
}
/**
@@ -430,6 +476,8 @@
*
* Unlike [Arrangement.SpaceAround], when the layout direction is RTL, the children will not be
* mirrored and as such children will appear in the order they are composed inside the [Row].
+ *
+ * Visually: #1##2##3##4#
*/
@Stable
val SpaceAround = object : Horizontal {
@@ -439,6 +487,8 @@
layoutDirection: LayoutDirection,
outPositions: IntArray
) = placeSpaceAround(totalSize, sizes, outPositions)
+
+ override fun toString() = "AbsoluteArrangement#SpaceAround"
}
/**
@@ -546,11 +596,15 @@
if (layoutDirection == LayoutDirection.Rtl && rtlMirror) outPositions.reverse()
}
+
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray
) = arrange(totalSize, sizes, LayoutDirection.Ltr, outPositions)
+
+ override fun toString() =
+ "${if (rtlMirror) "" else "Absolute"}Arrangement#spacedAligned($space, $alignment)"
}
internal fun placeRightOrBottom(
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
index 78f6cec..7a161de 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
@@ -40,7 +40,7 @@
* Padding is applied before content measurement and takes precedence; content may only be as large
* as the remaining space.
*
- * Negative padding is not permitted. See [offset].
+ * Negative padding is not permitted. See [Modifier.offset].
*
* Example usage:
* @sample androidx.compose.foundation.layout.samples.PaddingModifier
@@ -74,7 +74,7 @@
* Padding is applied before content measurement and takes precedence; content may only be as large
* as the remaining space.
*
- * Negative padding is not permitted. See [offset].
+ * Negative padding is not permitted. See [Modifier.offset].
*
* Example usage:
* @sample androidx.compose.foundation.layout.samples.SymmetricPaddingModifier
@@ -103,7 +103,7 @@
* Padding is applied before content measurement and takes precedence; content may only be as large
* as the remaining space.
*
- * Negative padding is not permitted. See [offset].
+ * Negative padding is not permitted. See [Modifier.offset].
*
* Example usage:
* @sample androidx.compose.foundation.layout.samples.PaddingAllModifier
@@ -129,7 +129,7 @@
* top, right and bottom. Padding is applied before content measurement and takes precedence;
* content may only be as large as the remaining space.
*
- * Negative padding is not permitted. See [offset].
+ * Negative padding is not permitted. See [Modifier.offset].
*
* Example usage:
* @sample androidx.compose.foundation.layout.samples.PaddingValuesModifier
@@ -158,7 +158,7 @@
* [padding] to apply relative paddings. Padding is applied before content measurement and takes
* precedence; content may only be as large as the remaining space.
*
- * Negative padding is not permitted. See [offset].
+ * Negative padding is not permitted. See [Modifier.offset].
*
* Example usage:
* @sample androidx.compose.foundation.layout.samples.AbsolutePaddingModifier
@@ -258,5 +258,14 @@
@Stable
val bottom: Dp = 0.dp
) {
+ /**
+ * Describes a padding of [all] dp along all 4 edges.
+ */
constructor(all: Dp) : this(all, all, all, all)
+
+ /**
+ * Describes a padding of [horizontal] dp along the left and right edges, and of [vertical]
+ * dp along the top and bottom edges.
+ */
+ constructor(horizontal: Dp, vertical: Dp) : this(horizontal, vertical, horizontal, vertical)
}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index b0c049a..e2ea5c1 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -66,9 +66,9 @@
}
public final class ImageKt {
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
- method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
}
@androidx.compose.runtime.Stable public interface Indication {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index b0c049a..e2ea5c1 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -66,9 +66,9 @@
}
public final class ImageKt {
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
- method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
}
@androidx.compose.runtime.Stable public interface Indication {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index b0c049a..e2ea5c1 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -66,9 +66,9 @@
}
public final class ImageKt {
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
- method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
}
@androidx.compose.runtime.Stable public interface Indication {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
index db44c17..d1cc274 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
@@ -101,7 +101,7 @@
.background(Color.LightGray, RoundedCornerShape(percent = 30))
.padding(16.dp)
) {
- Icon(Icons.Default.MailOutline)
+ Icon(Icons.Default.MailOutline, contentDescription = null)
Spacer(Modifier.width(16.dp))
innerTextField()
}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt
index 904f536..fd4f49f 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt
@@ -43,7 +43,7 @@
fun ImageSample() {
val ImageBitmap = createTestImage()
// Lays out and draws an image sized to the dimensions of the ImageBitmap
- Image(bitmap = ImageBitmap)
+ Image(bitmap = ImageBitmap, contentDescription = "Localized description")
}
@Sampled
@@ -56,7 +56,8 @@
ImageBitmap,
IntOffset(10, 12),
IntSize(50, 60)
- )
+ ),
+ contentDescription = "Localized description"
)
}
@@ -67,6 +68,7 @@
imageVector.resource.resource?.let {
Image(
imageVector = it,
+ contentDescription = null,
modifier = Modifier.preferredSize(200.dp, 200.dp),
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(Color.Cyan)
@@ -89,7 +91,11 @@
}
}
- Image(painter = customPainter, modifier = Modifier.preferredSize(100.dp, 100.dp))
+ Image(
+ painter = customPainter,
+ contentDescription = "Localized description",
+ modifier = Modifier.preferredSize(100.dp, 100.dp)
+ )
}
/**
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
index b49f36d..1e6f611 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
@@ -56,6 +56,7 @@
import androidx.compose.ui.platform.AmbientDensity
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -113,7 +114,11 @@
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
- Image(modifier = Modifier.testTag(contentTag), bitmap = createImageBitmap())
+ Image(
+ modifier = Modifier.testTag(contentTag),
+ contentDescription = null,
+ bitmap = createImageBitmap()
+ )
}
}
@@ -162,7 +167,8 @@
imageHeight / 2 - subsectionHeight / 2
),
IntSize(subsectionWidth, subsectionHeight)
- )
+ ),
+ null
)
}
}
@@ -258,6 +264,7 @@
// the bounds
Image(
bitmap = createImageBitmap(),
+ contentDescription = null,
modifier = Modifier
.testTag(contentTag)
.preferredSize(
@@ -325,6 +332,7 @@
) {
Image(
bitmap = ImageBitmap,
+ contentDescription = null,
modifier = Modifier
.testTag(contentTag)
.preferredSize(
@@ -357,6 +365,7 @@
// ImageBitmap that is to be drawn in the bottom end section of the composable
Image(
bitmap = createImageBitmap(),
+ contentDescription = null,
modifier = Modifier
.testTag(contentTag)
.preferredSize(
@@ -420,6 +429,7 @@
loadVectorResource(R.drawable.ic_vector_asset_test).resource.resource?.let {
Image(
it,
+ null,
modifier = Modifier.preferredSizeIn(
minWidth = minWidth,
minHeight = minHeight
@@ -505,6 +515,7 @@
val heightDp = asset.height / AmbientDensity.current.density
Image(
asset,
+ null,
modifier = Modifier
.testTag(testTag)
.background(Color.Green)
@@ -540,14 +551,15 @@
}
Image(
painterResource(painterId.value),
- contentScale = ContentScale.FillBounds,
+ null,
modifier = Modifier.testTag(testTag).clickable {
if (painterId.value == R.drawable.ic_vector_square_asset_test) {
painterId.value = R.drawable.ic_image_test
} else {
painterId.value = R.drawable.ic_vector_square_asset_test
}
- }
+ },
+ contentScale = ContentScale.FillBounds
)
}
@@ -559,4 +571,20 @@
rule.onNodeWithTag(testTag).captureToImage().assertPixels { imageColor }
}
+
+ @Test
+ fun testImageContentDescription() {
+ val testTag = "TestTag"
+ rule.setContent {
+ Image(
+ bitmap = ImageBitmap(100, 100),
+ modifier = Modifier.testTag(testTag),
+ contentDescription = "asdf"
+ )
+ }
+ rule.onNodeWithTag(testTag).fetchSemanticsNode().let {
+ Assert.assertTrue(it.config.contains(SemanticsProperties.ContentDescription))
+ Assert.assertEquals(it.config[SemanticsProperties.ContentDescription], "asdf")
+ }
+ }
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
index 32e24c0..72f9598 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
@@ -33,6 +33,8 @@
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
/**
* A composable that lays out and draws a given [ImageBitmap]. This will attempt to
@@ -49,13 +51,17 @@
* overload that consumes a [Painter] parameter shown in this sample
* @sample androidx.compose.foundation.samples.ImagePainterSubsectionSample
*
- * @param bitmap The [ImageBitmap] to draw.
+ * @param bitmap The [ImageBitmap] to draw
+ * @param contentDescription text used by accessibility services to describe what this image
+ * represents. This should always be provided unless this image is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
* @param modifier Modifier used to adjust the layout algorithm or draw decoration content (ex.
* background)
* @param alignment Optional alignment parameter used to place the [ImageBitmap] in the given
- * bounds defined by the width and height.
+ * bounds defined by the width and height
* @param contentScale Optional scale parameter used to determine the aspect ratio scaling to be used
- * if the bounds are a different size from the intrinsic size of the [ImageBitmap].
+ * if the bounds are a different size from the intrinsic size of the [ImageBitmap]
* @param alpha Optional opacity to be applied to the [ImageBitmap] when it is rendered onscreen
* @param colorFilter Optional ColorFilter to apply for the [ImageBitmap] when it is rendered
* onscreen
@@ -64,6 +70,7 @@
@Composable
inline fun Image(
bitmap: ImageBitmap,
+ contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
@@ -73,6 +80,7 @@
val imagePainter = remember(bitmap) { ImagePainter(bitmap) }
Image(
painter = imagePainter,
+ contentDescription = contentDescription,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
@@ -90,13 +98,17 @@
*
* @sample androidx.compose.foundation.samples.ImageVectorSample
*
- * @param imageVector The [ImageVector] to draw.
+ * @param imageVector The [ImageVector] to draw
+ * @param contentDescription text used by accessibility services to describe what this image
+ * represents. This should always be provided unless this image is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
* @param modifier Modifier used to adjust the layout algorithm or draw decoration content (ex.
* background)
* @param alignment Optional alignment parameter used to place the [ImageVector] in the given
- * bounds defined by the width and height.
+ * bounds defined by the width and height
* @param contentScale Optional scale parameter used to determine the aspect ratio scaling to be used
- * if the bounds are a different size from the intrinsic size of the [ImageVector].
+ * if the bounds are a different size from the intrinsic size of the [ImageVector]
* @param alpha Optional opacity to be applied to the [ImageVector] when it is rendered onscreen
* @param colorFilter Optional ColorFilter to apply for the [ImageVector] when it is rendered
* onscreen
@@ -105,6 +117,7 @@
@Composable
inline fun Image(
imageVector: ImageVector,
+ contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
@@ -112,6 +125,7 @@
colorFilter: ColorFilter? = null
) = Image(
painter = rememberVectorPainter(imageVector),
+ contentDescription = contentDescription,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
@@ -132,12 +146,16 @@
* @sample androidx.compose.foundation.samples.ImagePainterSample
*
* @param painter to draw
+ * @param contentDescription text used by accessibility services to describe what this image
+ * represents. This should always be provided unless this image is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
* @param modifier Modifier used to adjust the layout algorithm or draw decoration content (ex.
* background)
* @param alignment Optional alignment parameter used to place the [Painter] in the given
* bounds defined by the width and height.
* @param contentScale Optional scale parameter used to determine the aspect ratio scaling to be used
- * if the bounds are a different size from the intrinsic size of the [Painter].
+ * if the bounds are a different size from the intrinsic size of the [Painter]
* @param alpha Optional opacity to be applied to the [Painter] when it is rendered onscreen
* the default renders the [Painter] completely opaque
* @param colorFilter Optional colorFilter to apply for the [Painter] when it is rendered onscreen
@@ -145,17 +163,24 @@
@Composable
fun Image(
painter: Painter,
+ contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null
) {
+ val semantics = if (contentDescription != null) {
+ Modifier.semantics { this.contentDescription = contentDescription }
+ } else {
+ Modifier
+ }
+
// Explicitly use a simple Layout implementation here as Spacer squashes any non fixed
// constraint with zero
Layout(
emptyContent(),
- modifier.clipToBounds().paint(
+ modifier.then(semantics).clipToBounds().paint(
painter,
alignment = alignment,
contentScale = contentScale,
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
index 9e063c6..0cb8885 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
@@ -16,7 +16,7 @@
package androidx.compose.foundation
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.lazy.LazyListState
@@ -217,7 +217,7 @@
}
}
- val color by animateAsState(
+ val color by animateColorAsState(
if (isHover) style.hoverColor else style.unhoverColor,
animationSpec = TweenSpec(durationMillis = style.hoverDurationMillis)
)
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
index 3c14b88..f84c765 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
@@ -182,21 +182,21 @@
LayoutDirection.Rtl -> Icons.Filled.ArrowForward
}
IconButton( {
- Icon(icon)
+ Icon(icon, null)
}
}
@Composable
fun Filter(onClick: () -> Unit) {
IconButton(modifier = Modifier.testTag(Tags.FilterButton), {
- Icon(Icons.Filled.Search)
+ Icon(Icons.Filled.Search, null)
}
}
@Composable
fun Settings(onClick: () -> Unit) {
IconButton( {
- Icon(Icons.Filled.Settings)
+ Icon(Icons.Filled.Settings, null)
}
}
}
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
index 47e8e9c..b01334f 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
@@ -88,7 +88,7 @@
}
TopAppBar(backgroundColor = appBarColor, contentColor = onSurface) {
IconButton(modifier = Modifier.align(Alignment.CenterVertically), {
- Icon(Icons.Filled.Close)
+ Icon(Icons.Filled.Close, null)
}
FilterField(
filterText,
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
index 6e2bb73..656fb76 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
@@ -210,6 +210,7 @@
Icon(
imageVector = vectorResource(R.drawable.ic_plane),
+ contentDescription = stringResource(R.string.plane_description),
tint = colorResource(R.color.Blue700)
)
}
@@ -421,15 +422,16 @@
}
object string {
const val ok = 4
+ const val plane_description = 5
}
object dimen {
- const val padding_small = 5
+ const val padding_small = 6
}
object drawable {
- const val ic_plane = 6
+ const val ic_plane = 7
}
object color {
- const val Blue700 = 7
+ const val Blue700 = 8
}
}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
index f11321f..18ac649 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
@@ -56,11 +56,13 @@
* No action required if it's modified.
*/
-@Composable private fun NavigationSnippet1() {
+@Composable
+private fun NavigationSnippet1() {
val navController = rememberNavController()
}
-@Composable private fun NavigationSnippet2(navController: NavHostController) {
+@Composable
+private fun NavigationSnippet2(navController: NavHostController) {
NavHost(navController = navController, startDestination = "profile") {
composable("profile") { Profile(/*...*/) }
composable("friendslist") { FriendsList(/*...*/) }
@@ -69,7 +71,8 @@
}
private object NavigationSnippet3 {
- @Composable fun Profile(navController: NavController) {
+ @Composable
+ fun Profile(navController: NavController) {
/*...*/
Button( navController.navigate("friends") }) {
Text(text = "Navigate next")
@@ -78,14 +81,16 @@
}
}
-@Composable private fun NavigationSnippet4(navController: NavHostController) {
+@Composable
+private fun NavigationSnippet4(navController: NavHostController) {
NavHost(navController = navController, startDestination = "profile/{userId}") {
/*...*/
composable("profile/{userId}") { /*...*/ }
}
}
-@Composable private fun NavigationSnippet5(navController: NavHostController) {
+@Composable
+private fun NavigationSnippet5(navController: NavHostController) {
NavHost(navController = navController, startDestination = "profile/{userId}") {
/*...*/
composable(
@@ -95,17 +100,20 @@
}
}
-@Composable private fun NavGraphBuilder.NavigationSnippet6(navController: NavHostController) {
+@Composable
+private fun NavGraphBuilder.NavigationSnippet6(navController: NavHostController) {
composable("profile/{userId}") { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
}
-@Composable private fun NavigationSnippet7(navController: NavHostController) {
+@Composable
+private fun NavigationSnippet7(navController: NavHostController) {
navController.navigate("profile/user1234")
}
-@Composable private fun NavGraphBuilder.NavigationSnippet8(navController: NavHostController) {
+@Composable
+private fun NavGraphBuilder.NavigationSnippet8(navController: NavHostController) {
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "me" })
@@ -116,7 +124,8 @@
/* Deep links */
-@Composable private fun NavGraphBuilder.NavigationSnippet9(navController: NavHostController) {
+@Composable
+private fun NavGraphBuilder.NavigationSnippet9(navController: NavHostController) {
val uri = "https://example.com"
composable(
@@ -127,7 +136,8 @@
}
}
-@Composable private fun NavigationSnippet10() {
+@Composable
+private fun NavigationSnippet10() {
val id = "exampleId"
val context = AmbientContext.current
val deepLinkIntent = Intent(
@@ -143,7 +153,8 @@
}
}
-@Composable private fun NavigationSnippet11(items: List<Screen>) {
+@Composable
+private fun NavigationSnippet11(items: List<Screen>) {
val navController = rememberNavController()
Scaffold(
bottomBar = {
@@ -152,7 +163,7 @@
val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
items.forEach { screen ->
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(stringResource(screen.resourceId)) },
selected = currentRoute == screen.route,
>
@@ -178,7 +189,8 @@
}
}
-@Composable private fun NavGraphBuilder.NavigationSnippet12(navController: NavHostController) {
+@Composable
+private fun NavGraphBuilder.NavigationSnippet12(navController: NavHostController) {
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "me" })
@@ -200,12 +212,31 @@
}
}
-@Composable private fun Profile() { }
-@Composable private fun Profile(userId: String?, content: @Composable (String) -> Unit) { }
-@Composable private fun Profile(navController: NavHostController) { }
-@Composable private fun FriendsList() { }
-@Composable private fun FriendsList(navController: NavHostController) { }
-@Composable private fun Profile(navController: NavHostController, arg: String?) { TODO() }
+@Composable
+private fun Profile() {
+}
+
+@Composable
+private fun Profile(userId: String?, content: @Composable (String) -> Unit) {
+}
+
+@Composable
+private fun Profile(navController: NavHostController) {
+}
+
+@Composable
+private fun FriendsList() {
+}
+
+@Composable
+private fun FriendsList(navController: NavHostController) {
+}
+
+@Composable
+private fun Profile(navController: NavHostController, arg: String?) {
+ TODO()
+}
+
private class MyActivity
private sealed class Screen(val route: String, @StringRes val resourceId: Int) {
object Profile : Screen("profile", R.string.profile)
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt
index dd6f636..3d70e48 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt
@@ -47,6 +47,7 @@
import androidx.compose.runtime.savedinstancestate.savedInstanceState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.viewModel
import androidx.lifecycle.LiveData
@@ -235,12 +236,18 @@
Text(text = body, Modifier.padding(top = 8.dp))
// change expanded in response to click events
IconButton( expanded = false }, modifier = Modifier.fillMaxWidth()) {
- Icon(Icons.Default.ExpandLess)
+ Icon(
+ Icons.Default.ExpandLess,
+ contentDescription = stringResource(R.string.expand_less)
+ )
}
} else {
// change expanded in response to click events
IconButton( expanded = true }, modifier = Modifier.fillMaxWidth()) {
- Icon(Icons.Default.ExpandMore)
+ Icon(
+ Icons.Default.ExpandMore,
+ contentDescription = stringResource(R.string.expand_more)
+ )
}
}
}
@@ -284,12 +291,18 @@
if (expanded) {
Spacer(Modifier.height(8.dp))
Text(body)
- IconButton( Modifier.fillMaxWidth()) {
- Icon(Icons.Default.ExpandLess)
+ IconButton( modifier = Modifier.fillMaxWidth()) {
+ Icon(
+ Icons.Default.ExpandLess,
+ contentDescription = stringResource(R.string.expand_less)
+ )
}
} else {
- IconButton( Modifier.fillMaxWidth()) {
- Icon(Icons.Default.ExpandMore)
+ IconButton( modifier = Modifier.fillMaxWidth()) {
+ Icon(
+ Icons.Default.ExpandMore,
+ contentDescription = stringResource(R.string.expand_more)
+ )
}
}
}
@@ -325,6 +338,13 @@
}
}
+private object R {
+ object string {
+ const val expand_less = 0
+ const val expand_more = 1
+ }
+}
+
private const val it = 1
private lateinit var helloViewModel: StateSnippet2.HelloViewModel
private fun computeTextFormatting(st: String): String = ""
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt
index 18a6b75..8cf64b3 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt
@@ -159,7 +159,7 @@
Column(
modifier = Modifier.padding(16.dp)
) {
- Image(image)
+ Image(image, contentDescription = null)
Text("A day in Shark Fin Cove")
Text("Davenport, California")
@@ -179,9 +179,12 @@
.preferredHeight(180.dp)
.fillMaxWidth()
- Image(image,
+ Image(
+ image,
+ contentDescription = null,
modifier = imageModifier,
- contentScale = ContentScale.Crop)
+ contentScale = ContentScale.Crop
+ )
Text("A day in Shark Fin Cove")
Text("Davenport, California")
@@ -205,9 +208,12 @@
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp))
- Image(image,
+ Image(
+ image,
+ contentDescription = null,
modifier = imageModifier,
- contentScale = ContentScale.Crop)
+ contentScale = ContentScale.Crop
+ )
Spacer(Modifier.preferredHeight(16.dp))
Text("A day in Shark Fin Cove")
@@ -230,9 +236,12 @@
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp))
- Image(image,
+ Image(
+ image,
+ contentDescription = null,
modifier = imageModifier,
- contentScale = ContentScale.Crop)
+ contentScale = ContentScale.Crop
+ )
Spacer(Modifier.preferredHeight(16.dp))
Text("A day in Shark Fin Cove")
@@ -257,9 +266,12 @@
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp))
- Image(image,
+ Image(
+ image,
+ contentDescription = null,
modifier = imageModifier,
- contentScale = ContentScale.Crop)
+ contentScale = ContentScale.Crop
+ )
Spacer(Modifier.preferredHeight(16.dp))
Text("A day in Shark Fin Cove",
@@ -287,9 +299,12 @@
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp))
- Image(image,
+ Image(
+ image,
+ contentDescription = null,
modifier = imageModifier,
- contentScale = ContentScale.Crop)
+ contentScale = ContentScale.Crop
+ )
Spacer(Modifier.preferredHeight(16.dp))
Text(
@@ -321,9 +336,11 @@
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp))
- Image(image,
+ Image(
+ image, null,
modifier = imageModifier,
- contentScale = ContentScale.Crop)
+ contentScale = ContentScale.Crop
+ )
Spacer(Modifier.preferredHeight(16.dp))
Text(
diff --git a/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/ImageVectorTest.kt b/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/ImageVectorTest.kt
index 102d854..3e0c8b2 100644
--- a/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/ImageVectorTest.kt
+++ b/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/ImageVectorTest.kt
@@ -96,7 +96,7 @@
}
val imageVector =
vectorResource(R.drawable.ic_pathfill_sample)
- Image(imageVector, modifier = Modifier.testTag(testTag))
+ Image(imageVector, null, modifier = Modifier.testTag(testTag))
}
rule.onNodeWithTag(testTag).captureToImage().asAndroidBitmap().apply {
diff --git a/compose/material/material-icons-core/samples/src/main/java/androidx/compose/material/icons/samples/IconSamples.kt b/compose/material/material-icons-core/samples/src/main/java/androidx/compose/material/icons/samples/IconSamples.kt
index 24bd565..36b37ac 100644
--- a/compose/material/material-icons-core/samples/src/main/java/androidx/compose/material/icons/samples/IconSamples.kt
+++ b/compose/material/material-icons-core/samples/src/main/java/androidx/compose/material/icons/samples/IconSamples.kt
@@ -38,7 +38,7 @@
@Sampled
@Composable
fun DrawIcon() {
- Icon(Icons.Rounded.Menu)
+ Icon(Icons.Rounded.Menu, contentDescription = "Localized description")
}
@Composable
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 14adb40..68ce800 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -364,9 +364,9 @@
}
public final class IconKt {
- method @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-tu7MLKI(androidx.compose.ui.graphics.vector.ImageVector imageVector, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-8NTYWNk(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-BG621w0(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-hGAziDE(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ListItemKt {
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index 14adb40..68ce800 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -364,9 +364,9 @@
}
public final class IconKt {
- method @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-tu7MLKI(androidx.compose.ui.graphics.vector.ImageVector imageVector, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-8NTYWNk(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-BG621w0(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-hGAziDE(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ListItemKt {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 14adb40..68ce800 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -364,9 +364,9 @@
}
public final class IconKt {
- method @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-tu7MLKI(androidx.compose.ui.graphics.vector.ImageVector imageVector, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-8NTYWNk(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-BG621w0(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon-hGAziDE(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ListItemKt {
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
index 170084f..e07166c 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
@@ -16,7 +16,7 @@
package androidx.compose.material.demos
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -170,8 +170,8 @@
var checked by remember { mutableStateOf(false) }
IconToggleButton(checked = checked, enabled = false, checked = it }) {
- val tint by animateAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
- Icon(Icons.Filled.Favorite, tint = tint)
+ val tint by animateColorAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
+ Icon(Icons.Filled.Favorite, contentDescription = "Favorite", tint = tint)
}
}
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
index babd9ed..8c7a242 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
@@ -115,7 +115,7 @@
)
Box(Modifier.fillMaxSize()) {
- Image(modifier = inputModifier, bitmap = colorWheel.image)
+ Image(modifier = inputModifier, contentDescription = null, bitmap = colorWheel.image)
val color = colorWheel.colorForPosition(position)
if (color.isSpecified) {
Magnifier(visible = isDragging, position = position, color = color)
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
index b2981c7..dc48864 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
@@ -174,8 +174,12 @@
"Label" + if (selectedOption == Option.Error) "*" else ""
Text(text = label)
},
- leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
- trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+ leadingIcon = {
+ if (leadingChecked) Icon(Icons.Filled.Favorite, "Favorite")
+ },
+ trailingIcon = {
+ if (trailingChecked) Icon(Icons.Filled.Info, "Info")
+ },
isErrorValue = selectedOption == Option.Error,
modifier = Modifier.width(300.dp)
)
@@ -191,8 +195,12 @@
"Label" + if (selectedOption == Option.Error) "*" else ""
Text(text = label)
},
- leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
- trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+ leadingIcon = {
+ if (leadingChecked) Icon(Icons.Filled.Favorite, "Favorite")
+ },
+ trailingIcon = {
+ if (trailingChecked) Icon(Icons.Filled.Info, "Info")
+ },
isErrorValue = selectedOption == Option.Error,
modifier = Modifier.width(300.dp)
)
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
index 009a3b3e..eee99c4 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
@@ -78,7 +78,7 @@
val iconButton = @Composable {
IconButton( expanded = true }) {
- Icon(Icons.Default.MoreVert)
+ Icon(Icons.Default.MoreVert, null)
}
}
DropdownMenu(
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt
index a2ee1ad..423fcc4 100644
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt
+++ b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt
@@ -100,8 +100,9 @@
Spacer(Modifier.preferredWidth(16.dp))
Icon(
Icons.Filled.ArrowForwardIos,
- tint = Color.White.copy(alpha = 0.6f),
+ contentDescription = null,
modifier = Modifier.preferredSize(12.dp).align(Alignment.CenterVertically),
+ tint = Color.White.copy(alpha = 0.6f)
)
}
RallyDivider()
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt
index 9827abd..8773f93 100644
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt
+++ b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt
@@ -33,6 +33,7 @@
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
+import androidx.compose.material.studies.R
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -40,6 +41,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import java.util.Locale
@@ -126,7 +128,7 @@
>
modifier = Modifier.align(Alignment.Top)
) {
- Icon(Icons.Filled.Sort)
+ Icon(Icons.Filled.Sort, contentDescription = stringResource(R.string.sort))
}
}
}
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt
index 044c093..ae6e831 100644
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt
+++ b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt
@@ -17,14 +17,26 @@
package androidx.compose.material.studies.rally
import androidx.compose.material.icons.Icons
+import androidx.compose.material.studies.R
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
enum class RallyScreenState(
- val icon: ImageVector,
+ val icon: ScreenIcon,
val body: @Composable () -> Unit
) {
- Overview(Icons.Filled.PieChart, { OverviewBody() }),
- Accounts(Icons.Filled.AttachMoney, { AccountsBody(UserData.accounts) }),
- Bills(Icons.Filled.MoneyOff, { BillsBody(UserData.bills) })
-}
\ No newline at end of file
+ Overview(
+ ScreenIcon(Icons.Filled.PieChart, contentDescription = R.string.overview),
+ @Composable { OverviewBody() }
+ ),
+ Accounts(
+ ScreenIcon(Icons.Filled.AttachMoney, contentDescription = R.string.account),
+ @Composable { AccountsBody(UserData.accounts) }
+ ),
+ Bills(
+ ScreenIcon(Icons.Filled.MoneyOff, contentDescription = R.string.bills),
+ @Composable { BillsBody(UserData.bills) }
+ )
+}
+
+class ScreenIcon(val icon: ImageVector, val contentDescription: Int)
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt
index 843d7d8..b49e23f 100644
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt
+++ b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt
@@ -38,7 +38,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import java.util.Locale
@@ -65,7 +65,7 @@
@Composable
private fun RallyTab(
text: String,
- icon: ImageVector,
+ icon: ScreenIcon,
onSelected: () -> Unit,
selected: Boolean
) {
@@ -81,7 +81,11 @@
indication = rememberRipple(bounded = false)
)
) {
- Icon(icon, tint = tabTintColor)
+ Icon(
+ imageVector = icon.icon,
+ contentDescription = stringResource(icon.contentDescription),
+ tint = tabTintColor
+ )
if (selected) {
Spacer(Modifier.preferredWidth(12.dp))
Text(text, color = tabTintColor)
diff --git a/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml b/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7df5dd7b
--- /dev/null
+++ b/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2021 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.
+ -->
+
+<resources>
+ <string name="sort">Sort</string>
+ <string name="overview">Account overview</string>
+ <string name="account">Accounts</string>
+ <string name="bills">Bills</string>
+</resources>
\ No newline at end of file
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/AppBarSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/AppBarSamples.kt
index 1019531..2d17900 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/AppBarSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/AppBarSamples.kt
@@ -36,16 +36,16 @@
title = { Text("Simple TopAppBar") },
navigationIcon = {
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Menu)
+ Icon(Icons.Filled.Menu, contentDescription = null)
}
},
actions = {
// RowScope here, so these icons will be placed horizontally
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
}
)
@@ -56,15 +56,15 @@
fun SimpleBottomAppBar() {
BottomAppBar {
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Menu)
+ Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
// The actions should be at the end of the BottomAppBar
Spacer(Modifier.weight(1f, true))
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
index 368287d..6895b34 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
@@ -58,11 +58,11 @@
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton( scaffoldState.reveal() }) {
- Icon(Icons.Default.Menu)
+ Icon(Icons.Default.Menu, contentDescription = "Localized description")
}
} else {
IconButton( scaffoldState.conceal() }) {
- Icon(Icons.Default.Close)
+ Icon(Icons.Default.Close, contentDescription = "Localized description")
}
}
},
@@ -77,7 +77,7 @@
}
}
) {
- Icon(Icons.Default.Favorite)
+ Icon(Icons.Default.Favorite, contentDescription = "Localized description")
}
},
elevation = 0.dp,
@@ -103,7 +103,12 @@
items(50) {
ListItem(
text = { Text("Item $it") },
- icon = { Icon(Icons.Default.Favorite) }
+ icon = {
+ Icon(
+ Icons.Default.Favorite,
+ contentDescription = "Localized description"
+ )
+ }
)
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomNavigationSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomNavigationSamples.kt
index 321d4bc..b51b529 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomNavigationSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomNavigationSamples.kt
@@ -38,7 +38,7 @@
BottomNavigation {
items.forEachIndexed { index, item ->
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(item) },
selected = selectedItem == index,
selectedItem = index }
@@ -55,7 +55,7 @@
BottomNavigation {
items.forEachIndexed { index, item ->
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(item) },
selected = selectedItem == index,
selectedItem = index },
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
index 8ef6b56..3a39938 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
@@ -89,7 +89,7 @@
title = { Text("Bottom sheet scaffold") },
navigationIcon = {
IconButton( scaffoldState.drawerState.open() }) {
- Icon(Icons.Default.Menu)
+ Icon(Icons.Default.Menu, contentDescription = "Localized description")
}
}
)
@@ -104,7 +104,7 @@
}
}
) {
- Icon(Icons.Default.Favorite)
+ Icon(Icons.Default.Favorite, contentDescription = "Localized description")
}
},
floatingActionButtonPosition = FabPosition.End,
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt
index 12cfe41..3539c00 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt
@@ -58,7 +58,11 @@
@Composable
fun ButtonWithIconSample() {
Button( /* Do something! */ }) {
- Icon(Icons.Filled.Favorite, Modifier.size(ButtonDefaults.IconSize))
+ Icon(
+ Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(ButtonDefaults.IconSize)
+ )
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/FloatingActionButtonSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/FloatingActionButtonSamples.kt
index a90c6a5..da257ea 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/FloatingActionButtonSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/FloatingActionButtonSamples.kt
@@ -31,7 +31,7 @@
@Composable
fun SimpleFab() {
FloatingActionButton( /*do something*/ }) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
}
@@ -47,7 +47,7 @@
@Composable
fun SimpleExtendedFabWithIcon() {
ExtendedFloatingActionButton(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
text = { Text("ADD TO BASKET") },
/*do something*/ }
)
@@ -57,7 +57,7 @@
@Composable
fun FluidExtendedFab() {
ExtendedFloatingActionButton(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
text = { Text("FLUID FAB") },
/*do something*/ },
modifier = Modifier.fillMaxWidth()
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt
index 84ac071..847dbb1 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt
@@ -17,7 +17,7 @@
package androidx.compose.material.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.IconToggleButton
@@ -34,7 +34,7 @@
@Composable
fun IconButtonSample() {
IconButton( /* doSomething() */ }) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
}
@@ -44,7 +44,7 @@
var checked by remember { mutableStateOf(false) }
IconToggleButton(checked = checked, checked = it }) {
- val tint by animateAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
- Icon(Icons.Filled.Favorite, tint = tint)
+ val tint by animateColorAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description", tint = tint)
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
index 88f67fd..c41ca94 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
@@ -104,17 +104,35 @@
Divider()
ListItem(
text = { Text("One line list item with 24x24 icon") },
- icon = { Image(icon24x24, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon24x24,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
text = { Text("One line list item with 40x40 icon") },
- icon = { Image(icon40x40, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon40x40,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
text = { Text("One line list item with 56x56 icon") },
- icon = { Image(icon56x56, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon56x56,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
@@ -122,6 +140,7 @@
icon = {
Image(
icon56x56,
+ contentDescription = null,
colorFilter = ColorFilter.tint(AmbientContentColor.current)
)
},
@@ -130,7 +149,7 @@
Divider()
ListItem(
text = { Text("One line list item with trailing icon") },
- trailing = { Icon(vectorIcon) }
+ trailing = { Icon(vectorIcon, contentDescription = "Localized description") }
)
Divider()
ListItem(
@@ -138,10 +157,11 @@
icon = {
Image(
icon40x40,
+ contentDescription = null,
colorFilter = ColorFilter.tint(AmbientContentColor.current)
)
},
- trailing = { Icon(vectorIcon) }
+ trailing = { Icon(vectorIcon, contentDescription = "Localized description") }
)
Divider()
}
@@ -165,13 +185,25 @@
ListItem(
text = { Text("Two line list item with 24x24 icon") },
secondaryText = { Text("Secondary text") },
- icon = { Image(icon24x24, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon24x24,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
text = { Text("Two line list item with 40x40 icon") },
secondaryText = { Text("Secondary text") },
- icon = { Image(icon40x40, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon40x40,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
@@ -181,6 +213,7 @@
icon = {
Image(
icon40x40,
+ contentDescription = null,
colorFilter = ColorFilter.tint(AmbientContentColor.current)
)
}
@@ -220,7 +253,13 @@
)
},
singleLineSecondaryText = false,
- icon = { Image(icon24x24, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon24x24,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
@@ -232,7 +271,7 @@
)
},
singleLineSecondaryText = false,
- trailing = { Icon(vectorIcon) }
+ trailing = { Icon(vectorIcon, "Localized description") }
)
Divider()
ListItem(
@@ -254,12 +293,24 @@
Divider()
ListItem(
text = { Text("פריט ברשימה אחת עם תמונה.") },
- icon = { Image(icon40x40, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon40x40,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
text = { Text("One line list item with 24x24 icon") },
- icon = { Image(icon40x40, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon40x40,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
ListItem(
@@ -267,6 +318,7 @@
trailing = {
Image(
icon24x24,
+ contentDescription = null,
colorFilter = ColorFilter.tint(AmbientContentColor.current)
)
}
@@ -300,6 +352,7 @@
icon = {
Image(
icon40x40,
+ contentDescription = null,
colorFilter = ColorFilter.tint(AmbientContentColor.current)
)
}
@@ -311,6 +364,7 @@
icon = {
Image(
icon40x40,
+ contentDescription = null,
colorFilter = ColorFilter.tint(AmbientContentColor.current)
)
},
@@ -345,7 +399,13 @@
text = { Text("ثلاثة عناصر قائمة مع رمز") },
overlineText = { Text("فوق الخط") },
secondaryText = { Text("نص ثانوي") },
- icon = { Image(icon40x40, colorFilter = ColorFilter.tint(AmbientContentColor.current)) }
+ icon = {
+ Image(
+ icon40x40,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(AmbientContentColor.current)
+ )
+ }
)
Divider()
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt
index d423bd0..433bfbc 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt
@@ -42,7 +42,7 @@
val iconButton = @Composable {
IconButton( expanded = true }) {
- Icon(Icons.Default.MoreVert)
+ Icon(Icons.Default.MoreVert, contentDescription = "Localized description")
}
}
DropdownMenu(
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt
index 9c63c0e..40ce474 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt
@@ -50,7 +50,12 @@
items(50) {
ListItem(
text = { Text("Item $it") },
- icon = { Icon(Icons.Default.Favorite) }
+ icon = {
+ Icon(
+ Icons.Default.Favorite,
+ contentDescription = "Localized description"
+ )
+ }
)
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
index 6fb006f..b7bcac5 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
@@ -17,7 +17,7 @@
package androidx.compose.material.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
@@ -39,7 +39,7 @@
@Composable
fun LinearProgressIndicatorSample() {
var progress by remember { mutableStateOf(0.1f) }
- val animatedProgress by animateAsState(
+ val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
@@ -61,7 +61,7 @@
@Composable
fun CircularProgressIndicatorSample() {
var progress by remember { mutableStateOf(0.1f) }
- val animatedProgress by animateAsState(
+ val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
index df7666e..1b95b94 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
@@ -91,7 +91,7 @@
scaffoldState.drawerState.open()
}
) {
- Icon(Icons.Filled.Menu)
+ Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
}
)
@@ -161,7 +161,7 @@
scaffoldState.drawerState.open()
}
) {
- Icon(Icons.Filled.Menu)
+ Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
}
},
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
index 4a896c2..a10706d 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
@@ -17,8 +17,9 @@
package androidx.compose.material.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.animateAsState
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -98,7 +99,7 @@
},
background = {
val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
- val color by animateAsState(
+ val color by animateColorAsState(
when (dismissState.targetValue) {
Default -> Color.LightGray
DismissedToEnd -> Color.Green
@@ -113,7 +114,7 @@
StartToEnd -> Icons.Default.Done
EndToStart -> Icons.Default.Delete
}
- val scale by animateAsState(
+ val scale by animateFloatAsState(
if (dismissState.targetValue == Default) 0.75f else 1f
)
@@ -121,12 +122,16 @@
Modifier.fillMaxSize().background(color).padding(horizontal = 20.dp),
contentAlignment = alignment
) {
- Icon(icon, Modifier.scale(scale))
+ Icon(
+ icon,
+ contentDescription = "Localized description",
+ modifier = Modifier.scale(scale)
+ )
}
},
dismissContent = {
Card(
- elevation = animateAsState(
+ elevation = animateDpAsState(
if (dismissState.dismissDirection != null) 4.dp else 0.dp
).value
) {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
index af55358..a61e589 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
@@ -87,7 +87,7 @@
TabRow(selectedTabIndex = state) {
icons.forEachIndexed { index, icon ->
Tab(
- icon = { Icon(icon) },
+ icon = { Icon(icon, contentDescription = "Favorite") },
selected = state == index,
state = index }
)
@@ -114,7 +114,7 @@
titlesAndIcons.forEachIndexed { index, (title, icon) ->
Tab(
text = { Text(title) },
- icon = { Icon(icon) },
+ icon = { Icon(icon, contentDescription = null) },
selected = state == index,
state = index }
)
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
index 83553c1..f631a97 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
@@ -75,8 +75,8 @@
value = text,
text = it },
placeholder = { Text("placeholder") },
- leadingIcon = { Icon(Icons.Filled.Favorite) },
- trailingIcon = { Icon(Icons.Filled.Info) }
+ leadingIcon = { Icon(Icons.Filled.Favorite, contentDescription = "Localized description") },
+ trailingIcon = { Icon(Icons.Filled.Info, contentDescription = "Localized description") }
)
}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AppBarTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AppBarTest.kt
index 05b8283..39baeaf 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AppBarTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AppBarTest.kt
@@ -198,7 +198,7 @@
*/
private val FakeIcon = @Composable { modifier: Modifier ->
IconButton( modifier = modifier) {
- Icon(ColorPainter(Color.Red))
+ Icon(ColorPainter(Color.Red), null)
}
}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationScreenshotTest.kt
index b32d261..c347707 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationScreenshotTest.kt
@@ -316,18 +316,18 @@
Box(Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
BottomNavigation {
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = true,
>
interactionState = interactionState
)
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = false,
>
)
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = false,
>
)
@@ -359,7 +359,7 @@
Box(Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
BottomNavigation(backgroundColor = backgroundColor) {
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = true,
>
interactionState = interactionState,
@@ -367,14 +367,14 @@
unselectedContentColor = unselectedContentColor
)
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = false,
>
selectedContentColor = selectedContentColor,
unselectedContentColor = unselectedContentColor
)
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = false,
>
selectedContentColor = selectedContentColor,
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationTest.kt
index 107384f..dcdef28 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomNavigationTest.kt
@@ -81,7 +81,7 @@
BottomNavigation {
repeat(4) { index ->
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
label = { Text("Item $index") },
selected = index == 0,
>
@@ -119,7 +119,7 @@
BottomNavigationItem(
modifier = Modifier.testTag("item"),
icon = {
- Icon(Icons.Filled.Favorite, Modifier.testTag("icon"))
+ Icon(Icons.Filled.Favorite, null, Modifier.testTag("icon"))
},
label = {
Text("ItemText")
@@ -164,7 +164,7 @@
BottomNavigationItem(
modifier = Modifier.testTag("item"),
icon = {
- Icon(Icons.Filled.Favorite, Modifier.testTag("icon"))
+ Icon(Icons.Filled.Favorite, null, Modifier.testTag("icon"))
},
label = {
Text("ItemText")
@@ -198,7 +198,7 @@
BottomNavigationItem(
modifier = Modifier.testTag("item"),
icon = {
- Icon(Icons.Filled.Favorite, Modifier.testTag("icon"))
+ Icon(Icons.Filled.Favorite, null, Modifier.testTag("icon"))
},
label = {},
selected = false,
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt
index 7349c2a..9cc7ffc 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt
@@ -327,7 +327,7 @@
}.testTag(fabTag),
>
) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, null)
}
},
bodyContent = { Text("Content") }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
index 10d8101..c00a8b0 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
@@ -65,7 +65,7 @@
rule.setMaterialContent {
Box {
FloatingActionButton(modifier = Modifier.testTag("myButton"), {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, null)
}
}
}
@@ -99,7 +99,7 @@
rule
.setMaterialContentForSizeAssertions {
FloatingActionButton( {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, null)
}
}
.assertIsSquareWithSize(56.dp)
@@ -111,7 +111,7 @@
ExtendedFloatingActionButton(
modifier = Modifier.testTag("FAB"),
text = { Text("Extended FAB Text") },
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
>
)
}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
index 9953c9c..dea21b2 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.AmbientDensity
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.captureToImage
@@ -41,6 +42,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,7 +60,7 @@
val vector = Icons.Filled.Menu
rule
.setMaterialContentForSizeAssertions {
- Icon(vector)
+ Icon(vector, null)
}
.assertWidthIsEqualTo(width)
.assertHeightIsEqualTo(height)
@@ -74,7 +76,7 @@
).build()
rule
.setMaterialContentForSizeAssertions {
- Icon(vector)
+ Icon(vector, null)
}
.assertWidthIsEqualTo(width)
.assertHeightIsEqualTo(height)
@@ -90,7 +92,7 @@
ImageBitmap(width.toIntPx(), height.toIntPx())
}
- Icon(image)
+ Icon(image, null)
}
.assertWidthIsEqualTo(width)
.assertHeightIsEqualTo(height)
@@ -107,7 +109,7 @@
ImageBitmap(width.toIntPx(), height.toIntPx())
}
- Icon(image)
+ Icon(image, null)
}
.assertWidthIsEqualTo(width)
.assertHeightIsEqualTo(height)
@@ -120,7 +122,7 @@
val painter = ColorPainter(Color.Red)
rule
.setMaterialContentForSizeAssertions {
- Icon(painter)
+ Icon(painter, null)
}
.assertWidthIsEqualTo(width)
.assertHeightIsEqualTo(height)
@@ -138,7 +140,7 @@
}
val imagePainter = ImagePainter(image)
- Icon(imagePainter)
+ Icon(imagePainter, null)
}
.assertWidthIsEqualTo(width)
.assertHeightIsEqualTo(height)
@@ -160,7 +162,7 @@
Color.Red
)
}
- Icon(image, modifier = Modifier.testTag(testTag), tint = Color.Unspecified)
+ Icon(image, null, modifier = Modifier.testTag(testTag), tint = Color.Unspecified)
}
// With no color provided for a tint, the icon should render the original pixels
@@ -183,13 +185,31 @@
Color.Red
)
}
- Icon(image, modifier = Modifier.testTag(testTag), tint = Color.Blue)
+ Icon(image, null, modifier = Modifier.testTag(testTag), tint = Color.Blue)
}
// With a tint color provided, all pixels should be blue
rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Blue }
}
+ @Test
+ fun contentDescriptionAppliedToIcon() {
+ val testTag = "TestTag"
+ rule.setContent {
+ Icon(
+ bitmap = ImageBitmap(100, 100),
+ contentDescription = "qwerty",
+ modifier = Modifier.testTag(testTag)
+ )
+ }
+
+ rule.onNodeWithTag(testTag).fetchSemanticsNode().let {
+ assertThat(it.config.contains(SemanticsProperties.ContentDescription)).isTrue()
+ assertThat(it.config[SemanticsProperties.ContentDescription])
+ .isEqualTo("qwerty")
+ }
+ }
+
private fun createBitmapWithColor(
density: Density,
width: Int,
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
index a3588ff..087e32e 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
@@ -71,7 +71,7 @@
.setMaterialContentForSizeAssertions {
ListItem(
text = { Text("Primary text") },
- icon = { Icon(icon24x24) }
+ icon = { Icon(icon24x24, null) }
)
}
.assertHeightIsEqualTo(expectedHeightSmallIcon)
@@ -85,7 +85,7 @@
.setMaterialContentForSizeAssertions {
ListItem(
text = { Text("Primary text") },
- icon = { Icon(icon56x56) }
+ icon = { Icon(icon56x56, null) }
)
}
.assertHeightIsEqualTo(expectedHeightLargeIcon)
@@ -115,7 +115,7 @@
ListItem(
text = { Text("Primary text") },
secondaryText = { Text("Secondary text") },
- icon = { Icon(icon24x24) }
+ icon = { Icon(icon24x24, null) }
)
}
.assertHeightIsEqualTo(expectedHeightWithIcon)
@@ -199,7 +199,7 @@
ListItem(
text = { Text("Primary text", Modifier.saveLayout(textPosition, textSize)) },
trailing = {
- Image(icon24x24, Modifier.saveLayout(trailingPosition, trailingSize))
+ Image(icon24x24, null, Modifier.saveLayout(trailingPosition, trailingSize))
}
)
}
@@ -238,7 +238,7 @@
Box {
ListItem(
text = { Text("Primary text", Modifier.saveLayout(textPosition, textSize)) },
- icon = { Image(icon24x24, Modifier.saveLayout(iconPosition, iconSize)) }
+ icon = { Image(icon24x24, null, Modifier.saveLayout(iconPosition, iconSize)) }
)
}
}
@@ -365,7 +365,7 @@
)
},
icon = {
- Image(icon24x24, Modifier.saveLayout(iconPosition, iconSize))
+ Image(icon24x24, null, Modifier.saveLayout(iconPosition, iconSize))
}
)
}
@@ -436,10 +436,10 @@
)
},
icon = {
- Image(icon40x40, Modifier.saveLayout(iconPosition, iconSize))
+ Image(icon40x40, null, Modifier.saveLayout(iconPosition, iconSize))
},
trailing = {
- Image(icon24x24, Modifier.saveLayout(trailingPosition, trailingSize))
+ Image(icon24x24, null, Modifier.saveLayout(trailingPosition, trailingSize))
}
)
}
@@ -518,10 +518,10 @@
},
singleLineSecondaryText = false,
icon = {
- Image(icon24x24, Modifier.saveLayout(iconPosition, iconSize))
+ Image(icon24x24, null, Modifier.saveLayout(iconPosition, iconSize))
},
trailing = {
- Image(icon24x24, Modifier.saveLayout(trailingPosition, trailingSize))
+ Image(icon24x24, null, Modifier.saveLayout(trailingPosition, trailingSize))
}
)
}
@@ -613,6 +613,7 @@
icon = {
Image(
icon40x40,
+ null,
Modifier.saveLayout(iconPosition, iconSize)
)
},
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldScreenshotTest.kt
index 872efe4..9600b81 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldScreenshotTest.kt
@@ -623,7 +623,7 @@
val cutoutShape = if (fabCutout) CircleShape else null
BottomAppBar(cutoutShape = cutoutShape) {
IconButton( {
- Icon(Icons.Filled.Menu)
+ Icon(Icons.Filled.Menu, null)
}
}
}
@@ -645,7 +645,7 @@
val fab = @Composable {
if (showFab) {
FloatingActionButton(
- content = { Icon(Icons.Filled.Favorite) },
+ content = { Icon(Icons.Filled.Favorite, null) },
>
)
}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
index aa6181f0..d33dc24 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
@@ -285,7 +285,7 @@
},
>
) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, null)
}
},
floatingActionButtonPosition = FabPosition.Center,
@@ -321,7 +321,7 @@
},
>
) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, null)
}
},
floatingActionButtonPosition = FabPosition.End,
@@ -394,7 +394,7 @@
},
>
) {
- Icon(Icons.Filled.Favorite)
+ Icon(Icons.Filled.Favorite, null)
}
}
}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
index 65e2389..ea0bd06 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
@@ -114,7 +114,7 @@
fun iconTab_height() {
rule
.setMaterialContentForSizeAssertions {
- Tab(icon = { Icon(icon) }, selected = true, >
+ Tab(icon = { Icon(icon, null) }, selected = true, >
}
.assertHeightIsEqualTo(ExpectedSmallTabHeight)
}
@@ -126,7 +126,7 @@
Surface {
Tab(
text = { Text("Text and Icon") },
- icon = { Icon(icon) },
+ icon = { Icon(icon, null) },
selected = true,
>
)
@@ -245,7 +245,7 @@
text = {
Text(title, Modifier.testTag("text"))
},
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, null) },
selected = state == index,
state = index }
)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
index d244464..c1890e6 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
@@ -21,7 +21,7 @@
import androidx.compose.animation.core.AnimationEndReason.Interrupted
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -378,7 +378,7 @@
visible: Boolean
) {
if (color != Color.Transparent) {
- val alpha by animateAsState(
+ val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = TweenSpec()
)
@@ -407,7 +407,7 @@
) {
// The progress of the animation between Revealed (0) and Concealed (2).
// The midpoint (1) is the point where the appBar and backContent are switched.
- val animationProgress by animateAsState(
+ val animationProgress by animateFloatAsState(
targetValue = if (target == Revealed) 0f else 2f, animationSpec = TweenSpec()
)
val animationSlideOffset = with(AmbientDensity.current) { AnimationSlideOffset.toPx() }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
index 8dbe44b..743d369 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
@@ -19,7 +19,7 @@
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.VectorizedAnimationSpec
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.layout.Arrangement
@@ -204,7 +204,7 @@
selected: Boolean,
content: @Composable (animationProgress: Float) -> Unit
) {
- val animationProgress by animateAsState(
+ val animationProgress by animateFloatAsState(
targetValue = if (selected) 1f else 0f,
animationSpec = BottomNavigationAnimationSpec
)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
index 78915f1..134ff10 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
@@ -16,7 +16,7 @@
package androidx.compose.material
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FloatPropKey
import androidx.compose.animation.core.TransitionSpec
import androidx.compose.animation.core.keyframes
@@ -365,7 +365,7 @@
}
val duration = if (state == ToggleableState.Off) BoxOutDuration else BoxInDuration
- return animateAsState(target, tween(durationMillis = duration))
+ return animateColorAsState(target, tween(durationMillis = duration))
}
@Composable
@@ -387,7 +387,7 @@
// enabled / disabled.
return if (enabled) {
val duration = if (state == ToggleableState.Off) BoxOutDuration else BoxInDuration
- animateAsState(target, tween(durationMillis = duration))
+ animateColorAsState(target, tween(durationMillis = duration))
} else {
rememberUpdatedState(target)
}
@@ -411,7 +411,7 @@
// enabled / disabled.
return if (enabled) {
val duration = if (state == ToggleableState.Off) BoxOutDuration else BoxInDuration
- animateAsState(target, tween(durationMillis = duration))
+ animateColorAsState(target, tween(durationMillis = duration))
} else {
rememberUpdatedState(target)
}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
index e21c901..aeb0954 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
@@ -31,6 +31,8 @@
import androidx.compose.ui.graphics.toolingGraphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
/**
@@ -38,6 +40,10 @@
* clickable icon, see [IconButton].
*
* @param imageVector [ImageVector] to draw inside this Icon
+ * @param contentDescription text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
* @param modifier optional [Modifier] for this Icon
* @param tint tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no
* tint is applied
@@ -45,11 +51,13 @@
@Composable
fun Icon(
imageVector: ImageVector,
+ contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = AmbientContentColor.current.copy(alpha = AmbientContentAlpha.current)
) {
Icon(
painter = rememberVectorPainter(imageVector),
+ contentDescription = contentDescription,
modifier = modifier,
tint = tint
)
@@ -60,6 +68,10 @@
* clickable icon, see [IconButton].
*
* @param bitmap [ImageBitmap] to draw inside this Icon
+ * @param contentDescription text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
* @param modifier optional [Modifier] for this Icon
* @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no
* tint is applied
@@ -67,12 +79,14 @@
@Composable
fun Icon(
bitmap: ImageBitmap,
+ contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = AmbientContentColor.current
) {
val painter = remember(bitmap) { ImagePainter(bitmap) }
Icon(
painter = painter,
+ contentDescription = contentDescription,
modifier = modifier,
tint = tint
)
@@ -83,6 +97,10 @@
* clickable icon, see [IconButton].
*
* @param painter [Painter] to draw inside this Icon
+ * @param contentDescription text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
* @param modifier optional [Modifier] for this Icon
* @param tint tint to be applied to [painter]. If [Color.Unspecified] is provided, then no
* tint is applied
@@ -90,6 +108,7 @@
@Composable
fun Icon(
painter: Painter,
+ contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = AmbientContentColor.current.copy(alpha = AmbientContentAlpha.current)
) {
@@ -97,9 +116,15 @@
// size that this icon will be forced to take up.
// TODO: b/149735981 semantics for content description
val colorFilter = if (tint == Color.Unspecified) null else ColorFilter.tint(tint)
+ val semantics = if (contentDescription != null) {
+ Modifier.semantics { this.contentDescription = contentDescription }
+ } else {
+ Modifier
+ }
Box(
modifier.toolingGraphicsLayer().defaultSizeFor(painter)
.paint(painter, colorFilter = colorFilter)
+ .then(semantics)
)
}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 3ea26dd..1c4d33a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -21,7 +21,7 @@
import androidx.compose.animation.core.AnimationEndReason
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -287,7 +287,7 @@
visible: Boolean
) {
if (color != Color.Transparent) {
- val alpha by animateAsState(
+ val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = TweenSpec()
)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
index 97bc87e..feb68ad 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
@@ -16,8 +16,8 @@
package androidx.compose.material
-import androidx.compose.animation.animateAsState
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Interaction
@@ -77,7 +77,7 @@
interactionState: InteractionState = remember { InteractionState() },
colors: RadioButtonColors = RadioButtonDefaults.colors()
) {
- val dotRadius by animateAsState(
+ val dotRadius by animateDpAsState(
targetValue = if (selected) RadioButtonDotSize / 2 else 0.dp,
animationSpec = tween(durationMillis = RadioAnimationDuration)
)
@@ -182,7 +182,7 @@
// If not enabled 'snap' to the disabled state, as there should be no animations between
// enabled / disabled.
return if (enabled) {
- animateAsState(target, tween(durationMillis = RadioAnimationDuration))
+ animateColorAsState(target, tween(durationMillis = RadioAnimationDuration))
} else {
rememberUpdatedState(target)
}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
index ddba4a7..cf5d043 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
@@ -19,7 +19,7 @@
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.transition
@@ -226,7 +226,7 @@
// TODO: should we animate the width of the indicator as it moves between tabs of different
// sizes inside a scrollable tab row?
val currentTabWidth = currentTabPosition.width
- val indicatorOffset by animateAsState(
+ val indicatorOffset by animateDpAsState(
targetValue = currentTabPosition.left,
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
)
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 6afaa39..c11c1a0 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -21,6 +21,7 @@
}
public final class ActualJvmKt {
+ method @kotlin.PublishedApi internal static inline <R> R! synchronized(Object lock, kotlin.jvm.functions.Function0<? extends R> block);
}
@androidx.compose.runtime.Stable public abstract sealed class Ambient<T> {
@@ -242,6 +243,7 @@
}
public final class ExpectKt {
+ method @kotlin.PublishedApi internal static inline <R> R! synchronized(Object lock, kotlin.jvm.functions.Function0<? extends R> block);
}
@kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level, message="This is an experimental API for Compose and is likely to change before becoming " + "stable.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalComposeApi {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
index 31c52b0..9edecf5 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
@@ -30,6 +30,7 @@
internal expect fun identityHashCode(instance: Any?): Int
+@PublishedApi
internal expect inline fun <R> synchronized(lock: Any, block: () -> R): R
expect class AtomicReference<V>(value: V) {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 6f68340..8deda49 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -22,6 +22,7 @@
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.ThreadLocal
+import androidx.compose.runtime.synchronized
/**
* Take a snapshot of the current value of all state objects. The values are preserved until
@@ -1362,7 +1363,6 @@
internal val lock = Any()
@PublishedApi
-@Suppress("DEPRECATION_ERROR")
internal inline fun <T> sync(block: () -> T): T = synchronized(lock, block)
// The following variables should only be written when sync is taken
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index 07f89e2..2c4a5ae 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -20,9 +20,9 @@
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.TestOnly
import androidx.compose.runtime.collection.IdentityScopeMap
+import androidx.compose.runtime.synchronized
@ExperimentalComposeApi
-@Suppress("DEPRECATION_ERROR")
class SnapshotStateObserver(private val onChangedExecutor: (callback: () -> Unit) -> Unit) {
private val applyObserver: SnapshotApplyObserver = { applied, _ ->
var hasValues = false
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt
index 830f020..e61fdc6 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt
@@ -39,10 +39,9 @@
internal actual fun identityHashCode(instance: Any?): Int = System.identityHashCode(instance)
+@PublishedApi
internal actual inline fun <R> synchronized(lock: Any, block: () -> R): R {
- kotlin.synchronized(lock) {
- return block()
- }
+ return kotlin.synchronized(lock, block)
}
internal actual typealias TestOnly = org.jetbrains.annotations.TestOnly
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/TestAnimationPreview.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/TestAnimationPreview.kt
index 232a23f..17e63cf 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/TestAnimationPreview.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/TestAnimationPreview.kt
@@ -71,6 +71,6 @@
shape = MaterialTheme.shapes.large.copy(topLeft = CornerSize(state[CheckBoxCorner])),
modifier = Modifier.toggleable(value = selected, >
) {
- Icon(imageVector = Icons.Filled.Done)
+ Icon(imageVector = Icons.Filled.Done, contentDescription = null)
}
}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
index 2c25f26..eeb8a85 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
@@ -93,7 +93,7 @@
Inspectable(slotTableRecord) {
Column {
Text(text = "Hello World", color = Color.Green)
- Icon(Icons.Filled.FavoriteBorder)
+ Icon(Icons.Filled.FavoriteBorder, null)
Surface {
Button( { Text(text = "OK") }
}
@@ -309,7 +309,7 @@
Column {
Text(text = "Hello World", color = Color.Green)
Spacer(Modifier.preferredHeight(16.dp))
- Image(Icons.Filled.Call)
+ Image(Icons.Filled.Call, null)
}
}
}
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/VectorGraphicsDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/VectorGraphicsDemo.kt
index 8465693..5686f60 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/VectorGraphicsDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/VectorGraphicsDemo.kt
@@ -51,6 +51,7 @@
imageVector.resource.resource?.let {
Image(
imageVector = it,
+ contentDescription = "Crane",
modifier = Modifier.preferredSize(200.dp, 200.dp),
contentScale = ContentScale.Inside
)
@@ -60,6 +61,7 @@
complexImageVector.resource.resource?.let {
Image(
imageVector = it,
+ contentDescription = "Hourglass",
modifier = Modifier.preferredSize(64.dp, 64.dp),
contentScale = ContentScale.Fit
)
@@ -67,6 +69,7 @@
Image(
painter = vectorShape(120.dp, 120.dp),
+ contentDescription = null,
modifier = Modifier.preferredSize(200.dp, 150.dp)
)
}
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt
index bfcbed2..bd96ca8 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt
@@ -17,7 +17,7 @@
package androidx.compose.ui.demos.focus
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -76,7 +76,7 @@
@Composable
private fun Circle(modifier: Modifier = Modifier, nextShape: () -> Unit) {
var isFocused by remember { mutableStateOf(false) }
- val scale by animateAsState(if (isFocused) 0f else 1f, TweenSpec(2000)) {
+ val scale by animateFloatAsState(if (isFocused) 0f else 1f, TweenSpec(2000)) {
if (it == 0f) {
nextShape()
}
@@ -99,7 +99,7 @@
@Composable
private fun Square(modifier: Modifier = Modifier, nextShape: () -> Unit) {
var isFocused by remember { mutableStateOf(false) }
- val scale by animateAsState(if (isFocused) 0f else 1f, TweenSpec(2000)) {
+ val scale by animateFloatAsState(if (isFocused) 0f else 1f, TweenSpec(2000)) {
if (it == 0f) {
nextShape()
}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/DrawModifierSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/DrawModifierSample.kt
index 15c1dbd..cb5f8ad 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/DrawModifierSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/DrawModifierSample.kt
@@ -111,6 +111,7 @@
}
Image(
painter = vectorPainter,
+ contentDescription = null,
modifier = Modifier.size(120.dp).drawWithCache {
val gradient = Brush.linearGradient(
colors = listOf(Color.Red, Color.Blue),
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt
index dfce282..48f7a8e 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt
@@ -66,6 +66,7 @@
// in the landscape orientation based on the res/drawable and res/drawable-land-hdpi folders
Image(
painterResource(R.drawable.ic_vector_or_png),
+ contentDescription = null,
modifier = Modifier.size(50.dp)
)
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index cc50b9a..00f8ee8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -29,7 +29,7 @@
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.RequiresApi
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -3005,7 +3005,7 @@
activity.setContent {
Box(Modifier.background(Color.Red).drawLatchModifier()) {
var animatedSize by remember { mutableStateOf(size) }
- animatedSize = animateAsState(size).value
+ animatedSize = animateFloatAsState(size).value
if (animatedSize == 10f) {
Layout(
modifier = Modifier.background(Color.Cyan),
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 66d65f3..3847526 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -233,13 +233,14 @@
val clickState = remember { mutableStateOf(false) }
Image(
imageVector = if (clickState.value) icon1 else icon2,
+ contentDescription = null,
modifier = Modifier
.testTag(testTag)
.preferredSize(icon1.defaultWidth, icon1.defaultHeight)
.background(Color.Red)
.clickable { clickState.value = !clickState.value },
- contentScale = ContentScale.FillHeight,
- alignment = Alignment.TopStart
+ alignment = Alignment.TopStart,
+ contentScale = ContentScale.FillHeight
)
}
@@ -292,6 +293,7 @@
}
Image(
painter = vectorPainter,
+ contentDescription = null,
modifier = Modifier
.testTag(testTag)
.preferredSize(defaultWidth * 7, defaultHeight * 3)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt
index 73e2715..186a2b0 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt
@@ -92,6 +92,43 @@
}
@Test
+ @SmallTest
+ fun internalSizeChange() {
+ var latch = CountDownLatch(1)
+ var changedSize = IntSize.Zero
+ var sizePx by mutableStateOf(10)
+
+ rule.runOnUiThread {
+ activity.setContent {
+ with(AmbientDensity.current) {
+ Box(
+ Modifier.padding(10.toDp())
+ .onSizeChanged {
+ changedSize = it
+ latch.countDown()
+ }.padding(sizePx.toDp())
+ ) {
+ Box(Modifier.size(10.toDp()))
+ }
+ }
+ }
+ }
+
+ // Initial setting will call onSizeChanged
+ assertTrue(latch.await(1, TimeUnit.SECONDS))
+ assertEquals(30, changedSize.height)
+ assertEquals(30, changedSize.width)
+
+ latch = CountDownLatch(1)
+ sizePx = 20
+
+ // We've changed the size of the contents, so we should receive a onSizeChanged call
+ assertTrue(latch.await(1, TimeUnit.SECONDS))
+ assertEquals(50, changedSize.height)
+ assertEquals(50, changedSize.width)
+ }
+
+ @Test
fun onlyInnerSizeChange() {
var latch = CountDownLatch(1)
var changedSize = IntSize.Zero
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
index 338fcaf..90d7e8b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
@@ -23,14 +23,17 @@
import androidx.compose.ui.unit.IntSize
/**
- * Invoke [onSizeChanged] with the size of the content after it has been measured.
- * This will only be invoked either the first time measurement happens or when the content
+ * Invoke [onSizeChanged] when the size of the modifier immediately after it has changed. If
+ * there is no modifier following [onSizeChanged], the content size of the layout is reported.
+ *
+ * [onSizeChanged] will only be invoked during the first time measurement or when the
* size has changed.
*
- * Use [Layout] or [SubcomposeLayout] to have the size of one component to affect the size
+ * Use [Layout] or [SubcomposeLayout] to have the size of one component affect the size
* of another component. Using the size received from the [onSizeChanged] callback in a
* [MutableState] to affect layout will cause the new value to be recomposed and read only in the
- * following frame, causing a one frame lag.
+ * following frame, causing a one frame lag. You can use [onSizeChanged] to affect
+ * drawing operations.
*
* Example usage:
* @sample androidx.compose.ui.samples.OnSizeChangedSample
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 7f1d771..c4b79fe 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -60,7 +60,6 @@
import androidx.compose.ui.semantics.outerSemantics
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.deleteAt
import kotlin.math.roundToInt
@@ -642,7 +641,6 @@
}
val addedCallback = hasNewPositioningCallback()
onPositionedCallbacks.clear()
- onRemeasuredCallbacks.clear()
// Create a new chain of LayoutNodeWrappers, reusing existing ones from wrappers
// when possible.
@@ -651,9 +649,6 @@
if (mod is OnGloballyPositionedModifier) {
onPositionedCallbacks += mod
}
- if (mod is OnRemeasuredModifier) {
- onRemeasuredCallbacks += mod
- }
if (mod is RemeasurementModifier) {
mod.onRemeasurementAvailable(this)
}
@@ -699,6 +694,9 @@
if (mod is SemanticsModifier) {
wrapper = SemanticsWrapper(wrapper, mod).assignChained(toWrap)
}
+ if (mod is OnRemeasuredModifier) {
+ wrapper = RemeasureModifierWrapper(wrapper, mod).assignChained(toWrap)
+ }
}
wrapper
}
@@ -770,11 +768,6 @@
private val >
/**
- * List of all OnSizeChangedModifiers in the modifier chain.
- */
- private val >
-
- /**
* Flag used by [OnPositionedDispatcher] to identify LayoutNodes that have already
* had their [OnGloballyPositionedModifier]'s dispatch called so that they aren't called
* multiple times.
@@ -1070,16 +1063,6 @@
innerLayoutNodeWrapper.measureResult = measureResult
this.providedAlignmentLines.clear()
this.providedAlignmentLines += measureResult.alignmentLines
-
- if (onRemeasuredCallbacks.isNotEmpty()) {
- val invokeRemeasureCallbacks = {
- val content = innerLayoutNodeWrapper
- val size = IntSize(content.measuredWidth, content.measuredHeight)
- onRemeasuredCallbacks.forEach { it.onRemeasured(size) }
- }
- owner?.snapshotObserver?.pauseSnapshotReadObservation(invokeRemeasureCallbacks)
- ?: invokeRemeasureCallbacks.invoke()
- }
}
/**
@@ -1389,4 +1372,4 @@
wrapper.isChained = true
}
return this
-}
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
new file mode 100644
index 0000000..6d25308
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 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.node
+
+import androidx.compose.ui.layout.OnRemeasuredModifier
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Constraints
+
+/**
+ * Wrapper around the [OnRemeasuredModifier] to notify whenever a remeasurement happens.
+ */
+internal class RemeasureModifierWrapper(
+ wrapped: LayoutNodeWrapper,
+ modifier: OnRemeasuredModifier
+) : DelegatingLayoutNodeWrapper<OnRemeasuredModifier>(wrapped, modifier) {
+ override fun performMeasure(constraints: Constraints): Placeable {
+ val placeable = super.performMeasure(constraints)
+ val invokeRemeasureCallbacks = {
+ modifier.onRemeasured(measuredSize)
+ }
+ layoutNode.owner?.snapshotObserver?.pauseSnapshotReadObservation(invokeRemeasureCallbacks)
+ ?: invokeRemeasureCallbacks.invoke()
+ return placeable
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index 9bac865..950d856 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -18,7 +18,7 @@
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -214,7 +214,7 @@
var targetValue by mutableStateOf(10f)
setContent {
- val value by animateAsState(
+ val value by animateFloatAsState(
targetValue,
animationSpec = TweenSpec(durationMillis = 30, easing = LinearEasing)
)
diff --git a/datastore/datastore-core/src/main/java/androidx/datastore/core/SimpleActor.kt b/datastore/datastore-core/src/main/java/androidx/datastore/core/SimpleActor.kt
new file mode 100644
index 0000000..a2a7689
--- /dev/null
+++ b/datastore/datastore-core/src/main/java/androidx/datastore/core/SimpleActor.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 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.datastore.core
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.launch
+import java.util.concurrent.atomic.AtomicInteger
+
+internal class SimpleActor<T>(
+ /**
+ * The scope in which to consume messages.
+ */
+ private val scope: CoroutineScope,
+ /**
+ * Function that will be called when scope is cancelled.
+ */
+ onComplete: (Throwable?) -> Unit,
+ /**
+ * Function that will be called once for each message.
+ *
+ * Must *not* throw an exception (other than CancellationException if scope is cancelled).
+ */
+ private val consumeMessage: suspend (T) -> Unit
+) {
+ private val messageQueue = Channel<T>(capacity = UNLIMITED)
+ private val remainingMessages = AtomicInteger(0)
+
+ init {
+ // If the scope doesn't have a job, it won't be cancelled, so we don't need to register a
+ // callback.
+ scope.coroutineContext[Job]?.invokeOnCompletion { ex ->
+ onComplete(ex)
+ messageQueue.cancel()
+ }
+ }
+
+ fun offer(msg: T) {
+ /**
+ * Possible states:
+ * 1) No active consumer and remaining=0
+ * This will happen when there are no remaining messages to receive in the channel
+ * and there is no one actively sending.
+ * 2) No active consumer and remaining>0
+ * This will only happen after a new send call after state 1. The new sender
+ * incremented the count, and must send a message to the queue and is responsible for
+ * starting a new consumer (state 3).
+ * 3) Active consumer and remaining>0
+ * The consumer must continue to process messages until it sees that there are no
+ * remaining messages, at which point it will revert to state 1.
+ */
+
+ // If the number of remaining messages was 0, there is no consumer, since it quits
+ // consuming once remaining messages hits 0. We must kick off a new consumer.
+ val shouldReLaunch = remainingMessages.getAndIncrement() == 0
+
+ check(messageQueue.offer(msg)) // should never fail bc the channel capacity is unlimited
+
+ if (shouldReLaunch) {
+ scope.launch {
+ // We shouldn't have started a new consumer unless there are remaining messages...
+ check(remainingMessages.get() > 0)
+
+ do {
+ // We don't want to try to consume a new message unless we are still active.
+ // If ensureActive throws, the scope is no longer active, so it doesn't
+ // matter that we have remaining messages.
+ scope.ensureActive()
+
+ consumeMessage(messageQueue.receive())
+ } while (remainingMessages.decrementAndGet() != 0)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt b/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt
index a7a831e..e2f02e1 100644
--- a/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt
+++ b/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt
@@ -16,16 +16,14 @@
package androidx.datastore.core
import androidx.datastore.core.handlers.NoOpCorruptionHandler
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
-import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.completeWith
import kotlinx.coroutines.flow.Flow
@@ -59,7 +57,7 @@
/**
* Single process implementation of DataStore. This is NOT multi-process safe.
*/
-@OptIn(ExperimentalCoroutinesApi::class, ObsoleteCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
internal class SingleProcessDataStore<T>(
private val produceFile: () -> File,
private val serializer: Serializer<T>,
@@ -86,7 +84,7 @@
val dataChannel = downstreamChannel()
val updateMsg = Message.Update<T>(transform, ack, dataChannel, coroutineContext)
- actor.send(updateMsg)
+ actor.offer(updateMsg)
// If no read has succeeded yet, we need to wait on the result of the next read so we can
// bubble exceptions up to the caller. Read exceptions are not bubbled up through ack.
@@ -142,38 +140,39 @@
/**
* Consumes messages. All state changes should happen within actor.
*/
- private val actor: SendChannel<Message<T>> = scope.actor(
- capacity = UNLIMITED
- ) {
- try {
- messageConsumer@ for (msg in channel) {
- if (msg.dataChannel.isClosedForSend) {
- // The message was sent with an old, now closed, dataChannel. This means that
- // our read failed.
- continue@messageConsumer
- }
-
- try {
- readAndInitOnce(msg.dataChannel)
- } catch (ex: Throwable) {
- resetDataChannel(ex)
- continue@messageConsumer
- }
-
- // We have successfully read data and sent it to downstreamChannel.
-
- if (msg is Message.Update) {
- msg.ack.completeWith(
- runCatching {
- transformAndWrite(msg.transform, downstreamChannel(), msg.callerContext)
- }
- )
- }
- }
- } finally {
+ private val actor = SimpleActor<Message<T>>(
+ scope = scope,
+ >
// The scope has been cancelled. Cancel downstream in case there are any collectors
// still active.
- downstreamChannel().cancel()
+ if (it is CancellationException) {
+ downstreamChannel().cancel(it)
+ } else if (it is Throwable) {
+ downstreamChannel().close(it)
+ }
+ }
+ ) { msg ->
+ if (msg.dataChannel.isClosedForSend) {
+ // The message was sent with an old, now closed, dataChannel. This means that
+ // our read failed.
+ return@SimpleActor
+ }
+
+ try {
+ readAndInitOnce(msg.dataChannel)
+ } catch (ex: Throwable) {
+ resetDataChannel(ex)
+ return@SimpleActor
+ }
+
+ // We have successfully read data and sent it to downstreamChannel.
+
+ if (msg is Message.Update) {
+ msg.ack.completeWith(
+ runCatching {
+ transformAndWrite(msg.transform, downstreamChannel(), msg.callerContext)
+ }
+ )
}
}
diff --git a/datastore/datastore-core/src/test/java/androidx/datastore/core/SimpleActorTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/core/SimpleActorTest.kt
new file mode 100644
index 0000000..702abb1
--- /dev/null
+++ b/datastore/datastore-core/src/test/java/androidx/datastore/core/SimpleActorTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2021 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.datastore.core
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.coroutineContext
+
+@ExperimentalCoroutinesApi
+class SimpleActorTest {
+ @Test
+ fun testSimpleActor() = runBlockingTest {
+ val msgs = mutableListOf<Int>()
+
+ val actor = SimpleActor<Int>(
+ this,
+ >
+ ) {
+ msgs.add(it)
+ }
+
+ actor.offer(1)
+ actor.offer(2)
+ actor.offer(3)
+ actor.offer(4)
+
+ assertThat(msgs).isEqualTo(listOf(1, 2, 3, 4))
+ }
+
+ @Test
+ fun testOnCompleteIsCalledWhenScopeIsCancelled() = runBlocking<Unit> {
+ val scope = CoroutineScope(Job())
+ val called = AtomicBoolean(false)
+
+ val actor = SimpleActor<Int>(
+ scope,
+ >
+ assertThat(called.compareAndSet(false, true)).isTrue()
+ }
+ ) {
+ // do nothing
+ }
+
+ actor.offer(123)
+
+ scope.coroutineContext[Job]!!.cancelAndJoin()
+
+ assertThat(called.get()).isTrue()
+ }
+
+ @Test
+ fun testManyConcurrentCalls() = runBlocking<Unit> {
+ val scope = CoroutineScope(Job() + Executors.newFixedThreadPool(4).asCoroutineDispatcher())
+ val numCalls = 100000
+ val volatileIntHolder = VolatileIntHolder()
+
+ val latch = CountDownLatch(numCalls)
+ val actor = SimpleActor<Int>(scope, {
+ val newValue = volatileIntHolder.int + 1
+ // This should be safe because there shouldn't be any concurrent calls
+ volatileIntHolder.int = newValue
+ latch.countDown()
+ }
+
+ repeat(numCalls) {
+ scope.launch {
+ actor.offer(it)
+ }
+ }
+
+ latch.await(5, TimeUnit.SECONDS)
+
+ assertThat(volatileIntHolder.int).isEqualTo(numCalls)
+ }
+
+ @Test
+ fun testManyConcurrentCalls_withDelayBeforeSettingValue() = runBlocking<Unit> {
+ val scope = CoroutineScope(Job() + Executors.newFixedThreadPool(4).asCoroutineDispatcher())
+ val numCalls = 500
+ val volatileIntHolder = VolatileIntHolder()
+
+ val latch = CountDownLatch(numCalls)
+ val actor = SimpleActor<Int>(scope, {
+ val newValue = volatileIntHolder.int + 1
+ delay(1)
+ // This should be safe because there shouldn't be any concurrent calls
+ volatileIntHolder.int = newValue
+ latch.countDown()
+ }
+
+ repeat(numCalls) {
+ scope.launch {
+ actor.offer(it)
+ }
+ }
+
+ latch.await(5, TimeUnit.SECONDS)
+
+ assertThat(volatileIntHolder.int).isEqualTo(numCalls)
+ }
+
+ @Test
+ fun testMessagesAreConsumedInProvidedScope() = runBlocking {
+ val scope = CoroutineScope(TestElement("test123"))
+ val latch = CompletableDeferred<Unit>()
+
+ val actor = SimpleActor<Int>(scope, {
+ assertThat(getTestElement().name).isEqualTo("test123")
+ latch.complete(Unit)
+ }
+
+ actor.offer(123)
+
+ latch.await()
+ }
+
+ class TestElement(val name: String) : AbstractCoroutineContextElement(Key) {
+ companion object Key : CoroutineContext.Key<TestElement>
+ }
+
+ private suspend fun getTestElement(): TestElement {
+ return coroutineContext[TestElement]!!
+ }
+
+ private class VolatileIntHolder {
+ @Volatile
+ var int = 0
+ }
+}
\ No newline at end of file
diff --git a/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt
index b8c7ded..ac1dca1 100644
--- a/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt
+++ b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt
@@ -301,7 +301,10 @@
dataStoreScope.cancel()
assertThrows<CancellationException> { slowUpdate.await() }
+
assertThrows<CancellationException> { notStartedUpdate.await() }
+
+ assertThrows<CancellationException> { store.updateData { 123 } }
}
@Test
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 8a6e871..072e510 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -420,6 +420,9 @@
WARNING: Illegal reflective access by androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion\$isFromCompiledClass\$[0-9]+ \(file:\$OUT_DIR/androidx/room/room\-compiler\-processing/build/libs/room\-compiler\-processing\-[0-9]+\.[0-9]+\.[0-9]+\-alpha[0-9]+\.jar\) to field com\.sun\.tools\.javac\.code\.Symbol\$ClassSymbol\.classfile
WARNING: Please consider reporting this to the maintainers of androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion\$isFromCompiledClass\$[0-9]+
# > Task :wear:wear-watchface-complications-rendering:compileDebugUnitTestJavaWithJavac
+# > Task :wear:wear-watchface:testDebugUnitTest
+System\.logW\: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable\: Explicit termination method \'dispose\' not called
+System\.logW\: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable\: Explicit termination method \'release\' not called
# > Task :benchmark:benchmark-perfetto:mergeDebugAndroidTestJavaResource
More than one file was found with OS independent path '.*'\. This version of the Android Gradle Plugin chooses the file from the app or dynamic\-feature module, but this can cause unexpected behavior or errors at runtime\. Future versions of the Android Gradle Plugin will throw an error in this case\.
# > Task :docs-runner:dokkaJavaTipOfTreeDocs
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 146b6ca..e134e44 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -35,6 +35,7 @@
docs(project(":camera:camera-lifecycle"))
docs(project(":camera:camera-view"))
docs(project(":car:app:app"))
+ docs(project(":car:app:app-aaos"))
docs(project(":cardview:cardview"))
docs(project(":collection:collection"))
docs(project(":collection:collection-ktx"))
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
index 04ce980..0008abd 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
@@ -230,9 +230,9 @@
Animation anim = Preconditions.checkNotNull(
Preconditions.checkNotNull(animationInfo.getAnimation(context)).animation);
Operation.State finalState = operation.getFinalState();
- if (finalState == Operation.State.VISIBLE) {
- // If we've moving to VISIBLE, we can't use a AnimationSet
- // due that causing the introduction of visual artifacts (b/163084315).
+ if (finalState != Operation.State.REMOVED) {
+ // If the operation does not remove the view, we can't use a
+ // AnimationSet due that causing the introduction of visual artifacts (b/163084315).
viewToAnimate.startAnimation(anim);
// This means we can't use setAnimationListener() without overriding
// any listener that the Fragment has set themselves, so we
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
index 8cccb82..e370f53 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
@@ -95,6 +95,10 @@
private static final String SAVED_CANCELABLE = "android:cancelable";
private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
private static final String SAVED_BACK_STACK_ID = "android:backStackId";
+ /**
+ * Copied from {@link Dialog}.
+ */
+ private static final String SAVED_INTERNAL_DIALOG_SHOWING = "android:dialogShowing";
private Handler mHandler;
private Runnable mDismissRunnable = new Runnable() {
@@ -691,6 +695,7 @@
super.onSaveInstanceState(outState);
if (mDialog != null) {
Bundle dialogState = mDialog.onSaveInstanceState();
+ dialogState.putBoolean(SAVED_INTERNAL_DIALOG_SHOWING, false);
outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
}
if (mStyle != STYLE_NORMAL) {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 64fa88e..99bf534 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -3305,7 +3305,7 @@
RouteInfo route = new RouteInfo(provider, groupId, uniqueId);
route.maybeUpdateDescriptor(groupRouteDescriptor);
- if (mSelectedRoute != route) {
+ if (mSelectedRoute == route) {
return;
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.java b/navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.kt
similarity index 91%
rename from navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.java
rename to navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.kt
index f9f0fbb..167dcb1 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.java
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavArgs.kt
@@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package androidx.navigation;
+package androidx.navigation
/**
* An interface marking generated Args classes.
*/
-public interface NavArgs {
-}
+public interface NavArgs
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NoOpNavigator.java b/navigation/navigation-common/src/main/java/androidx/navigation/NoOpNavigator.java
deleted file mode 100644
index a4ed438..0000000
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NoOpNavigator.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2018 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.navigation;
-
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-
-/**
- * A {@link Navigator} that only supports creating destinations.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@Navigator.Name("NoOp")
-public class NoOpNavigator extends Navigator<NavDestination> {
- @NonNull
- @Override
- public NavDestination createDestination() {
- return new NavDestination(this);
- }
-
- @Nullable
- @Override
- public NavDestination navigate(@NonNull NavDestination destination, @Nullable Bundle args,
- @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
- return destination;
- }
-
- @Override
- public boolean popBackStack() {
- return true;
- }
-}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NoOpNavigator.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NoOpNavigator.kt
new file mode 100644
index 0000000..044db7a
--- /dev/null
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NoOpNavigator.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.navigation
+
+import android.os.Bundle
+import androidx.annotation.RestrictTo
+
+/**
+ * A [Navigator] that only supports creating destinations.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Navigator.Name("NoOp")
+public class NoOpNavigator : Navigator<NavDestination>() {
+ override fun createDestination(): NavDestination = NavDestination(this)
+
+ override fun navigate(
+ destination: NavDestination,
+ args: Bundle?,
+ navOptions: NavOptions?,
+ navigatorExtras: Extras?
+ ): NavDestination? = destination
+
+ override fun popBackStack(): Boolean = true
+}
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
index 5367b9f..7c3f3159c 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
@@ -53,7 +53,7 @@
val entryRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
items.forEach { (name, route) ->
BottomNavigationItem(
- icon = { Icon(Icons.Filled.Favorite) },
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(name) },
selected = entryRoute == route,
>
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
index 9e5dc62..1c9b115 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
@@ -861,6 +861,45 @@
.isEqualTo(Lifecycle.State.RESUMED)
}
+ @UiThreadTest
+ @Test
+ fun testLifecycleToDestroyedWhenInitialized() {
+ val navController = createNavController(TestLifecycleOwner(Lifecycle.State.INITIALIZED))
+ val navGraph = navController.navigatorProvider.navigation(
+ id = 1,
+ startDestination = R.id.start_test
+ ) {
+ test(R.id.start_test)
+ test(R.id.second_test)
+ }
+ navController.graph = navGraph
+
+ val startBackStackEntry = navController.getBackStackEntry(R.id.start_test)
+ assertWithMessage("The start destination should be initialized")
+ .that(startBackStackEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.INITIALIZED)
+
+ navController.navigate(R.id.second_test)
+
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
+ assertWithMessage("The new destination should be initialized")
+ .that(secondBackStackEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.INITIALIZED)
+
+ navController.popBackStack()
+
+ assertWithMessage("The popped destination should be initialized")
+ .that(secondBackStackEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.INITIALIZED)
+
+ // Pop the last destination off the stack
+ navController.popBackStack()
+
+ assertWithMessage("The start destination should be initialized after pop")
+ .that(startBackStackEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.INITIALIZED)
+ }
+
private fun createNavController(
lifecycleOwner: LifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
): NavController {
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
index 7c6c27b..afd3cf0 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -324,7 +324,9 @@
for (Navigator<?> navigator : popOperations) {
if (navigator.popBackStack()) {
NavBackStackEntry entry = mBackStack.removeLast();
- entry.setMaxLifecycle(Lifecycle.State.DESTROYED);
+ if (entry.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.CREATED)) {
+ entry.setMaxLifecycle(Lifecycle.State.DESTROYED);
+ }
if (mViewModel != null) {
mViewModel.clear(entry.mId);
}
diff --git a/settings.gradle b/settings.gradle
index 982c21b..cae4b3a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -159,6 +159,7 @@
includeProject(":benchmark:integration-tests:startup-benchmark", "benchmark/integration-tests/startup-benchmark", [BuildType.MAIN])
includeProject(":biometric:biometric", "biometric/biometric", [BuildType.MAIN])
includeProject(":biometric:biometric-ktx", "biometric/biometric-ktx", [BuildType.MAIN])
+includeProject(":biometric:biometric-ktx-samples", "biometric/biometric-ktx/samples", [BuildType.MAIN])
includeProject(":biometric:integration-tests:testapp", "biometric/integration-tests/testapp", [BuildType.MAIN])
includeProject(":browser:browser", "browser/browser", [BuildType.MAIN])
includeProject(":buildSrc-tests", "buildSrc-tests", [BuildType.MAIN])
@@ -186,6 +187,7 @@
includeProject(":camera:integration-tests:camera-testapp-view", "camera/integration-tests/viewtestapp", [BuildType.MAIN])
includeProject(":camera:integration-tests:camera-testlib-extensions", "camera/integration-tests/extensionstestlib", [BuildType.MAIN])
includeProject(":car:app:app", "car/app/app", [BuildType.MAIN])
+includeProject(":car:app:app-aaos", "car/app/app-aaos", [BuildType.MAIN])
includeProject(":cardview:cardview", "cardview/cardview", [BuildType.MAIN])
includeProject(":collection:collection", "collection/collection", [BuildType.MAIN])
includeProject(":collection:collection-benchmark", "collection/collection-benchmark", [BuildType.MAIN])
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
index 5d8e730..29aa15a 100644
--- a/slices/view/build.gradle
+++ b/slices/view/build.gradle
@@ -27,7 +27,7 @@
dependencies {
implementation(project(":slice-core"))
implementation project(":appcompat:appcompat")
- implementation("androidx.recyclerview:recyclerview:1.1.0")
+ implementation("androidx.recyclerview:recyclerview:1.2.0-beta01")
implementation("androidx.collection:collection:1.1.0")
api("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceContent.java b/slices/view/src/main/java/androidx/slice/widget/SliceContent.java
index 6061e64..6dc5983 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceContent.java
@@ -256,7 +256,8 @@
}
if (actionItem == null) {
Intent intent = new Intent();
- PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
+ PendingIntent pi = PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
actionItem = new SliceItem(pi, null, FORMAT_ACTION, null, null);
}
if (shortcutAction != null && shortcutIcon != null && actionItem != null) {
diff --git a/viewpager2/integration-tests/testapp/build.gradle b/viewpager2/integration-tests/testapp/build.gradle
index 8eae4e5..bf00bec 100644
--- a/viewpager2/integration-tests/testapp/build.gradle
+++ b/viewpager2/integration-tests/testapp/build.gradle
@@ -31,7 +31,6 @@
id("AndroidXPlugin")
id("com.android.application")
id("org.jetbrains.kotlin.android")
- id('kotlin-android-extensions')
}
dependencies {
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
index 571b9c4..c5009bf 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
@@ -147,6 +147,31 @@
/**
* This test should have an equivalent in CTS when this is implemented in the framework.
+ */
+ @Test
+ public void testReverseBypass() throws Exception {
+ WebkitUtils.checkFeature(WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS);
+
+ final String contentUrl = "http://www.example.com";
+ final String bypassUrl = "www.example.com";
+ int proxyServerRequestCount = mProxyServer.getRequestCount();
+
+ // Set proxy override with reverse bypass and load content url
+ // The content url (in the bypass list) should use proxy settings.
+ setProxyOverrideSync(new ProxyConfig.Builder()
+ .addProxyRule(mProxyServer.getHostName() + ":" + mProxyServer.getPort())
+ .addBypassRule(bypassUrl)
+ .setReverseBypass(true)
+ .build());
+ mWebViewOnUiThread.loadUrl(contentUrl);
+
+ proxyServerRequestCount++;
+ assertNotNull(mProxyServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount());
+ }
+
+ /**
+ * This test should have an equivalent in CTS when this is implemented in the framework.
*
* Enumerates valid patterns to check they are supported.
*/
diff --git a/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java b/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java
index 1b62fc9..3fc5e78 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java
@@ -17,6 +17,7 @@
package androidx.webkit;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresFeature;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef;
@@ -68,14 +69,17 @@
private List<ProxyRule> mProxyRules;
private List<String> mBypassRules;
+ private boolean mReverseBypass;
/**
* @hide Internal use only
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- public ProxyConfig(@NonNull List<ProxyRule> proxyRules, @NonNull List<String> bypassRules) {
+ public ProxyConfig(@NonNull List<ProxyRule> proxyRules, @NonNull List<String> bypassRules,
+ boolean reverseBypass) {
mProxyRules = proxyRules;
mBypassRules = bypassRules;
+ mReverseBypass = reverseBypass;
}
/**
@@ -106,6 +110,17 @@
}
/**
+ * @return reverseBypass
+ *
+ * TODO(laisminchillo): unhide this when we're ready to expose this
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public boolean isReverseBypass() {
+ return mReverseBypass;
+ }
+
+ /**
* Class that holds a scheme filter and a proxy URL.
*/
public static final class ProxyRule {
@@ -162,6 +177,7 @@
public static final class Builder {
private List<ProxyRule> mProxyRules;
private List<String> mBypassRules;
+ private boolean mReverseBypass = false;
/**
* Create an empty ProxyConfig Builder.
@@ -177,6 +193,7 @@
public Builder(@NonNull ProxyConfig proxyConfig) {
mProxyRules = proxyConfig.getProxyRules();
mBypassRules = proxyConfig.getBypassRules();
+ mReverseBypass = proxyConfig.isReverseBypass();
}
/**
@@ -186,7 +203,7 @@
*/
@NonNull
public ProxyConfig build() {
- return new ProxyConfig(proxyRules(), bypassRules());
+ return new ProxyConfig(proxyRules(), bypassRules(), reverseBypass());
}
/**
@@ -316,6 +333,28 @@
return addBypassRule(BYPASS_RULE_REMOVE_IMPLICIT);
}
+ /**
+ * Reverse the bypass list, so only URLs in the bypass list will use these proxy settings.
+ *
+ * <p>
+ * This method should only be called if
+ * {@link WebViewFeature#isFeatureSupported(String)}
+ * returns {@code true} for {@link WebViewFeature#PROXY_OVERRIDE_REVERSE_BYPASS}.
+ *
+ * @return This Builder object
+ *
+ * TODO(laisminchillo): unhide this when we're ready to expose this
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @RequiresFeature(name = WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS,
+ enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+ @NonNull
+ public Builder setReverseBypass(boolean reverseBypass) {
+ mReverseBypass = reverseBypass;
+ return this;
+ }
+
@NonNull
private List<ProxyRule> proxyRules() {
return mProxyRules;
@@ -325,5 +364,9 @@
private List<String> bypassRules() {
return mBypassRules;
}
+
+ private boolean reverseBypass() {
+ return mReverseBypass;
+ }
}
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 55916b9..1bb99540 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -93,6 +93,7 @@
FORCE_DARK_STRATEGY,
WEB_MESSAGE_LISTENER,
DOCUMENT_START_SCRIPT,
+ PROXY_OVERRIDE_REVERSE_BYPASS,
})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -454,6 +455,16 @@
public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
/**
+ * Feature for {@link #isFeatureSupported(String)}.
+ * This feature covers {@link androidx.webkit.ProxyConfig.Builder.setReverseBypass(boolean)}
+ *
+ * TODO(laisminchillo): unhide when ready.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
+
+ /**
* Return whether a feature is supported at run-time. On devices running Android version {@link
* android.os.Build.VERSION_CODES#LOLLIPOP} and higher, this will check whether a feature is
* supported, depending on the combination of the desired feature, the Android version of
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
index 36b5074a..5d7174f 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
@@ -36,13 +36,23 @@
@Override
public void setProxyOverride(@NonNull ProxyConfig proxyConfig, @NonNull Executor executor,
@NonNull Runnable listener) {
- WebViewFeatureInternal feature = WebViewFeatureInternal.PROXY_OVERRIDE;
- if (feature.isSupportedByWebView()) {
- // A 2D String array representation is required by reflection
- String[][] proxyRuleArray = proxyRulesToStringArray(proxyConfig.getProxyRules());
- String[] bypassRuleArray = proxyConfig.getBypassRules().toArray(new String[0]);
+ WebViewFeatureInternal proxyOverride = WebViewFeatureInternal.PROXY_OVERRIDE;
+ WebViewFeatureInternal reverseBypass = WebViewFeatureInternal.PROXY_OVERRIDE_REVERSE_BYPASS;
+
+ // A 2D String array representation is required by reflection
+ String[][] proxyRuleArray = proxyRulesToStringArray(proxyConfig.getProxyRules());
+ String[] bypassRuleArray = proxyConfig.getBypassRules().toArray(new String[0]);
+
+ if (proxyOverride.isSupportedByWebView() && !proxyConfig.isReverseBypass()) {
getBoundaryInterface().setProxyOverride(
proxyRuleArray, bypassRuleArray, listener, executor);
+ } else if (proxyOverride.isSupportedByWebView() && reverseBypass.isSupportedByWebView()) {
+ getBoundaryInterface().setProxyOverride(
+ proxyRuleArray,
+ bypassRuleArray,
+ listener,
+ executor,
+ proxyConfig.isReverseBypass());
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index 2bec18d..7ff7120 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -402,6 +402,12 @@
*/
DOCUMENT_START_SCRIPT(WebViewFeature.DOCUMENT_START_SCRIPT, Features.DOCUMENT_START_SCRIPT),
+ /**
+ * This feature covers {@link androidx.webkit.ProxyConfig.Builder.setReverseBypass(boolean)}
+ */
+ PROXY_OVERRIDE_REVERSE_BYPASS(WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS,
+ Features.PROXY_OVERRIDE_REVERSE_BYPASS),
+
; // This semicolon ends the enum. Add new features with a trailing comma above this line.
private static final int NOT_SUPPORTED_BY_FRAMEWORK = -1;
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java b/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
index 8bc1ba3..11ad72a 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
@@ -142,8 +142,8 @@
assertTrue(actual.getDisplayFeatures().isEmpty());
}
-
- @Test
+ // TODO(b/175507310): Reinstate after fix.
+ // @Test
@Override
public void testTranslateWindowLayoutInfo_filterRemovesHingeFeatureNotSpanningFullDimension() {
List<SidecarDisplayFeature> sidecarDisplayFeatures = new ArrayList<>();
@@ -167,7 +167,8 @@
assertTrue(actual.getDisplayFeatures().isEmpty());
}
- @Test
+ // TODO(b/175507310): Reinstate after fix.
+ // @Test
@Override
public void testTranslateWindowLayoutInfo_filterRemovesFoldFeatureNotSpanningFullDimension() {
List<SidecarDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
diff --git a/window/window/src/main/java/androidx/window/SidecarAdapter.java b/window/window/src/main/java/androidx/window/SidecarAdapter.java
index 439921e..c245f31 100644
--- a/window/window/src/main/java/androidx/window/SidecarAdapter.java
+++ b/window/window/src/main/java/androidx/window/SidecarAdapter.java
@@ -215,6 +215,7 @@
* with the value passed from extension.
*/
@Nullable
+ @SuppressWarnings("UnusedVariable") // TODO(b/175507310): Remove after fix.
private static DisplayFeature translate(SidecarDisplayFeature feature,
SidecarDeviceState deviceState, Rect windowBounds) {
Rect bounds = feature.getRect();
@@ -238,8 +239,9 @@
}
if (feature.getType() == SidecarDisplayFeature.TYPE_HINGE
|| feature.getType() == SidecarDisplayFeature.TYPE_FOLD) {
- if (!((bounds.left == 0 && bounds.right == windowBounds.width())
- || (bounds.top == 0 && bounds.bottom == windowBounds.height()))) {
+ // TODO(b/175507310): Reinstate after fix on the OEM side.
+ if (!((bounds.left == 0/* && bounds.right == windowBounds.width()*/)
+ || (bounds.top == 0/* && bounds.bottom == windowBounds.height()*/))) {
// Bounds for fold and hinge types are expected to span the entire window space.
// See DisplayFeature#getBounds().
if (DEBUG) {