Merge "Use new JvmDefaultMode values for Kotlin 1.6.20" into androidx-main
diff --git a/activity/activity-compose/src/main/java/androidx/activity/compose/ComponentActivity.kt b/activity/activity-compose/src/main/java/androidx/activity/compose/ComponentActivity.kt
index 3e09abe..df7910f 100644
--- a/activity/activity-compose/src/main/java/androidx/activity/compose/ComponentActivity.kt
+++ b/activity/activity-compose/src/main/java/androidx/activity/compose/ComponentActivity.kt
@@ -23,7 +23,8 @@
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
/**
* Composes the given composable into the given activity. The [content] will become the root view
@@ -85,7 +86,7 @@
if (ViewTreeViewModelStoreOwner.get(decorView) == null) {
ViewTreeViewModelStoreOwner.set(decorView, this)
}
- if (ViewTreeSavedStateRegistryOwner.get(decorView) == null) {
- ViewTreeSavedStateRegistryOwner.set(decorView, this)
+ if (decorView.findViewTreeSavedStateRegistryOwner() == null) {
+ decorView.setViewTreeSavedStateRegistryOwner(this)
}
}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
index 830c798..1e69ec6 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
@@ -23,7 +23,7 @@
import androidx.activity.test.R
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -59,7 +59,7 @@
.that(ViewTreeViewModelStoreOwner.get(inflatedTextView))
.isSameInstanceAs(this@withActivity)
assertWithMessage("inflated view has correct ViewTreeSavedStateRegistryOwner")
- .that(ViewTreeSavedStateRegistryOwner.get(inflatedTextView))
+ .that(inflatedTextView.findViewTreeSavedStateRegistryOwner())
.isSameInstanceAs(this@withActivity)
}
}
@@ -95,7 +95,7 @@
override fun onViewAttachedToWindow(v: View?) {
attachedLifecycleOwner = ViewTreeLifecycleOwner.get(view)
attachedViewModelStoreOwner = ViewTreeViewModelStoreOwner.get(view)
- attachedSavedStateRegistryOwner = ViewTreeSavedStateRegistryOwner.get(view)
+ attachedSavedStateRegistryOwner = view.findViewTreeSavedStateRegistryOwner()
}
})
attach(view)
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatActivityViewTreeTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatActivityViewTreeTest.kt
index 9571b75..a2c78463 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatActivityViewTreeTest.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatActivityViewTreeTest.kt
@@ -18,7 +18,7 @@
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -49,7 +49,7 @@
@Test
fun queryViewTreeSavedStateRegistryTest() {
- val ssrOwner = ViewTreeSavedStateRegistryOwner.get(activityRule.activity.window.decorView)
+ val ssrOwner = activityRule.activity.window.decorView.findViewTreeSavedStateRegistryOwner()
assertThat(ssrOwner).isEqualTo(activityRule.activity)
}
}
\ No newline at end of file
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index 1e19fec..9874274 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -98,6 +98,11 @@
ctor public PowerMetric();
}
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class PowerRail {
+ method public boolean hasMetrics(optional boolean throwOnMissingMetrics);
+ field public static final androidx.benchmark.macro.PowerRail INSTANCE;
+ }
+
public enum StartupMode {
enum_constant public static final androidx.benchmark.macro.StartupMode COLD;
enum_constant public static final androidx.benchmark.macro.StartupMode HOT;
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt
new file mode 100644
index 0000000..476ac59
--- /dev/null
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.macro
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Assume.assumeTrue
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PowerRailTest {
+
+ @Test
+ fun hasMetrics_Pixel6() {
+ assumeTrue(Build.VERSION.SDK_INT > 31 && Build.MODEL.lowercase() == "oriole")
+
+ assertTrue(PowerRail.hasMetrics(throwOnMissingMetrics = true))
+ assertTrue(PowerRail.hasMetrics(throwOnMissingMetrics = false))
+ }
+
+ @Test
+ fun hasMetrics_false() {
+ assumeTrue(Build.VERSION.SDK_INT < 29)
+
+ assertFailsWith<UnsupportedOperationException> {
+ PowerRail.hasMetrics(throwOnMissingMetrics = true)
+ }
+
+ assertFalse(
+ PowerRail.hasMetrics(throwOnMissingMetrics = false)
+ )
+ }
+}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index 3fa18d7..f5ad12a 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -135,7 +135,8 @@
absolutePath: String,
tsAbsolutePath: String
): String {
- val relativePath = Outputs.relativePathFor(absolutePath)
+ // Link to a path with timestamp to prevent studio from caching the file
+ val relativePath = Outputs.relativePathFor(tsAbsolutePath)
.replace("(", "\\(")
.replace(")", "\\)")
@@ -144,6 +145,6 @@
Baseline profile [results](file://$relativePath)
To copy the profile use:
- adb pull "$tsAbsolutePath" .
+ adb pull "$absolutePath" .
""".trimIndent()
}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index aca7f19..821fc6c 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -310,8 +310,6 @@
setupBlock = {
if (startupMode == StartupMode.COLD) {
killProcess()
- // drop app pages from page cache to ensure it is loaded from disk, from scratch
- dropKernelPageCache()
// Clear profile caches when possible.
// Benchmarks get faster over time as ART can create profiles for future
@@ -332,6 +330,15 @@
throw IllegalStateException("block never used for CompilationMode.None")
}
}
+ // drop app pages from page cache to ensure it is loaded from disk, from scratch
+
+ // resetAndCompile uses ProfileInstallReceiver to write a skip file.
+ // This is done to reduce the interference from ProfileInstaller,
+ // so long-running benchmarks don't get optimized due to a background dexopt.
+
+ // To restore the state of the process we need to drop app pages so its
+ // loaded from disk, from scratch.
+ dropKernelPageCache()
} else if (iteration == 0 && startupMode != null) {
try {
iteration = null // override to null for warmup, before starting measurements
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index ff29788..381a95c 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -21,6 +21,7 @@
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.benchmark.Shell
+import androidx.benchmark.macro.PowerRail.hasMetrics
import androidx.benchmark.macro.perfetto.AudioUnderrunQuery
import androidx.benchmark.macro.perfetto.EnergyQuery
import androidx.benchmark.macro.perfetto.FrameTimingQuery
@@ -382,7 +383,9 @@
internal const val MEASURE_BLOCK_SECTION_NAME = "measureBlock"
}
- internal override fun configure(packageName: String) {}
+ internal override fun configure(packageName: String) {
+ hasMetrics(throwOnMissingMetrics = true)
+ }
internal override fun start() {}
@@ -432,7 +435,9 @@
internal const val MEASURE_BLOCK_SECTION_NAME = "measureBlock"
}
- internal override fun configure(packageName: String) {}
+ internal override fun configure(packageName: String) {
+ hasMetrics(throwOnMissingMetrics = true)
+ }
internal override fun start() {}
@@ -482,7 +487,9 @@
internal const val MEASURE_BLOCK_SECTION_NAME = "measureBlock"
}
- internal override fun configure(packageName: String) {}
+ internal override fun configure(packageName: String) {
+ hasMetrics(throwOnMissingMetrics = true)
+ }
internal override fun start() {}
@@ -533,7 +540,9 @@
internal const val MEASURE_BLOCK_SECTION_NAME = "measureBlock"
}
- internal override fun configure(packageName: String) {}
+ internal override fun configure(packageName: String) {
+ hasMetrics(throwOnMissingMetrics = true)
+ }
internal override fun start() {}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt
new file mode 100644
index 0000000..4a18f72
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.macro
+
+import androidx.annotation.RestrictTo
+import androidx.benchmark.Shell
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+object PowerRail {
+
+ private const val commandHal2 = "dumpsys android.hardware.power.stats.IPowerStats/default delta"
+ private const val commandHal1 =
+ "lshal debug android.hardware.power.stats@1.0::IPowerStats/default delta"
+
+ private const val hal2Header = "============= PowerStats HAL 2.0 energy meter =============="
+ private const val hal1Header =
+ "============= PowerStats HAL 1.0 rail energy data =============="
+
+ /**
+ * Checks if rail metrics are generated on specified device.
+ *
+ * @Throws UnsupportedOperationException if `hasException == true` and no rail metrics are found.
+ */
+ fun hasMetrics(throwOnMissingMetrics: Boolean = false): Boolean {
+ val resultHal2 = Shell.executeCommand(commandHal2)
+ val resultHal1 = Shell.executeCommand(commandHal1)
+
+ if ((resultHal2.contains(hal2Header)) || (resultHal1.contains(hal1Header))) {
+ return true
+ }
+ if (throwOnMissingMetrics) {
+ throw UnsupportedOperationException(
+ """
+ Rail metrics are not available on this device.
+ To check a device for power/energy measurement support, it must output rail metrics
+ for one of the following commands:
+
+ adb shell $commandHal2
+ adb shell $commandHal1
+
+ To check at runtime for this, use PowerRail.hasMetrics()
+
+ """.trimIndent()
+ )
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialPowerBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialPowerBenchmark.kt
index f211def..638830c 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialPowerBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialPowerBenchmark.kt
@@ -17,11 +17,11 @@
package androidx.benchmark.integration.macrobenchmark
import android.content.Intent
-import androidx.benchmark.Shell
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.EnergyMetric
import androidx.benchmark.macro.ExperimentalMetricApi
import androidx.benchmark.macro.PowerMetric
+import androidx.benchmark.macro.PowerRail
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.TotalEnergyMetric
import androidx.benchmark.macro.TotalPowerMetric
@@ -45,7 +45,7 @@
@Test
fun measureEnergyPower() {
- assumeTrue(hasRailMetrics())
+ assumeTrue(PowerRail.hasMetrics())
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
metrics = listOf(PowerMetric(), EnergyMetric()),
@@ -64,7 +64,7 @@
@Test
fun measureEnergyPowerMultiple() {
- assumeTrue(hasRailMetrics())
+ assumeTrue(PowerRail.hasMetrics())
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
metrics = listOf(PowerMetric(), EnergyMetric()),
@@ -98,7 +98,7 @@
@Test
fun measureTotalEnergyPower() {
- assumeTrue(hasRailMetrics())
+ assumeTrue(PowerRail.hasMetrics())
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
metrics = listOf(TotalEnergyMetric(), TotalPowerMetric()),
@@ -117,7 +117,7 @@
@Test
fun measureTotalEnergyPowerMultiple() {
- assumeTrue(hasRailMetrics())
+ assumeTrue(PowerRail.hasMetrics())
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
metrics = listOf(TotalPowerMetric(), TotalEnergyMetric()),
@@ -149,25 +149,6 @@
}
}
- private fun hasRailMetrics(): Boolean {
- var commandHal2 = "dumpsys android.hardware.power.stats.IPowerStats/default delta"
- var commandHal1 = "lshal debug android.hardware.power.stats@1.0::IPowerStats/default delta"
- var resultHal2 = Shell.executeCommand(commandHal2)
- var resultHal1 = Shell.executeCommand(commandHal1)
- if ((resultHal2.isEmpty()) && (resultHal1.isEmpty())) {
- throw UnsupportedOperationException("""
- Rail metrics are not available on this device. To check a device for
- power/energy measurement support, it must output something for one of
- the following commands:
-
- adb shell $commandHal2
- adb shell $commandHal1
-
- """.trimIndent())
- }
- return true
- }
-
companion object {
private const val PACKAGE_NAME = "androidx.benchmark.integration.macrobenchmark.target"
private const val ACTION = "$PACKAGE_NAME.TRIVIAL_STARTUP_ACTIVITY"
diff --git a/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java b/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
index 5f22b16..7dfa19d 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
@@ -65,9 +65,14 @@
@Config(minSdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsSuccess_WhenManagerReturnsSuccess_OnApi29AndAbove() {
+ final int authenticators = Authenticators.BIOMETRIC_WEAK;
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
mock(android.hardware.biometrics.BiometricManager.class);
when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_SUCCESS);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ when(frameworkBiometricManager.canAuthenticate(authenticators))
+ .thenReturn(BIOMETRIC_SUCCESS);
+ }
when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
@@ -80,17 +85,21 @@
.setFingerprintHardwarePresent(true)
.build());
- final int authenticators = Authenticators.BIOMETRIC_WEAK;
assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = 29)
+ @Config(minSdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenManagerReturnsNoneEnrolled_OnApi29AndAbove() {
+ final int authenticators = Authenticators.BIOMETRIC_WEAK;
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
mock(android.hardware.biometrics.BiometricManager.class);
when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NONE_ENROLLED);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ when(frameworkBiometricManager.canAuthenticate(authenticators))
+ .thenReturn(BIOMETRIC_ERROR_NONE_ENROLLED);
+ }
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
@@ -103,18 +112,22 @@
.setFingerprintHardwarePresent(true)
.build());
- final int authenticators = Authenticators.BIOMETRIC_WEAK;
assertThat(biometricManager.canAuthenticate(authenticators))
.isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = 29)
+ @Config(minSdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenManagerReturnsNoHardware_OnApi29AndAbove() {
+ final int authenticators = Authenticators.BIOMETRIC_WEAK;
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
mock(android.hardware.biometrics.BiometricManager.class);
when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ when(frameworkBiometricManager.canAuthenticate(authenticators))
+ .thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+ }
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
@@ -127,13 +140,12 @@
.setFingerprintHardwarePresent(true)
.build());
- 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)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenCombinationNotSupported_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -176,7 +188,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenNoAuthenticatorsAllowed_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -215,7 +227,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenDeviceNotSecurable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -254,7 +266,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenUnsecured_BiometricOnly_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -294,7 +306,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsError_WhenUnsecured_CredentialAllowed_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -334,7 +346,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsSuccess_WhenDeviceCredentialAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -370,7 +382,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticateStrong_ReturnsSuccess_WhenStrongBiometricGuaranteed_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -394,7 +406,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticateStrong_ReturnsError_WhenWeakUnavailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -418,7 +430,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticateStrong_ReturnsSuccess_WhenFingerprintAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -459,7 +471,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testCanAuthenticate_ReturnsUnknown_WhenFingerprintUnavailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -484,7 +496,7 @@
@Test
- @Config(minSdk = Build.VERSION_CODES.P, maxSdk = Build.VERSION_CODES.P)
+ @Config(sdk = Build.VERSION_CODES.P)
public void testCanAuthenticate_ReturnsUnknown_WhenFingerprintUnavailable_OnApi28() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
@@ -503,7 +515,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.P, maxSdk = Build.VERSION_CODES.P)
+ @Config(sdk = Build.VERSION_CODES.P)
public void testCanAuthenticate_ReturnsError_WhenNoFingerprintHardware_OnApi28() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
@@ -571,7 +583,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+ @Config(sdk = Build.VERSION_CODES.R)
@RequiresApi(Build.VERSION_CODES.R)
public void testGetStrings_WhenFingerprintAvailable_OnApi30() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -630,7 +642,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+ @Config(sdk = Build.VERSION_CODES.R)
@RequiresApi(Build.VERSION_CODES.R)
public void testGetStrings_WhenFaceAvailable_OnApi30() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -689,7 +701,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+ @Config(sdk = Build.VERSION_CODES.R)
@RequiresApi(Build.VERSION_CODES.R)
public void testGetStrings_WhenIrisAvailable_OnApi30() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -748,7 +760,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+ @Config(sdk = Build.VERSION_CODES.R)
@RequiresApi(Build.VERSION_CODES.R)
public void testGetStrings_WhenFingerprintAndFaceAvailable_OnApi30() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -807,7 +819,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+ @Config(sdk = Build.VERSION_CODES.R)
@RequiresApi(Build.VERSION_CODES.R)
public void testGetStrings_WhenBiometricsPresentButNotAvailable_OnApi30() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -856,7 +868,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+ @Config(sdk = Build.VERSION_CODES.R)
@RequiresApi(Build.VERSION_CODES.R)
public void testGetStrings_WhenOnlyScreenLockAvailable_OnApi30() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -905,7 +917,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testGetStrings_WhenFingerprintAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -946,7 +958,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testGetStrings_WhenFaceAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -989,7 +1001,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testGetStrings_WhenIrisAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -1032,7 +1044,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testGetStrings_WhenFingerprintAndFaceAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -1075,7 +1087,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testGetStrings_WhenBiometricsPresentButNotAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
@@ -1108,7 +1120,7 @@
}
@Test
- @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+ @Config(sdk = Build.VERSION_CODES.Q)
@RequiresApi(Build.VERSION_CODES.Q)
public void testGetStrings_WhenOnlyScreenLockAvailable_OnApi29() {
final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
diff --git a/build.gradle b/build.gradle
index 4f878c1..99a5690 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,19 +19,9 @@
buildscript {
SdkHelperKt.setSupportRootFolder(project, project.projectDir)
-
- apply from: "buildSrc/repos.gradle"
- repos.addMavenRepositories(repositories)
-
SdkHelperKt.writeSdkPathToLocalPropertiesFile(project)
}
-def root = project
-repos.addMavenRepositories(root.repositories)
-subprojects { subproject ->
- subproject.repositories.addAll(root.repositories)
-}
-
apply from: "buildSrc/dependencies.gradle"
apply from: "buildSrc/out.gradle"
init.chooseOutDir()
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index f923aa1..0d83485 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -18,6 +18,7 @@
// This project is stored outside of buildSrc/ so that waiting for these tests to complete doesn't delay the rest of the build
import androidx.build.BuildServerConfigurationKt
+import androidx.build.SdkResourceGenerator
plugins {
id("AndroidXPlugin")
@@ -26,8 +27,6 @@
dependencies {
implementation(gradleApi())
- testImplementation(libs.junit)
- testImplementation(libs.truth)
implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/private/build/libs/private.jar")))
implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/public/build/libs/public.jar")))
implementation("com.googlecode.json-simple:json-simple:1.1")
@@ -36,8 +35,15 @@
// Optional dependency where Ivy fails to parse the POM file.
exclude(group:"net.java.dev.msv", module:"xsdlib")
}
+
+ testImplementation(libs.junit)
+ testImplementation(libs.truth)
+ testImplementation(project(":internal-testutils-gradle-plugin"))
+ testImplementation(gradleTestKit())
}
+SdkResourceGenerator.generateForHostTest(project)
+
// Also do style checking of the buildSrc project from within this project
// We run that from this project so that it doesn't block other projects while it runs
def ktlintDir = file("../buildSrc")
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXRootPluginTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXRootPluginTest.kt
new file mode 100644
index 0000000..582361e
--- /dev/null
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXRootPluginTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import androidx.testutils.gradle.ProjectSetupRule
+import java.io.File
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class AndroidXRootPluginTest {
+ @Test
+ fun rootProjectConfigurationHasAndroidXTasks() {
+ TemporaryFolder().wrap { tmpFolder ->
+ ProjectSetupRule().wrap { setup ->
+ val props = setup.props
+
+ fun buildSrcFile(path: String) =
+ File(props.tipOfTreeMavenRepoPath, "../../../buildSrc/$path")
+
+ val privateJar = buildSrcFile("private/build/libs/private.jar")
+ val publicJar = buildSrcFile("public/build/libs/public.jar")
+
+ val settingsGradleText = """
+ pluginManagement {
+ ${setup.repositories}
+ }
+
+ dependencyResolutionManagement {
+ versionCatalogs {
+ libs {
+ from(files("${props.rootProjectPath}/gradle/libs.versions.toml"))
+ }
+ }
+ }
+ """.trimIndent()
+
+ File(setup.rootDir, "settings.gradle").writeText(settingsGradleText)
+
+ val buildGradleText = """
+ buildscript {
+ project.ext.outDir = file("${tmpFolder.newFolder().path}")
+ project.ext.supportRootFolder = file("${tmpFolder.newFolder().path}")
+
+ ${setup.repositories}
+
+ dependencies {
+ classpath(project.files("${privateJar.path}"))
+ classpath(project.files("${publicJar.path}"))
+ classpath '${props.agpDependency}'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:${props.kotlinVersion}'
+ }
+ }
+
+ apply plugin: androidx.build.AndroidXRootImplPlugin
+ """.trimIndent()
+
+ File(setup.rootDir, "build.gradle").writeText(buildGradleText)
+ Assert.assertTrue(privateJar.path, privateJar.exists())
+ val output = GradleRunner.create().withProjectDir(setup.rootDir)
+ .withArguments("tasks", "--stacktrace").build().output
+ Assert.assertTrue(
+ output,
+ output.contains("listAndroidXProperties - Lists AndroidX-specific properties")
+ )
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * JUnit 4 [TestRule]s are traditionally added to a test class as public JVM fields
+ * with a @[org.junit.Rule] annotation. This works decently in Java, but has drawbacks,
+ * such as requiring all methods in a test class to be subject to the same [TestRule]s, and
+ * making it difficult to configure [TestRule]s in different ways between test methods.
+ * With lambdas, objects that have been built as [TestRule] can use this extension function
+ * to allow per-method custom application.
+ */
+ private fun <T : TestRule> T.wrap(fn: (T) -> Unit) = apply(object : Statement() {
+ override fun evaluate() = fn(this@wrap)
+ }, Description.EMPTY).evaluate()
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index f58237b..564b0a8 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -19,36 +19,18 @@
import androidx.build.dependencyTracker.AffectedModuleDetector
import com.android.build.api.dsl.Lint
import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
-import com.android.build.gradle.internal.lint.AndroidLintTextOutputTask
-import com.google.common.io.Files
import java.io.File
import java.util.Locale
-import org.gradle.api.DefaultTask
import org.gradle.api.Project
-import org.gradle.api.file.RegularFileProperty
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.getByType
/**
- * Setting this property means that lint will update lint-baseline.xml if it exists.
- */
-private const val UPDATE_LINT_BASELINE = "updateLintBaseline"
-
-/**
* Name of the service we use to limit the number of concurrent executions of lint
*/
public const val LINT_SERVICE_NAME = "androidxLintService"
-/**
- * Property used by Lint to continue creating baselines without failing lint, normally set by:
- * -Dlint.baselines.continue=true from command line.
- */
-private const val LINT_BASELINE_CONTINUE = "lint.baselines.continue"
-
// service for limiting the number of concurrent lint tasks
interface AndroidXLintService : BuildService<BuildServiceParameters.None>
@@ -168,44 +150,16 @@
// it contains expected violations that we do not want to trigger a build failure
val isTestingLintItself = (project.path == ":lint-checks:integration-tests")
- // If -PupdateLintBaseline was set we should update the baseline if it exists
- val updateLintBaseline = project.providers.gradleProperty(UPDATE_LINT_BASELINE).isPresent &&
- !isTestingLintItself
-
lint.apply {
// Skip lintVital tasks on assemble. We explicitly run lintRelease for libraries.
checkReleaseBuilds = false
}
- // task to copy autogenerated baselines back into the source tree
- val replaceLintBaselineTask = project.tasks.register(
- "replaceLintBaseline",
- UpdateBaselineTask::class.java,
- ) { copyTask ->
- copyTask.source.set(generatedLintBaseline)
- copyTask.dest.set(lintBaseline)
- }
-
- val projectGeneratedLintBaseline = generatedLintBaseline
-
tasks.withType(AndroidLintAnalysisTask::class.java).configureEach { task ->
// don't run too many copies of lint at once due to memory limitations
task.usesService(
task.project.gradle.sharedServices.registrations.getByName(LINT_SERVICE_NAME).service
)
- if (updateLintBaseline) {
- // Remove the generated baseline before running lint, so that lint won't try to use it
- task.doFirst {
- if (projectGeneratedLintBaseline.exists()) {
- projectGeneratedLintBaseline.delete()
- }
- }
- }
- }
- tasks.withType(AndroidLintTextOutputTask::class.java).configureEach { task ->
- if (updateLintBaseline) {
- task.finalizedBy(replaceLintBaselineTask)
- }
}
// Lint is configured entirely in finalizeDsl so that individual projects cannot easily
@@ -326,17 +280,6 @@
lintConfig = File(project.getSupportRootFolder(), lintXmlPath)
}
- // Ideally, teams aren't able to add new violations to a baseline file; they should only
- // be able to burn down existing violations. That's hard to enforce, though, so we'll
- // generally allow teams to update their baseline files with a publicly-known flag.
- if (updateLintBaseline) {
- // Continue generating baselines regardless of errors.
- abortOnError = false
-
- // Avoid printing every single lint error to the terminal.
- textReport = false
- }
-
val lintBaselineFile = lintBaseline.get().asFile
// If we give lint the filepath of a baseline file, then:
// If the file does not exist, lint will write new violations to it
@@ -347,9 +290,7 @@
// If we're not updating the baselines, then we want lint to check for new errors.
// This requires us only pass a baseline to lint if one already exists.
- if (updateLintBaseline) {
- baseline = generatedLintBaseline
- } else if (lintBaselineFile.exists()) {
+ if (lintBaselineFile.exists()) {
baseline = lintBaselineFile
}
}
@@ -357,50 +298,3 @@
val Project.lintBaseline get() =
project.objects.fileProperty().fileValue(File(projectDir, "/lint-baseline.xml"))
-val Project.generatedLintBaseline get() = File(project.buildDir, "/generated-lint-baseline.xml")
-
-/**
- * Task that copies the generated line baseline
- * If an input baseline file has no issues, it is considered to be nonexistent
- */
-abstract class UpdateBaselineTask : DefaultTask() {
- @get:InputFiles // allows missing input file
- abstract val source: RegularFileProperty
-
- @get:OutputFile
- abstract val dest: RegularFileProperty
-
- @TaskAction
- fun copyBaseline() {
- val source = source.get().asFile
- val dest = dest.get().asFile
- val sourceText = if (source.exists()) {
- source.readText()
- } else {
- ""
- }
- var sourceHasIssues =
- if (source.exists()) {
- // Does the baseline contain any issues?
- source.reader().useLines { lines ->
- lines.any { line ->
- line.endsWith("<issue")
- }
- }
- } else {
- false
- }
- val destNonempty = dest.exists()
- val changing = (sourceHasIssues != destNonempty) ||
- (sourceHasIssues && sourceText != dest.readText())
- if (changing) {
- if (sourceHasIssues) {
- Files.copy(source, dest)
- println("Updated baseline file ${dest.path}")
- } else {
- dest.delete()
- println("Deleted baseline file ${dest.path}")
- }
- }
- }
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index 8a6b7a2..3fc8165 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -31,7 +31,7 @@
/**
* Task for verifying the androidx dependency-stability-suffix rule
- * (A library is only as stable as its lease stable dependency)
+ * (A library is only as stable as its least stable dependency)
*/
abstract class VerifyDependencyVersionsTask : DefaultTask() {
@@ -193,6 +193,8 @@
// Don't check any configurations that directly bundle the dependencies with the output
if (name == "bundleInside") return false
+ if (name == "embedThemesDebug") return false
+ if (name == "embedThemesRelease") return false
// Don't check any compile-only configurations
if (name.startsWith("compile")) return false
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index ba334db..71891d3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -568,6 +568,7 @@
project.tasks.whenTaskAdded { task ->
if (task is Test || task.name.startsWith("assemble") ||
task.name == "lint" ||
+ task.name == "lintAnalyzeDebug" ||
task.name == "transformDexArchiveWithExternalLibsDexMergerForPublicDebug" ||
task.name == "transformResourcesWithMergeJavaResForPublicDebug" ||
task.name == "checkPublicDebugDuplicateClasses"
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
index 48197b5..b8ed461 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -20,7 +20,6 @@
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
-import org.gradle.api.plugins.ExtraPropertiesExtension
import java.io.File
object SupportConfig {
@@ -68,9 +67,7 @@
}
fun Project.getPrebuiltsRoot(): File {
- val ext = project.rootProject.property("ext") as ExtraPropertiesExtension
- val reposProperties = ext.get("repos") as Map<*, *>
- return File(reposProperties["prebuiltsRoot"].toString())
+ return File(project.rootProject.property("prebuiltsRoot").toString())
}
/**
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt
index 3efb25f..8bca8b1 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
-import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CaptureRequest
@@ -30,6 +29,9 @@
import android.view.Surface
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
+import androidx.camera.camera2.internal.compat.quirk.CameraQuirks
+import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks
import androidx.camera.camera2.internal.util.RequestProcessorRequest
import androidx.camera.camera2.interop.CaptureRequestOptions
import androidx.camera.core.impl.CameraCaptureFailure
@@ -86,12 +88,13 @@
private var numOfCapturedImages = 0
private val previewImageRetrieved = CompletableDeferred<Unit>()
- private fun getHardwareLevel(cameraId: String): Int {
+ private fun getCameraCharacteristic(cameraId: String): CameraCharacteristicsCompat {
val cameraManager = ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.CAMERA_SERVICE) as CameraManager
- return cameraManager.getCameraCharacteristics(cameraId)
- .get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)!!
+ return CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
+ cameraManager.getCameraCharacteristics(cameraId)
+ )
}
@Before
@@ -104,7 +107,8 @@
mainThreadExecutor as ScheduledExecutorService,
handler,
captureSessionRepository,
- getHardwareLevel(CAMERA_ID)
+ CameraQuirks.get(CAMERA_ID, getCameraCharacteristic(CAMERA_ID)),
+ DeviceQuirks.getAll()
)
cameraDeviceHolder = CameraUtil.getCameraDevice(
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
index 0610617..f3d79dd 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
@@ -37,7 +37,6 @@
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
@@ -57,6 +56,9 @@
import androidx.camera.camera2.internal.CaptureSession.State;
import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.quirk.ConfigureSurfaceToSecondarySessionFailQuirk;
+import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
+import androidx.camera.camera2.internal.compat.quirk.PreviewOrientationIncorrectQuirk;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureCallbacks;
import androidx.camera.core.impl.CameraCaptureResult;
@@ -64,6 +66,7 @@
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.ImmediateSurface;
import androidx.camera.core.impl.MutableOptionsBundle;
+import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
@@ -90,6 +93,7 @@
import org.mockito.Mockito;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -163,7 +167,7 @@
mCaptureSessionOpenerBuilder = new SynchronizedCaptureSessionOpener.Builder(mExecutor,
mScheduledExecutor, mHandler, mCaptureSessionRepository,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
+ new Quirks(new ArrayList<>()), DeviceQuirks.getAll());
mCameraDeviceHolder = CameraUtil.getCameraDevice(
mCaptureSessionRepository.getCameraStateCallback());
@@ -633,9 +637,7 @@
assertThat(captureSession.getState()).isEqualTo(State.OPENED);
SynchronizedCaptureSession syncCaptureSession = captureSession.mSynchronizedCaptureSession;
- assertFutureCompletes(syncCaptureSession.getSynchronizedBlocker(
- SynchronizedCaptureSessionOpener.FEATURE_WAIT_FOR_REQUEST), 5,
- TimeUnit.SECONDS);
+ assertFutureCompletes(syncCaptureSession.getOpeningBlocker(), 5, TimeUnit.SECONDS);
verify(mTestParameters0.mCamera2CaptureCallback, timeout(3000).atLeastOnce())
.onCaptureStarted(any(CameraCaptureSession.class), any(CaptureRequest.class),
@@ -646,7 +648,8 @@
public void surfaceTerminationFutureIsCalledWhenSessionIsClose() throws InterruptedException {
mCaptureSessionOpenerBuilder = new SynchronizedCaptureSessionOpener.Builder(mExecutor,
mScheduledExecutor, mHandler, mCaptureSessionRepository,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
+ new Quirks(Arrays.asList(new PreviewOrientationIncorrectQuirk())),
+ DeviceQuirks.getAll());
CaptureSession captureSession = createCaptureSession();
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
@@ -758,7 +761,8 @@
throws ExecutionException, InterruptedException {
mCaptureSessionOpenerBuilder = new SynchronizedCaptureSessionOpener.Builder(mExecutor,
mScheduledExecutor, mHandler, mCaptureSessionRepository,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
+ new Quirks(Arrays.asList(new ConfigureSurfaceToSecondarySessionFailQuirk())),
+ DeviceQuirks.getAll());
CaptureSession captureSession = createCaptureSession();
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ProcessingCaptureSessionTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ProcessingCaptureSessionTest.kt
index e631b70..a35c78f 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ProcessingCaptureSessionTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ProcessingCaptureSessionTest.kt
@@ -22,8 +22,10 @@
import android.graphics.ImageFormat.YUV_420_888
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.TotalCaptureResult
import android.media.ImageReader
import android.media.ImageWriter
import android.os.Build
@@ -34,6 +36,7 @@
import android.view.Surface
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks
import androidx.camera.camera2.internal.util.RequestProcessorRequest
import androidx.camera.camera2.interop.CaptureRequestOptions
import androidx.camera.core.CameraInfo
@@ -53,6 +56,7 @@
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.SessionProcessorSurface
+import androidx.camera.core.impl.TagBundle
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.CameraUtil.CameraDeviceHolder
@@ -168,7 +172,8 @@
executor as ScheduledExecutorService,
handler,
captureSessionRepository,
- camera2CameraInfo.supportedHardwareLevel
+ camera2CameraInfo.cameraQuirks,
+ DeviceQuirks.getAll()
)
cameraDeviceHolder = CameraUtil.getCameraDevice(
@@ -237,7 +242,7 @@
).awaitWithTimeout(3000)
// Assert
- sessionConfigParameters.assertRepeatingRequestCompleted()
+ sessionConfigParameters.assertRepeatingRequestCompletedWithTags()
sessionConfigParameters.assertPreviewImageReceived()
val parametersConfigSet = sessionProcessor.assertSetParametersInvoked()
@@ -264,7 +269,7 @@
sessionConfigParameters.getActiveSessionConfigForRepeating()
// Assert
- sessionConfigParameters.assertRepeatingRequestCompleted()
+ sessionConfigParameters.assertRepeatingRequestCompletedWithTags()
sessionConfigParameters.assertPreviewImageReceived()
val parametersConfigSet = sessionProcessor.assertSetParametersInvoked()
@@ -760,10 +765,14 @@
private var previewImageReader: ImageReader? = null
private var captureImageReader: ImageReader
private val sessionConfigured = CompletableDeferred<Unit>()
- private val repeatingRequestCompleted = CompletableDeferred<Unit>()
+ private val repeatingRequestCompletedWithTags = CompletableDeferred<Unit>()
private val previewImageReady = CompletableDeferred<Unit>()
private val captureImageReady = CompletableDeferred<Unit>()
private val stillCaptureCompleted = CompletableDeferred<Unit>()
+ private val tagKey1 = "KEY1"
+ private val tagKey2 = "KEY2"
+ private val tagValue1 = "Value1"
+ private val tagValue2 = 99
init {
// Preview
@@ -850,11 +859,27 @@
CaptureRequest.CONTROL_AF_MODE_OFF
).build()
)
- addRepeatingCameraCaptureCallback(object : CameraCaptureCallback() {
- override fun onCaptureCompleted(cameraCaptureResult: CameraCaptureResult) {
- repeatingRequestCompleted.complete(Unit)
- }
- })
+ addRepeatingCameraCaptureCallback(
+ CaptureCallbackContainer.create(object : CaptureCallback() {
+ override fun onCaptureCompleted(
+ session: CameraCaptureSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult
+ ) {
+ if (request.tag !is TagBundle) {
+ return
+ }
+
+ val tagBundle = request.tag as TagBundle
+ if (tagBundle.getTag(tagKey1)!! == tagValue1 &&
+ tagBundle.getTag(tagKey2)!! == tagValue2
+ ) {
+ repeatingRequestCompletedWithTags.complete(Unit)
+ }
+ }
+ }))
+ addTag(tagKey1, tagValue1)
+ addTag(tagKey2, tagValue2)
}.build()
}
@@ -888,8 +913,8 @@
sessionConfigured.awaitWithTimeout(3000)
}
- suspend fun assertRepeatingRequestCompleted() {
- repeatingRequestCompleted.awaitWithTimeout(3000)
+ suspend fun assertRepeatingRequestCompletedWithTags() {
+ repeatingRequestCompletedWithTags.awaitWithTimeout(3000)
}
suspend fun assertPreviewImageReceived() {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 74ea063..c48da217 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -40,6 +40,7 @@
import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
+import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
import androidx.camera.core.CameraState;
import androidx.camera.core.CameraUnavailableException;
import androidx.camera.core.Logger;
@@ -227,7 +228,7 @@
}
mCaptureSessionOpenerBuilder = new SynchronizedCaptureSessionOpener.Builder(mExecutor,
mScheduledExecutorService, schedulerHandler, mCaptureSessionRepository,
- mCameraInfoInternal.getSupportedHardwareLevel());
+ cameraInfoImpl.getCameraQuirks(), DeviceQuirks.getAll());
mCameraAvailability = new CameraAvailability(cameraId);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2RequestProcessor.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2RequestProcessor.java
index fc1dd9c..20c9794 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2RequestProcessor.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2RequestProcessor.java
@@ -27,12 +27,14 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.Logger;
+import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureFailure;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.RequestProcessor;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.SessionProcessorSurface;
+import androidx.camera.core.impl.TagBundle;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
@@ -60,9 +62,13 @@
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class Camera2RequestProcessor implements RequestProcessor {
private static final String TAG = "Camera2RequestProcessor";
+ @NonNull
private final CaptureSession mCaptureSession;
+ @NonNull
private final List<SessionProcessorSurface> mProcessorSurfaces;
private volatile boolean mIsClosed = false;
+ @Nullable
+ private volatile SessionConfig mSessionConfig;
public Camera2RequestProcessor(@NonNull CaptureSession captureSession,
@NonNull List<SessionProcessorSurface> processorSurfaces) {
@@ -79,6 +85,14 @@
mIsClosed = true;
}
+ /**
+ * Update the SessionConfig to get the tags and the capture callback attached to the
+ * repeating request.
+ */
+ public void updateSessionConfig(@Nullable SessionConfig sessionConfig) {
+ mSessionConfig = sessionConfig;
+ }
+
private boolean areRequestsValid(@NonNull List<RequestProcessor.Request> requests) {
for (Request request : requests) {
if (!isRequestValid(request)) {
@@ -154,7 +168,22 @@
sessionConfigBuilder.setTemplateType(request.getTemplateId());
sessionConfigBuilder.setImplementationOptions(request.getParameters());
sessionConfigBuilder.addCameraCaptureCallback(CaptureCallbackContainer.create(
- new Camera2CallbackWrapper(request, callback, true)));
+ new Camera2CallbackWrapper(request, callback, true)));
+
+ if (mSessionConfig != null) {
+ // Attach the CameraX camera capture callback so that CameraControl can get the capture
+ // results it needs.
+ for (CameraCaptureCallback cameraCaptureCallback :
+ mSessionConfig.getRepeatingCameraCaptureCallbacks()) {
+ sessionConfigBuilder.addCameraCaptureCallback(cameraCaptureCallback);
+ }
+
+ // Set the tag (key, value) from CameraX.
+ TagBundle tagBundle = mSessionConfig.getRepeatingCaptureConfig().getTagBundle();
+ for (String key : tagBundle.listKeys()) {
+ sessionConfigBuilder.addTag(key, tagBundle.getTag(key));
+ }
+ }
for (Integer outputConfigId : request.getTargetOutputConfigIds()) {
sessionConfigBuilder.addSurface(findSurface(outputConfigId));
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index 2d40296..203c4dc 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -67,11 +67,7 @@
* (1) Target surfaces specified in {@link #setSessionConfig} and
* {@link #issueCaptureRequests(List)} are ignored. Target surfaces can only be set by
* {@link SessionProcessor}.
- * (2) After {@link #setSessionConfig(SessionConfig)} is invoked,
- * {@link SessionConfig#getRepeatingCameraCaptureCallbacks()} will be invoked but the
- * {@link CameraCaptureResult} doesn't contain camera2
- * {@link android.hardware.camera2.CaptureResult}.
- * (3) {@link #issueCaptureRequests(List)} can only execute {@link CaptureConfig} with
+ * (2) {@link #issueCaptureRequests(List)} can only execute {@link CaptureConfig} with
* CameraDevice.TEMPLATE_STILL_CAPTURE. Others captureConfigs will be cancelled immediately.
* {@link CaptureConfig#getCameraCaptureCallbacks()} will be invoked but the
* {@link CameraCaptureResult} doesn't contain camera2
@@ -531,8 +527,9 @@
return;
}
- mSessionProcessorCaptureCallback
- .setCameraCaptureCallbacks(sessionConfig.getRepeatingCameraCaptureCallbacks());
+ if (mRequestProcessor != null) {
+ mRequestProcessor.updateSessionConfig(sessionConfig);
+ }
if (mProcessorState == ProcessorState.ON_CAPTURE_SESSION_STARTED) {
mSessionOptions =
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java
index 564f627..2c1a74b 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java
@@ -26,7 +26,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.SynchronizedCaptureSessionOpener.SynchronizedSessionFeature;
import androidx.camera.camera2.internal.compat.CameraCaptureSessionCompat;
import com.google.common.util.concurrent.ListenableFuture;
@@ -54,7 +53,7 @@
* @see SynchronizedCaptureSessionOpener
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-interface SynchronizedCaptureSession {
+public interface SynchronizedCaptureSession {
@NonNull
CameraDevice getDevice();
@@ -81,16 +80,11 @@
Surface getInputSurface();
/**
- * Get a {@link ListenableFuture} which indicate the progress of specific task on this
- * SynchronizedCaptureSession.
- *
- * @param feature the key to get the ListenableFuture. The key can be
- * {@link SynchronizedSessionFeature#FEATURE_WAIT_FOR_REQUEST}.
- * @return the ListenableFuture which completes when the specific task is completed.
+ * Get a {@link ListenableFuture} which indicates the task should be finished before another
+ * {@link SynchronizedCaptureSession} to be opened.
*/
@NonNull
- ListenableFuture<Void> getSynchronizedBlocker(
- @SynchronizedSessionFeature @NonNull String feature);
+ ListenableFuture<Void> getOpeningBlocker();
/**
* Return the {@link CameraCaptureSessionCompat} object which is used in this
@@ -324,7 +318,7 @@
}
- void onConfigureFailed(@NonNull SynchronizedCaptureSession session) {
+ public void onConfigureFailed(@NonNull SynchronizedCaptureSession session) {
}
@@ -343,7 +337,7 @@
* @param session the SynchronizedCaptureSession that is created by
* {@link SynchronizedCaptureSessionImpl#openCaptureSession}
*/
- void onClosed(@NonNull SynchronizedCaptureSession session) {
+ public void onClosed(@NonNull SynchronizedCaptureSession session) {
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java
index c2d0e21..c15cc2a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java
@@ -29,7 +29,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.SynchronizedCaptureSessionOpener.SynchronizedSessionFeature;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.CameraCaptureSessionCompat;
import androidx.camera.camera2.internal.compat.CameraDeviceCompat;
@@ -127,8 +126,7 @@
@NonNull
@Override
- public ListenableFuture<Void> getSynchronizedBlocker(
- @SynchronizedSessionFeature @NonNull String feature) {
+ public ListenableFuture<Void> getOpeningBlocker() {
return Futures.immediateFuture(null);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java
index df71852..10ab59b 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java
@@ -27,22 +27,19 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.SynchronizedCaptureSessionOpener.SynchronizedSessionFeature;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.workaround.ForceCloseCaptureSession;
+import androidx.camera.camera2.internal.compat.workaround.ForceCloseDeferrableSurface;
+import androidx.camera.camera2.internal.compat.workaround.WaitForRepeatingRequestStart;
import androidx.camera.core.Logger;
import androidx.camera.core.impl.DeferrableSurface;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureChain;
+import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
@@ -69,12 +66,6 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
private final Object mObjectLock = new Object();
- @NonNull
- private final Set<String> mEnabledFeature;
- @NonNull
- private final ListenableFuture<Void> mStartStreamingFuture;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- CallbackToFutureAdapter.Completer<Void> mStartStreamingCompleter;
@Nullable
@GuardedBy("mObjectLock")
@@ -82,35 +73,22 @@
@Nullable
@GuardedBy("mObjectLock")
ListenableFuture<Void> mOpeningCaptureSession;
- @Nullable
- @GuardedBy("mObjectLock")
- ListenableFuture<List<Surface>> mStartingSurface;
- /** Whether the capture session has submitted the repeating request. */
- @GuardedBy("mObjectLock")
- private boolean mHasSubmittedRepeating;
+ private final ForceCloseDeferrableSurface mCloseSurfaceQuirk;
+ private final WaitForRepeatingRequestStart mWaitForOtherSessionCompleteQuirk;
+ private final ForceCloseCaptureSession mForceCloseSessionQuirk;
SynchronizedCaptureSessionImpl(
- @NonNull Set<String> enabledFeature,
+ @NonNull Quirks cameraQuirks,
+ @NonNull Quirks deviceQuirks,
@NonNull CaptureSessionRepository repository,
@NonNull @CameraExecutor Executor executor,
@NonNull ScheduledExecutorService scheduledExecutorService,
@NonNull Handler compatHandler) {
super(repository, executor, scheduledExecutorService, compatHandler);
- mEnabledFeature = enabledFeature;
-
- if (enabledFeature.contains(SynchronizedCaptureSessionOpener.FEATURE_WAIT_FOR_REQUEST)) {
- mStartStreamingFuture = CallbackToFutureAdapter.getFuture(completer -> {
- // Opening and releasing the capture session quickly and constantly is a problem for
- // LEGACY devices. See: b/146773463. It needs to check all the releasing capture
- // sessions are ready for opening next capture session.
- mStartStreamingCompleter = completer;
- return "StartStreamingFuture[session=" + SynchronizedCaptureSessionImpl.this
- + "]";
- });
- } else {
- mStartStreamingFuture = Futures.immediateFuture(null);
- }
+ mCloseSurfaceQuirk = new ForceCloseDeferrableSurface(cameraQuirks, deviceQuirks);
+ mWaitForOtherSessionCompleteQuirk = new WaitForRepeatingRequestStart(cameraQuirks);
+ mForceCloseSessionQuirk = new ForceCloseCaptureSession(deviceQuirks);
}
@NonNull
@@ -119,42 +97,18 @@
@NonNull SessionConfigurationCompat sessionConfigurationCompat,
@NonNull List<DeferrableSurface> deferrableSurfaces) {
synchronized (mObjectLock) {
- List<ListenableFuture<Void>> futureList =
- getBlockerFuture(SynchronizedCaptureSessionOpener.FEATURE_WAIT_FOR_REQUEST,
- mCaptureSessionRepository.getClosingCaptureSession());
-
- mOpeningCaptureSession =
- FutureChain.from(Futures.successfulAsList(futureList)).transformAsync(
- v -> super.openCaptureSession(cameraDevice,
- sessionConfigurationCompat, deferrableSurfaces),
- CameraXExecutors.directExecutor());
-
+ mOpeningCaptureSession = mWaitForOtherSessionCompleteQuirk.openCaptureSession(
+ cameraDevice, sessionConfigurationCompat, deferrableSurfaces,
+ mCaptureSessionRepository.getClosingCaptureSession(),
+ super::openCaptureSession);
return Futures.nonCancellationPropagating(mOpeningCaptureSession);
}
}
@NonNull
@Override
- public ListenableFuture<Void> getSynchronizedBlocker(
- @SynchronizedSessionFeature @NonNull String feature) {
- switch (feature) {
- case SynchronizedCaptureSessionOpener.FEATURE_WAIT_FOR_REQUEST:
- // Returns the future which is completed once the session starts streaming
- // frames.
- return Futures.nonCancellationPropagating(mStartStreamingFuture);
- default:
- return super.getSynchronizedBlocker(feature);
- }
- }
-
- private List<ListenableFuture<Void>> getBlockerFuture(
- @SynchronizedSessionFeature @NonNull String feature,
- List<SynchronizedCaptureSession> sessions) {
- List<ListenableFuture<Void>> futureList = new ArrayList<>();
- for (SynchronizedCaptureSession session : sessions) {
- futureList.add(session.getSynchronizedBlocker(feature));
- }
- return futureList;
+ public ListenableFuture<Void> getOpeningBlocker() {
+ return mWaitForOtherSessionCompleteQuirk.getStartStreamFuture();
}
@NonNull
@@ -163,8 +117,7 @@
@NonNull List<DeferrableSurface> deferrableSurfaces, long timeout) {
synchronized (mObjectLock) {
mDeferrableSurfaces = deferrableSurfaces;
- return Futures.nonCancellationPropagating(
- super.startWithDeferrableSurface(deferrableSurfaces, timeout));
+ return super.startWithDeferrableSurface(deferrableSurfaces, timeout);
}
}
@@ -172,14 +125,9 @@
public boolean stop() {
synchronized (mObjectLock) {
if (isCameraCaptureSessionOpen()) {
- closeConfiguredDeferrableSurfaces();
- } else {
- if (mOpeningCaptureSession != null) {
- mOpeningCaptureSession.cancel(true);
- }
- if (mStartingSurface != null) {
- mStartingSurface.cancel(true);
- }
+ mCloseSurfaceQuirk.onSessionEnd(mDeferrableSurfaces);
+ } else if (mOpeningCaptureSession != null) {
+ mOpeningCaptureSession.cancel(true);
}
return super.stop();
}
@@ -188,146 +136,40 @@
@Override
public int setSingleRepeatingRequest(@NonNull CaptureRequest request,
@NonNull CameraCaptureSession.CaptureCallback listener) throws CameraAccessException {
- if (mEnabledFeature.contains(SynchronizedCaptureSessionOpener.FEATURE_WAIT_FOR_REQUEST)) {
- synchronized (mObjectLock) {
- mHasSubmittedRepeating = true;
- CameraCaptureSession.CaptureCallback comboCaptureCallback =
- Camera2CaptureCallbacks.createComboCallback(mCaptureCallback, listener);
-
- return super.setSingleRepeatingRequest(request, comboCaptureCallback);
- }
- } else {
- return super.setSingleRepeatingRequest(request, listener);
- }
+ return mWaitForOtherSessionCompleteQuirk.setSingleRepeatingRequest(
+ request, listener, super::setSingleRepeatingRequest);
}
@Override
public void onConfigured(@NonNull SynchronizedCaptureSession session) {
debugLog("Session onConfigured()");
- if (mEnabledFeature.contains(SynchronizedCaptureSessionOpener.FEATURE_FORCE_CLOSE)) {
- Set<SynchronizedCaptureSession> staleCreatingSessions = new LinkedHashSet<>();
- for (SynchronizedCaptureSession s :
- mCaptureSessionRepository.getCreatingCaptureSessions()) {
- // Collect the sessions that started configuring before the current session. The
- // current session and the session that starts configure after the current session
- // are not included since they don't need to be closed.
- if (s == session) {
- break;
- }
- staleCreatingSessions.add(s);
- }
- // Once the CaptureSession is configured, the stale CaptureSessions should not have
- // chance to complete the configuration flow. Force change to configure fail since
- // the configureFail will treat the CaptureSession is closed. More detail please see
- // b/158540776.
- forceOnConfigureFailed(staleCreatingSessions);
- }
-
- super.onConfigured(session);
-
- // Once the new CameraCaptureSession is created, all the previous opened
- // CameraCaptureSession can be treated as closed (more detail in b/144817309),
- // trigger its associated StateCallback#onClosed callback to finish the
- // session close flow.
- if (mEnabledFeature.contains(SynchronizedCaptureSessionOpener.FEATURE_FORCE_CLOSE)) {
- Set<SynchronizedCaptureSession> openedSessions = new LinkedHashSet<>();
- for (SynchronizedCaptureSession s : mCaptureSessionRepository.getCaptureSessions()) {
-
- // The entrySet keys of the LinkedHashMap should be insertion-ordered, so we
- // get the previous capture sessions by iterate it from the beginning.
- if (s == session) {
- break;
- }
- openedSessions.add(s);
- }
-
- forceOnClosed(openedSessions);
- }
+ mForceCloseSessionQuirk.onSessionConfigured(session,
+ mCaptureSessionRepository.getCreatingCaptureSessions(),
+ mCaptureSessionRepository.getCaptureSessions(),
+ super::onConfigured);
}
@Override
public void close() {
debugLog("Session call close()");
- if (mEnabledFeature.contains(SynchronizedCaptureSessionOpener.FEATURE_WAIT_FOR_REQUEST)) {
- synchronized (mObjectLock) {
- if (!mHasSubmittedRepeating) {
- // If the release() is called before any repeating requests have been issued,
- // then the startStreamingFuture should be cancelled.
- mStartStreamingFuture.cancel(true);
- }
- }
- }
-
- mStartStreamingFuture.addListener(() -> {
+ mWaitForOtherSessionCompleteQuirk.onSessionEnd();
+ mWaitForOtherSessionCompleteQuirk.getStartStreamFuture().addListener(() -> {
// Checks the capture session is ready before closing. See: b/146773463.
debugLog("Session call super.close()");
super.close();
}, getExecutor());
}
- void closeConfiguredDeferrableSurfaces() {
- synchronized (mObjectLock) {
- if (mDeferrableSurfaces == null) {
- debugLog("deferrableSurface == null, maybe forceClose, skip close");
- return;
- }
-
- if (mEnabledFeature.contains(
- SynchronizedCaptureSessionOpener.FEATURE_DEFERRABLE_SURFACE_CLOSE)) {
- // Do not close for non-LEGACY devices. Reusing {@link DeferrableSurface} is only a
- // problem for LEGACY devices. See: b/135050586.
- // Another problem is the behavior of TextureView below API 23. It releases {@link
- // SurfaceTexture}. Hence, request to close and recreate {@link DeferrableSurface}.
- // See: b/145725334.
- for (DeferrableSurface deferrableSurface : mDeferrableSurfaces) {
- deferrableSurface.close();
- }
- debugLog("deferrableSurface closed");
- }
- }
- }
-
@Override
public void onClosed(@NonNull SynchronizedCaptureSession session) {
- closeConfiguredDeferrableSurfaces();
+ synchronized (mObjectLock) {
+ mCloseSurfaceQuirk.onSessionEnd(mDeferrableSurfaces);
+ }
debugLog("onClosed()");
super.onClosed(session);
}
- static void forceOnClosed(@NonNull Set<SynchronizedCaptureSession> sessions) {
- for (SynchronizedCaptureSession session : sessions) {
- session.getStateCallback().onClosed(session);
- }
- }
-
- private void forceOnConfigureFailed(@NonNull Set<SynchronizedCaptureSession> sessions) {
- for (SynchronizedCaptureSession session : sessions) {
- session.getStateCallback().onConfigureFailed(session);
- }
- }
-
void debugLog(String message) {
Logger.d(TAG, "[" + SynchronizedCaptureSessionImpl.this + "] " + message);
}
-
- private final CameraCaptureSession.CaptureCallback mCaptureCallback =
- new CameraCaptureSession.CaptureCallback() {
- @Override
- public void onCaptureStarted(@NonNull CameraCaptureSession session,
- @NonNull CaptureRequest request, long timestamp, long frameNumber) {
- if (mStartStreamingCompleter != null) {
- mStartStreamingCompleter.set(null);
- mStartStreamingCompleter = null;
- }
- }
-
- @Override
- public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
- int sequenceId) {
- if (mStartStreamingCompleter != null) {
- mStartStreamingCompleter.setCancelled();
- mStartStreamingCompleter = null;
- }
- }
- };
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionOpener.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionOpener.java
index fbbeff7..7e28273 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionOpener.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionOpener.java
@@ -16,30 +16,25 @@
package androidx.camera.camera2.internal;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.params.SessionConfiguration;
-import android.os.Build;
import android.os.Handler;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StringDef;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.workaround.ForceCloseCaptureSession;
+import androidx.camera.camera2.internal.compat.workaround.ForceCloseDeferrableSurface;
+import androidx.camera.camera2.internal.compat.workaround.WaitForRepeatingRequestStart;
import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.Quirks;
import com.google.common.util.concurrent.ListenableFuture;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
@@ -69,41 +64,6 @@
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
final class SynchronizedCaptureSessionOpener {
- /**
- * Force close CaptureSession
- * Criteria: OS version
- * Description: on API22 or lower CaptureSession's close event might not be triggered and
- * thus have to be force closed
- */
- static final String FEATURE_FORCE_CLOSE = "force_close";
-
- /**
- * Do not close for non-LEGACY devices
- * Criteria: hardware level, OS version
- * Description: Do not close for non-LEGACY devices. Reusing {@link DeferrableSurface} is
- * only a problem for LEGACY devices. See: b/135050586. Another problem is the behavior of
- * TextureView below API 23. It releases {@link SurfaceTexture}. Hence, request to close and
- * recreate {@link DeferrableSurface}. See: b/145725334. (A better place for View related
- * workaround is in the view artifact. However changing this now would be a breaking change.)
- */
- static final String FEATURE_DEFERRABLE_SURFACE_CLOSE = "deferrableSurface_close";
-
- /**
- * Delay Opening and releasing the capture session after set repeating request complete.
- * Criteria: hardware level
- * Description: Opening and releasing the capture session quickly and constantly is a problem
- * for LEGACY devices. See: b/146773463. It needs to check all the releasing capture
- * sessions are ready for opening next capture session.
- */
- static final String FEATURE_WAIT_FOR_REQUEST = "wait_for_request";
-
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @StringDef({FEATURE_DEFERRABLE_SURFACE_CLOSE, FEATURE_WAIT_FOR_REQUEST, FEATURE_FORCE_CLOSE})
- @Retention(RetentionPolicy.SOURCE)
- public @interface SynchronizedSessionFeature {
- }
-
@NonNull
private final OpenerImpl mImpl;
@@ -219,47 +179,36 @@
private final ScheduledExecutorService mScheduledExecutorService;
private final Handler mCompatHandler;
private final CaptureSessionRepository mCaptureSessionRepository;
- private final int mSupportedHardwareLevel;
- private final Set<String> mEnableFeature = new HashSet<>();
+ private final Quirks mCameraQuirks;
+ private final Quirks mDeviceQuirks;
+ private final boolean mQuirkExist;
Builder(@NonNull @CameraExecutor Executor executor,
@NonNull ScheduledExecutorService scheduledExecutorService,
@NonNull Handler compatHandler,
@NonNull CaptureSessionRepository captureSessionRepository,
- int supportedHardwareLevel) {
+ @NonNull Quirks cameraQuirks,
+ @NonNull Quirks deviceQuirks) {
mExecutor = executor;
mScheduledExecutorService = scheduledExecutorService;
mCompatHandler = compatHandler;
mCaptureSessionRepository = captureSessionRepository;
- mSupportedHardwareLevel = supportedHardwareLevel;
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- mEnableFeature.add(FEATURE_FORCE_CLOSE);
- }
-
- if (mSupportedHardwareLevel
- == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
- || Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
- mEnableFeature.add(FEATURE_DEFERRABLE_SURFACE_CLOSE);
- }
-
- if (mSupportedHardwareLevel
- == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
- mEnableFeature.add(FEATURE_WAIT_FOR_REQUEST);
- }
+ mCameraQuirks = cameraQuirks;
+ mDeviceQuirks = deviceQuirks;
+ mQuirkExist = new ForceCloseDeferrableSurface(mCameraQuirks,
+ mDeviceQuirks).shouldForceClose() || new WaitForRepeatingRequestStart(
+ mCameraQuirks).shouldWaitRepeatingSubmit() || new ForceCloseCaptureSession(
+ mDeviceQuirks).shouldForceClose();
}
@NonNull
SynchronizedCaptureSessionOpener build() {
- if (mEnableFeature.isEmpty()) {
- return new SynchronizedCaptureSessionOpener(
- new SynchronizedCaptureSessionBaseImpl(mCaptureSessionRepository, mExecutor,
- mScheduledExecutorService, mCompatHandler));
- }
-
return new SynchronizedCaptureSessionOpener(
- new SynchronizedCaptureSessionImpl(mEnableFeature, mCaptureSessionRepository,
- mExecutor, mScheduledExecutorService, mCompatHandler));
+ mQuirkExist ? new SynchronizedCaptureSessionImpl(mCameraQuirks, mDeviceQuirks,
+ mCaptureSessionRepository, mExecutor, mScheduledExecutorService,
+ mCompatHandler)
+ : new SynchronizedCaptureSessionBaseImpl(mCaptureSessionRepository,
+ mExecutor, mScheduledExecutorService, mCompatHandler));
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
index 9cd4ce7..b153fd8 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
@@ -76,6 +76,15 @@
if (AfRegionFlipHorizontallyQuirk.load(cameraCharacteristicsCompat)) {
quirks.add(new AfRegionFlipHorizontallyQuirk());
}
+ if (ConfigureSurfaceToSecondarySessionFailQuirk.load(cameraCharacteristicsCompat)) {
+ quirks.add(new ConfigureSurfaceToSecondarySessionFailQuirk());
+ }
+ if (PreviewOrientationIncorrectQuirk.load(cameraCharacteristicsCompat)) {
+ quirks.add(new PreviewOrientationIncorrectQuirk());
+ }
+ if (CaptureSessionStuckQuirk.load(cameraCharacteristicsCompat)) {
+ quirks.add(new CaptureSessionStuckQuirk());
+ }
return new Quirks(quirks);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureSessionOnClosedNotCalledQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureSessionOnClosedNotCalledQuirk.java
new file mode 100644
index 0000000..72818ee
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureSessionOnClosedNotCalledQuirk.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk to denote the devices may not receives
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback#onClosed} callback.
+ *
+ * <p>QuirkSummary
+ * Bug Id: 144817309
+ * Description: On Android API 22 and lower,
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback#onClosed}
+ * callback will not be triggered under some circumstances.
+ * Device(s): Devices in Android API version <= 22
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class CaptureSessionOnClosedNotCalledQuirk implements Quirk {
+
+ static boolean load() {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureSessionStuckQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureSessionStuckQuirk.java
new file mode 100644
index 0000000..d05e85a
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureSessionStuckQuirk.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.quirk;
+
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk to denote the {@link android.hardware.camera2.CameraCaptureSession} cannot
+ * successfully be configured if the previous CameraCaptureSession doesn't finish its in-flight
+ * capture sequence.
+ *
+ * <p>QuirkSummary
+ * Bug Id: 146773463
+ * Description: Opening and releasing the capture session quickly and constantly is a problem
+ * for LEGACY devices. It needs to check that all the existing capture sessions
+ * have finished the processing of their capture sequences before opening the
+ * next capture session.
+ * Device(s): Devices in LEGACY camera hardware level.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class CaptureSessionStuckQuirk implements Quirk {
+
+ static boolean load(@NonNull CameraCharacteristicsCompat characteristics) {
+ final Integer level = characteristics.get(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ return level != null && level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ConfigureSurfaceToSecondarySessionFailQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ConfigureSurfaceToSecondarySessionFailQuirk.java
new file mode 100644
index 0000000..e88e95e
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ConfigureSurfaceToSecondarySessionFailQuirk.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.quirk;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk to denote the surface can only be used to configure to only one
+ * {@link CameraCaptureSession}, the next {@link CameraCaptureSession} may need to use
+ * another one.
+ *
+ * <p>QuirkSummary
+ * Bug Id: 129520942, 135050586
+ * Description: Reusing a surface to create different {@link CameraCaptureSession} causes
+ * crash on LEGACY devices.
+ * Device(s): Devices in LEGACY camera hardware level.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ConfigureSurfaceToSecondarySessionFailQuirk implements Quirk {
+
+ static boolean load(@NonNull CameraCharacteristicsCompat characteristics) {
+ final Integer level = characteristics.get(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ return level != null && level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/DeviceQuirksLoader.java
index fc9efb6..42d899a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/DeviceQuirksLoader.java
@@ -71,6 +71,12 @@
if (RepeatingStreamConstraintForVideoRecordingQuirk.load()) {
quirks.add(new RepeatingStreamConstraintForVideoRecordingQuirk());
}
+ if (TextureViewIsClosedQuirk.load()) {
+ quirks.add(new TextureViewIsClosedQuirk());
+ }
+ if (CaptureSessionOnClosedNotCalledQuirk.load()) {
+ quirks.add(new CaptureSessionOnClosedNotCalledQuirk());
+ }
return quirks;
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/JpegHalCorruptImageQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/JpegHalCorruptImageQuirk.java
index 378400a..fbdd892 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/JpegHalCorruptImageQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/JpegHalCorruptImageQuirk.java
@@ -31,11 +31,14 @@
/**
* Quirk which denotes JPEGs produced directly from the HAL may sometimes be corrupted.
*
- * <p>Corrupt images generally manifest as completely monochrome JPEGs, sometimes solid green.
- * If possible, it is preferred that CameraX produce JPEGs from some other image format rather
- * than receiving JPEGs directly from the HAL.
- *
- * @see <a href="https://issuetracker.google.com/159831206">issuetracker.google.com/159831206</a>
+ * <p>QuirkSummary
+ * Bug Id: <a href="https://issuetracker.google.com/159831206">159831206</a>
+ * Description: Corrupt images generally manifest as completely monochrome JPEGs, sometimes
+ * solid green. On the affected devices, this is easier to reproduce
+ * immediately after rebooting the device. If possible, it is preferred
+ * that CameraX produce JPEGs from some other image format rather than
+ * receiving JPEGs directly from the HAL.
+ * Device(s): Samsung Galaxy S7 (SM-G930T and SM-G930V variants)
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class JpegHalCorruptImageQuirk implements SoftwareJpegEncodingPreferredQuirk {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewOrientationIncorrectQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewOrientationIncorrectQuirk.java
new file mode 100644
index 0000000..ea49eae
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewOrientationIncorrectQuirk.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.quirk;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk where the orientation of the preview is incorrect while a surface to be used to
+ * configure on 2 different {@link CameraCaptureSession}.
+ *
+ * <p>QuirkSummary
+ * Bug Id: 128577112
+ * Description: Reusing a surface to create a different {@link CameraCaptureSession}
+ * causes incorrect preview orientation for LEGACY devices. Some GL related setting
+ * is left over when the legacy shim is recreated. It causes the 2nd
+ * PreviewCaptureSession to be rotated and stretched compared to the 1st one.
+ * Device(s): Devices in LEGACY camera hardware level.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class PreviewOrientationIncorrectQuirk implements Quirk {
+
+ static boolean load(@NonNull CameraCharacteristicsCompat characteristics) {
+ final Integer level = characteristics.get(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ return level != null && level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TextureViewIsClosedQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TextureViewIsClosedQuirk.java
new file mode 100644
index 0000000..c493a13
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TextureViewIsClosedQuirk.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.quirk;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCaptureSession;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk to denote a new surface should be acquired while the camera is going to create a new
+ * {@link CameraCaptureSession}.
+ *
+ * <p>QuirkSummary
+ * Bug Id: 145725334
+ * Description: When using TextureView below Android API 23, it releases
+ * {@link SurfaceTexture} when activity is stopped.
+ * Device(s): Devices in Android API version <= 23
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class TextureViewIsClosedQuirk implements Quirk {
+
+ static boolean load() {
+ return Build.VERSION.SDK_INT <= Build.VERSION_CODES.M;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ForceCloseCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ForceCloseCaptureSession.java
new file mode 100644
index 0000000..fff2d41
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ForceCloseCaptureSession.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.workaround;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.SynchronizedCaptureSession;
+import androidx.camera.camera2.internal.compat.quirk.CaptureSessionOnClosedNotCalledQuirk;
+import androidx.camera.core.impl.Quirks;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The workaround is used to set the {@link androidx.camera.camera2.internal.CaptureSession} to
+ * closed state since the
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback#onClosed} may not be called
+ * after the {@link android.hardware.camera2.CameraCaptureSession} is closed.
+ *
+ * @see CaptureSessionOnClosedNotCalledQuirk
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ForceCloseCaptureSession {
+
+ @Nullable
+ private final CaptureSessionOnClosedNotCalledQuirk mCaptureSessionOnClosedNotCalledQuirk;
+
+ /** Constructor of the ForceCloseCaptureSession workaround */
+ public ForceCloseCaptureSession(@NonNull Quirks deviceQuirks) {
+ mCaptureSessionOnClosedNotCalledQuirk =
+ deviceQuirks.get(CaptureSessionOnClosedNotCalledQuirk.class);
+ }
+
+ /** Return true if the obsolete non-closed capture sessions should be forced closed. */
+ public boolean shouldForceClose() {
+ return mCaptureSessionOnClosedNotCalledQuirk != null;
+ }
+
+ /**
+ * For b/144817309, the onClosed() callback on
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback}
+ * might not be invoked if the capture session is not the latest one. To align the fixed
+ * framework behavior, we manually call the onClosed() when a new CameraCaptureSession is
+ * created.
+ */
+ public void onSessionConfigured(@NonNull SynchronizedCaptureSession session,
+ @NonNull List<SynchronizedCaptureSession> creatingSessions,
+ @NonNull List<SynchronizedCaptureSession> sessions,
+ @NonNull OnConfigured onConfigured) {
+ if (shouldForceClose()) {
+ Set<SynchronizedCaptureSession> staleCreatingSessions = new LinkedHashSet<>();
+ for (SynchronizedCaptureSession s : creatingSessions) {
+ // Collect the sessions that started configuring before the current session. The
+ // current session and the session that starts configure after the current session
+ // are not included since they don't need to be closed.
+ if (s == session) {
+ break;
+ }
+ staleCreatingSessions.add(s);
+ }
+ // Once the CaptureSession is configured, the stale CaptureSessions should not have
+ // chance to complete the configuration flow. Force change to configure fail since
+ // the configureFail will treat the CaptureSession is closed. More detail please see
+ // b/158540776.
+ forceOnConfigureFailed(staleCreatingSessions);
+ }
+
+ onConfigured.run(session);
+
+ // Once the new CameraCaptureSession is created, all the previous opened
+ // CameraCaptureSession can be treated as closed (more detail in b/144817309),
+ // trigger its associated StateCallback#onClosed callback to finish the
+ // session close flow.
+ if (shouldForceClose()) {
+ Set<SynchronizedCaptureSession> openedSessions = new LinkedHashSet<>();
+ for (SynchronizedCaptureSession s : sessions) {
+
+ // The entrySet keys of the LinkedHashMap should be insertion-ordered, so we
+ // get the previous capture sessions by iterate it from the beginning.
+ if (s == session) {
+ break;
+ }
+ openedSessions.add(s);
+ }
+
+ forceOnClosed(openedSessions);
+ }
+ }
+
+ private void forceOnConfigureFailed(@NonNull Set<SynchronizedCaptureSession> sessions) {
+ for (SynchronizedCaptureSession session : sessions) {
+ session.getStateCallback().onConfigureFailed(session);
+ }
+ }
+
+ private void forceOnClosed(@NonNull Set<SynchronizedCaptureSession> sessions) {
+ for (SynchronizedCaptureSession session : sessions) {
+ session.getStateCallback().onClosed(session);
+ }
+ }
+
+ /** Interface to forward call of the onConfigured() method. */
+ @FunctionalInterface
+ public interface OnConfigured {
+ /** Run the onConfigured() method. */
+ void run(@NonNull SynchronizedCaptureSession session);
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ForceCloseDeferrableSurface.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ForceCloseDeferrableSurface.java
new file mode 100644
index 0000000..ced108e
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ForceCloseDeferrableSurface.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.workaround;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.quirk.ConfigureSurfaceToSecondarySessionFailQuirk;
+import androidx.camera.camera2.internal.compat.quirk.PreviewOrientationIncorrectQuirk;
+import androidx.camera.camera2.internal.compat.quirk.TextureViewIsClosedQuirk;
+import androidx.camera.core.Logger;
+import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.Quirks;
+
+import java.util.List;
+
+/**
+ * The workaround is used to close the {@link androidx.camera.core.impl.DeferrableSurface} when the
+ * {@link android.hardware.camera2.CameraCaptureSession} is closed. The next CameraCaptureSession
+ * will throw an exception when attempting to retrieve the underlying Surface.
+ * The Camera now posts an error to the UseCase signaling the DeferrableSurface needs to be
+ * recreated.
+ *
+ * @see TextureViewIsClosedQuirk
+ * @see PreviewOrientationIncorrectQuirk
+ * @see ConfigureSurfaceToSecondarySessionFailQuirk
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ForceCloseDeferrableSurface {
+ private final boolean mHasTextureViewIsClosedQuirk;
+ private final boolean mHasPreviewOrientationIncorrectQuirk;
+ private final boolean mHasConfigureSurfaceToSecondarySessionFailQuirk;
+
+ /** Constructor of the ForceCloseDeferrableSurface workaround */
+ public ForceCloseDeferrableSurface(@NonNull Quirks cameraQuirks,
+ @NonNull Quirks deviceQuirks) {
+ mHasTextureViewIsClosedQuirk = deviceQuirks.contains(TextureViewIsClosedQuirk.class);
+ mHasPreviewOrientationIncorrectQuirk = cameraQuirks.contains(
+ PreviewOrientationIncorrectQuirk.class);
+ mHasConfigureSurfaceToSecondarySessionFailQuirk =
+ cameraQuirks.contains(ConfigureSurfaceToSecondarySessionFailQuirk.class);
+ }
+
+ /**
+ * Return true if any of the TextureViewIsClosedQuirk, PreviewOrientationIncorrectQuirk,
+ * ConfigureSurfaceToSecondarySessionFailQuirk is enabled.
+ */
+ public boolean shouldForceClose() {
+ return mHasTextureViewIsClosedQuirk || mHasPreviewOrientationIncorrectQuirk
+ || mHasConfigureSurfaceToSecondarySessionFailQuirk;
+ }
+
+ /**
+ * Close the {@link DeferrableSurface} to force the {@link DeferrableSurface} to be recreated
+ * in the new CameraCaptureSession.
+ */
+ public void onSessionEnd(@Nullable List<DeferrableSurface> deferrableSurfaces) {
+ if (shouldForceClose() && deferrableSurfaces != null) {
+ for (DeferrableSurface deferrableSurface : deferrableSurfaces) {
+ deferrableSurface.close();
+ }
+ Logger.d("ForceCloseDeferrableSurface", "deferrableSurface closed");
+ }
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/WaitForRepeatingRequestStart.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/WaitForRepeatingRequestStart.java
new file mode 100644
index 0000000..8da55f2
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/WaitForRepeatingRequestStart.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.workaround;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.Camera2CaptureCallbacks;
+import androidx.camera.camera2.internal.SynchronizedCaptureSession;
+import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.quirk.CaptureSessionStuckQuirk;
+import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.Quirks;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.impl.utils.futures.FutureChain;
+import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The workaround is used to wait for the other CameraCaptureSessions to complete their in-flight
+ * capture sequences before opening the current session.
+ * <p>If it tries to open the CameraCaptureSession before the others to complete their in-flight
+ * capture sequences, the current session may fail to be configured.
+ *
+ * @see CaptureSessionStuckQuirk
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class WaitForRepeatingRequestStart {
+ private final boolean mHasCaptureSessionStuckQuirk;
+ private final Object mLock = new Object();
+
+ @NonNull
+ private final ListenableFuture<Void> mStartStreamingFuture;
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ CallbackToFutureAdapter.Completer<Void> mStartStreamingCompleter;
+ /** Whether the capture session has submitted the repeating request. */
+ private boolean mHasSubmittedRepeating;
+
+ /** Constructor of the WaitForRepeatingRequestStart workaround */
+ public WaitForRepeatingRequestStart(@NonNull Quirks cameraQuirks) {
+ mHasCaptureSessionStuckQuirk = cameraQuirks.contains(CaptureSessionStuckQuirk.class);
+
+ if (shouldWaitRepeatingSubmit()) {
+ mStartStreamingFuture = CallbackToFutureAdapter.getFuture(completer -> {
+ mStartStreamingCompleter = completer;
+ return "WaitForRepeatingRequestStart[" + this + "]";
+ });
+ } else {
+ mStartStreamingFuture = Futures.immediateFuture(null);
+ }
+ }
+
+ /**
+ * Return true if the opening of the session should wait for the other CameraCaptureSessions
+ * to complete their in-flight capture sequences before opening the current session.
+ */
+ public boolean shouldWaitRepeatingSubmit() {
+ return mHasCaptureSessionStuckQuirk;
+ }
+
+ /** Returns a ListenableFuture to indicate whether the start repeating request is done. */
+ @NonNull
+ public ListenableFuture<Void> getStartStreamFuture() {
+ return Futures.nonCancellationPropagating(mStartStreamingFuture);
+ }
+
+ /**
+ * For b/146773463: It needs to check all the releasing capture sessions are ready for
+ * opening next capture session.
+ */
+ @NonNull
+ public ListenableFuture<Void> openCaptureSession(
+ @NonNull CameraDevice cameraDevice,
+ @NonNull SessionConfigurationCompat sessionConfigurationCompat,
+ @NonNull List<DeferrableSurface> deferrableSurfaces,
+ @NonNull List<SynchronizedCaptureSession> closingSessions,
+ @NonNull OpenCaptureSession openCaptureSession) {
+ List<ListenableFuture<Void>> futureList = new ArrayList<>();
+ for (SynchronizedCaptureSession session : closingSessions) {
+ futureList.add(session.getOpeningBlocker());
+ }
+
+ return FutureChain.from(Futures.successfulAsList(futureList)).transformAsync(
+ v -> openCaptureSession.run(cameraDevice, sessionConfigurationCompat,
+ deferrableSurfaces), CameraXExecutors.directExecutor());
+ }
+
+ /** Hook the setSingleRepeatingRequest() to know if it has started a repeating request. */
+ public int setSingleRepeatingRequest(
+ @NonNull CaptureRequest request,
+ @NonNull CameraCaptureSession.CaptureCallback listener,
+ @NonNull SingleRepeatingRequest singleRepeatingRequest)
+ throws CameraAccessException {
+ synchronized (mLock) {
+ if (shouldWaitRepeatingSubmit()) {
+ listener = Camera2CaptureCallbacks.createComboCallback(mCaptureCallback, listener);
+ mHasSubmittedRepeating = true;
+ }
+ return singleRepeatingRequest.run(request, listener);
+ }
+ }
+
+ /** This should be called when a SynchronizedCaptureSession is stopped or closed. */
+ public void onSessionEnd() {
+ synchronized (mLock) {
+ if (shouldWaitRepeatingSubmit() && !mHasSubmittedRepeating) {
+ // If the session is closed before any repeating requests have been issued,
+ // then the startStreamingFuture should be cancelled.
+ mStartStreamingFuture.cancel(true);
+ }
+ }
+ }
+
+ private final CameraCaptureSession.CaptureCallback mCaptureCallback =
+ new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureStarted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request, long timestamp, long frameNumber) {
+ if (mStartStreamingCompleter != null) {
+ mStartStreamingCompleter.set(null);
+ mStartStreamingCompleter = null;
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
+ int sequenceId) {
+ if (mStartStreamingCompleter != null) {
+ mStartStreamingCompleter.setCancelled();
+ mStartStreamingCompleter = null;
+ }
+ }
+ };
+
+ /** Interface to forward call of the setSingleRepeatingRequest() method. */
+ @FunctionalInterface
+ public interface SingleRepeatingRequest {
+ /** Run the setSingleRepeatingRequest() method. */
+ int run(@NonNull CaptureRequest request,
+ @NonNull CameraCaptureSession.CaptureCallback listener)
+ throws CameraAccessException;
+ }
+
+ /** Interface to forward call of the openCaptureSession() method. */
+ @FunctionalInterface
+ public interface OpenCaptureSession {
+ /** Run the openCaptureSession() method. */
+ @NonNull
+ ListenableFuture<Void> run(@NonNull CameraDevice cameraDevice,
+ @NonNull SessionConfigurationCompat sessionConfigurationCompat,
+ @NonNull List<DeferrableSurface> deferrableSurfaces);
+ }
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionTest.java
index 4630c62..03cdf81 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionTest.java
@@ -29,8 +29,13 @@
import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.quirk.CaptureSessionOnClosedNotCalledQuirk;
+import androidx.camera.camera2.internal.compat.quirk.ConfigureSurfaceToSecondarySessionFailQuirk;
+import androidx.camera.camera2.internal.compat.quirk.PreviewOrientationIncorrectQuirk;
+import androidx.camera.camera2.internal.compat.quirk.TextureViewIsClosedQuirk;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.ImmediateSurface;
+import androidx.camera.core.impl.Quirks;
import org.junit.Before;
import org.junit.Test;
@@ -41,16 +46,14 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP,
- instrumentedPackages = { "androidx.camera.camera2.internal.compat.params" })
+ instrumentedPackages = {"androidx.camera.camera2.internal.compat.params"})
public class SynchronizedCaptureSessionTest {
private static final int NUM_OUTPUTS = 3;
@@ -80,13 +83,13 @@
mFakeDeferrableSurfaces.add(mDeferrableSurface1);
mFakeDeferrableSurfaces.add(mDeferrableSurface2);
- Set<String> enabledFeature = new HashSet<>();
- enabledFeature.add(SynchronizedCaptureSessionOpener.FEATURE_FORCE_CLOSE);
- enabledFeature.add(SynchronizedCaptureSessionOpener.FEATURE_DEFERRABLE_SURFACE_CLOSE);
-
mCaptureSessionOpenerBuilder = new SynchronizedCaptureSessionOpener.Builder(
android.os.AsyncTask.SERIAL_EXECUTOR, mScheduledExecutorService,
- mock(Handler.class), mCaptureSessionRepository, -1);
+ mock(Handler.class), mCaptureSessionRepository,
+ new Quirks(Arrays.asList(new PreviewOrientationIncorrectQuirk(),
+ new ConfigureSurfaceToSecondarySessionFailQuirk())),
+ new Quirks(Arrays.asList(new CaptureSessionOnClosedNotCalledQuirk(),
+ new TextureViewIsClosedQuirk())));
mSynchronizedCaptureSessionOpener = mCaptureSessionOpenerBuilder.build();
mMockCaptureSession = mock(CameraCaptureSession.class);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
index e39a21c..84fb8e0 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
@@ -31,8 +31,8 @@
import androidx.camera.core.impl.utils.CompareSizesByArea;
import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
import androidx.camera.video.internal.compat.quirk.ExcludeStretchedVideoQualityQuirk;
+import androidx.camera.video.internal.compat.quirk.ReportedVideoQualityNotSupportedQuirk;
import androidx.camera.video.internal.compat.quirk.VideoEncoderCrashQuirk;
-import androidx.camera.video.internal.compat.quirk.VideoQualityNotSupportQuirk;
import androidx.camera.video.internal.compat.quirk.VideoQualityQuirk;
import androidx.core.util.Preconditions;
@@ -203,8 +203,10 @@
private boolean isDeviceValidQuality(@NonNull Quality quality) {
List<Class<? extends VideoQualityQuirk>> quirkList = Arrays.asList(
- ExcludeStretchedVideoQualityQuirk.class, VideoQualityNotSupportQuirk.class,
- VideoEncoderCrashQuirk.class);
+ ExcludeStretchedVideoQualityQuirk.class,
+ ReportedVideoQualityNotSupportedQuirk.class,
+ VideoEncoderCrashQuirk.class
+ );
for (Class<? extends VideoQualityQuirk> quirkClass : quirkList) {
VideoQualityQuirk quirk = DeviceQuirks.get(quirkClass);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
index cf05118..b2a5fd4 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
@@ -53,8 +53,8 @@
if (CameraUseInconsistentTimebaseQuirk.load()) {
quirks.add(new CameraUseInconsistentTimebaseQuirk());
}
- if (VideoQualityNotSupportQuirk.load()) {
- quirks.add(new VideoQualityNotSupportQuirk());
+ if (ReportedVideoQualityNotSupportedQuirk.load()) {
+ quirks.add(new ReportedVideoQualityNotSupportedQuirk());
}
if (EncoderNotUsePersistentInputSurfaceQuirk.load()) {
quirks.add(new EncoderNotUsePersistentInputSurfaceQuirk());
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/VideoQualityNotSupportQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/ReportedVideoQualityNotSupportedQuirk.java
similarity index 60%
rename from camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/VideoQualityNotSupportQuirk.java
rename to camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/ReportedVideoQualityNotSupportedQuirk.java
index 6af69ed..734403e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/VideoQualityNotSupportQuirk.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/ReportedVideoQualityNotSupportedQuirk.java
@@ -17,6 +17,7 @@
package androidx.camera.video.internal.compat.quirk;
import android.media.CamcorderProfile;
+import android.media.MediaCodec;
import android.media.MediaRecorder.VideoEncoder;
import android.os.Build;
@@ -24,21 +25,25 @@
import androidx.annotation.RequiresApi;
import androidx.camera.core.impl.CamcorderProfileProvider;
import androidx.camera.video.Quality;
-import androidx.camera.video.VideoCapabilities;
/**
- * Quirk denotes that quality {@link VideoCapabilities} queried by {@link CamcorderProfileProvider}
- * does not work on video recording on device, and need to exclude it.
+ * Quirk where qualities reported as available by {@link CamcorderProfileProvider#hasProfile(int)}
+ * does not work on the device, and should not be used.
*
- * <p>On Huawei Mate20 and Mate20 Pro, {@link CamcorderProfile} indicates it can support
- * resolutions 3840x2160 for {@link VideoEncoder#H264}, and it can create the video
- * encoder by the corresponding format. However, there is not any video frame output from Camera
- * . The CaptureSession is opened and configured, but something error in the HAL of these devices
- * . Hence use a quirk to exclude the problematic resolution quality. See b/202080832#comment8.
- *
+ * <p>QuirkSummary
+ * Bug Id: 202080832
+ * Description: On devices exhibiting this quirk, {@link CamcorderProfile} indicates it
+ * can support resolutions for a specific video encoder (e.g., 3840x2160 for
+ * {@link VideoEncoder#H264} on Huawei Mate 20), and it can create the video
+ * encoder by the corresponding format. However, the camera is unable to produce
+ * video frames when configured with a {@link MediaCodec} surface at the
+ * specified resolution. On these devices, the capture session is opened and
+ * configured, but an error occurs in the HAL. See b/202080832#comment8
+ * for details of this error.
+ * Device(s): Huawei Mate 20, Huawei Mate 20 Pro
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class VideoQualityNotSupportQuirk implements VideoQualityQuirk {
+public class ReportedVideoQualityNotSupportedQuirk implements VideoQualityQuirk {
static boolean load() {
return isHuaweiMate20() || isHuaweiMate20Pro();
}
diff --git a/collection/collection/src/jvmMain/java/androidx/collection/ArrayMap.java b/collection/collection/src/jvmMain/java/androidx/collection/ArrayMap.java
index d24fffd..ec9bbb1 100644
--- a/collection/collection/src/jvmMain/java/androidx/collection/ArrayMap.java
+++ b/collection/collection/src/jvmMain/java/androidx/collection/ArrayMap.java
@@ -337,9 +337,21 @@
return result;
}
+ @NonNull
@Override
+ @SuppressWarnings("unchecked")
public <T> T[] toArray(@NonNull T[] array) {
- return toArrayHelper(array, 0);
+ final int mySize = size();
+ if (array.length < mySize) {
+ array = (T[]) Array.newInstance(array.getClass().getComponentType(), mySize);
+ }
+ for (int i = 0; i < mySize; i++) {
+ array[i] = (T) keyAt(i);
+ }
+ if (array.length > mySize) {
+ array[mySize] = null;
+ }
+ return array;
}
@Override
@@ -460,9 +472,21 @@
return result;
}
+ @NonNull
@Override
+ @SuppressWarnings("unchecked")
public <T> T[] toArray(@NonNull T[] array) {
- return toArrayHelper(array, 1);
+ final int mySize = size();
+ if (array.length < mySize) {
+ array = (T[]) Array.newInstance(array.getClass().getComponentType(), mySize);
+ }
+ for (int i = 0; i < mySize; i++) {
+ array[i] = (T) valueAt(i);
+ }
+ if (array.length > mySize) {
+ array[mySize] = null;
+ }
+ return array;
}
}
@@ -591,23 +615,6 @@
}
}
- @SuppressWarnings("unchecked")
- <T> T[] toArrayHelper(T[] array, int offset) {
- final int N = size();
- if (array.length < N) {
- @SuppressWarnings("unchecked") T[] newArray
- = (T[]) Array.newInstance(array.getClass().getComponentType(), N);
- array = newArray;
- }
- for (int i = 0; i < N; i++) {
- array[i] = (T) array[(i << 1) + offset];
- }
- if (array.length > N) {
- array[N] = null;
- }
- return array;
- }
-
static <T> boolean equalsSetHelper(Set<T> set, Object object) {
if (set == object) {
return true;
diff --git a/collection/collection/src/jvmTest/java/androidx/collection/ArrayMapTest.java b/collection/collection/src/jvmTest/java/androidx/collection/ArrayMapTest.java
index ed1c507..e61138d 100644
--- a/collection/collection/src/jvmTest/java/androidx/collection/ArrayMapTest.java
+++ b/collection/collection/src/jvmTest/java/androidx/collection/ArrayMapTest.java
@@ -17,6 +17,8 @@
package androidx.collection;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -24,6 +26,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
@@ -121,4 +124,52 @@
assertEquals(map.keyAt(0), "abc");
assertEquals(map.valueAt(0), "def");
}
+
+ @Test
+ public void toArray() {
+ Map<String, Integer> map = new ArrayMap<>();
+ map.put("a", 1);
+ String[] keys = map.keySet().toArray(new String[1]);
+ Integer[] values = map.values().toArray(new Integer[1]);
+
+ assertEquals(Arrays.toString(keys), "[a]");
+ assertEquals(Arrays.toString(values), "[1]");
+
+ // re-do again, it should re-use the same arrays
+ String[] keys2 = map.keySet().toArray(keys);
+ Integer[] values2 = map.values().toArray(values);
+
+ assertSame(keys2, keys);
+ assertSame(values2, values);
+ assertEquals(Arrays.toString(keys2), "[a]");
+ assertEquals(Arrays.toString(values2), "[1]");
+
+ // add new items
+ map.put("b", 2);
+ map.put("c", 3);
+
+ // now it shouldn't re-use arrays because arrays are too small
+ String[] keys3 = map.keySet().toArray(keys);
+ Integer[] values3 = map.values().toArray(values);
+
+ assertNotSame(values3, values);
+ assertNotSame(keys3, keys);
+ assertEquals(Arrays.toString(keys3), "[a, b, c]");
+ assertEquals(Arrays.toString(values3), "[1, 2, 3]");
+
+
+ map.remove("b");
+ map.remove("c");
+
+ // arrays are big enough, re-use
+ String[] keys4 = map.keySet().toArray(keys3);
+ Integer[] values4 = map.values().toArray(values3);
+
+ assertSame(values4, values3);
+ assertSame(keys4, keys3);
+ // https://docs.oracle.com/javase/8/docs/api/java/util/Set.html#toArray-T:A-
+ // only the next index is nulled in the array, per documentation.
+ assertEquals(Arrays.toString(keys4), "[a, null, c]");
+ assertEquals(Arrays.toString(values4), "[1, null, 3]");
+ }
}
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
index dcd4e22..9514f5e 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
@@ -184,8 +184,8 @@
}
}
},
- content = {
- Row(Modifier.padding(bottom = 56.dp)) {
+ content = { innerPadding ->
+ Row(Modifier.padding(innerPadding)) {
LeftColumn(Modifier.weight(1f))
RightColumn(Modifier.width(200.dp))
}
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index 1fd974e..288996e 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -91,6 +91,8 @@
Removed class androidx.compose.foundation.lazy.LazyGridKt
RemovedClass: androidx.compose.foundation.lazy.LazyGridSpanKt:
Removed class androidx.compose.foundation.lazy.LazyGridSpanKt
+RemovedClass: androidx.compose.foundation.lazy.Lazy_androidKt:
+ Removed class androidx.compose.foundation.lazy.Lazy_androidKt
RemovedClass: androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactoryKt:
Removed class androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactoryKt
RemovedClass: androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchPolicyKt:
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 0869326..44b7172 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -479,9 +479,6 @@
@kotlin.DslMarker public @interface LazyScopeMarker {
}
- public final class Lazy_androidKt {
- }
-
}
package androidx.compose.foundation.lazy.grid {
@@ -636,6 +633,9 @@
public final class LazyLayoutPrefetcher_androidKt {
}
+ public final class Lazy_androidKt {
+ }
+
public final class PinnableParentKt {
}
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 894497b..cfcdb0f 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -542,9 +542,6 @@
@kotlin.DslMarker public @interface LazyScopeMarker {
}
- public final class Lazy_androidKt {
- }
-
}
package androidx.compose.foundation.lazy.grid {
@@ -700,6 +697,9 @@
public final class LazyLayoutPrefetcher_androidKt {
}
+ public final class Lazy_androidKt {
+ }
+
@androidx.compose.foundation.ExperimentalFoundationApi public interface PinnableParent {
method public androidx.compose.foundation.lazy.layout.PinnableParent.PinnedItemsHandle pinBeyondBoundsItems();
}
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index 1fd974e..288996e 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -91,6 +91,8 @@
Removed class androidx.compose.foundation.lazy.LazyGridKt
RemovedClass: androidx.compose.foundation.lazy.LazyGridSpanKt:
Removed class androidx.compose.foundation.lazy.LazyGridSpanKt
+RemovedClass: androidx.compose.foundation.lazy.Lazy_androidKt:
+ Removed class androidx.compose.foundation.lazy.Lazy_androidKt
RemovedClass: androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactoryKt:
Removed class androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactoryKt
RemovedClass: androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchPolicyKt:
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 0869326..44b7172 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -479,9 +479,6 @@
@kotlin.DslMarker public @interface LazyScopeMarker {
}
- public final class Lazy_androidKt {
- }
-
}
package androidx.compose.foundation.lazy.grid {
@@ -636,6 +633,9 @@
public final class LazyLayoutPrefetcher_androidKt {
}
+ public final class Lazy_androidKt {
+ }
+
public final class PinnableParentKt {
}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FontFamilyDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FontFamilyDemo.kt
index c4cfc9e..5e33f75 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FontFamilyDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FontFamilyDemo.kt
@@ -172,7 +172,7 @@
// example of defining custom font typeface resolver for use in a FontFamily
// this is typically done to add _new_ types of font resources to Compose
object ExampleAsyncFontTypefaceLoader : AndroidFont.TypefaceLoader {
- override fun loadBlocking(context: Context, font: AndroidFont): android.graphics.Typeface? {
+ override fun loadBlocking(context: Context, font: AndroidFont): Typeface? {
return when (font) {
is DemoOptionalFont -> null // all optional fonts fail
is DemoBlockingFont -> font.typeface
@@ -184,7 +184,7 @@
override suspend fun awaitLoad(
context: Context,
font: AndroidFont
- ): android.graphics.Typeface {
+ ): Typeface {
// delayed fonts take the specified delay
font as DemoAsyncFont
delay(font.delay)
@@ -192,32 +192,22 @@
}
}
-@OptIn(ExperimentalTextApi::class)
class DemoAsyncFont(
override val weight: FontWeight,
override val style: FontStyle,
val delay: Long,
- val typeface: android.graphics.Typeface = android.graphics.Typeface.MONOSPACE
-) : AndroidFont(Async) {
- override val typefaceLoader: TypefaceLoader
- get() = ExampleAsyncFontTypefaceLoader
-}
+ val typeface: Typeface = Typeface.MONOSPACE
+) : AndroidFont(Async, ExampleAsyncFontTypefaceLoader)
@OptIn(ExperimentalTextApi::class)
class DemoOptionalFont(
override val weight: FontWeight,
override val style: FontStyle,
-) : AndroidFont(OptionalLocal) {
- override val typefaceLoader: TypefaceLoader
- get() = ExampleAsyncFontTypefaceLoader
-}
+) : AndroidFont(OptionalLocal, ExampleAsyncFontTypefaceLoader)
@OptIn(ExperimentalTextApi::class)
class DemoBlockingFont(
override val weight: FontWeight,
override val style: FontStyle,
- val typeface: android.graphics.Typeface
-) : AndroidFont(OptionalLocal) {
- override val typefaceLoader: TypefaceLoader
- get() = ExampleAsyncFontTypefaceLoader
-}
+ val typeface: Typeface
+) : AndroidFont(OptionalLocal, ExampleAsyncFontTypefaceLoader)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index ad95123..db78578 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -53,14 +53,12 @@
override fun placeChildren() {}
}
}
- val itemsProvider = {
- object : LazyLayoutItemsProvider {
- override fun getContent(index: Int): @Composable () -> Unit = {}
- override val itemsCount: Int = 0
- override fun getKey(index: Int) = Unit
- override val keyToIndexMap: Map<Any, Int> = emptyMap()
- override fun getContentType(index: Int): Any? = null
- }
+ val itemsProvider = object : LazyLayoutItemsProvider {
+ override fun getContent(index: Int): @Composable () -> Unit = {}
+ override val itemsCount: Int = 0
+ override fun getKey(index: Int) = Unit
+ override val keyToIndexMap: Map<Any, Int> = emptyMap()
+ override fun getContentType(index: Int): Any? = null
}
rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldSoftKeyboardTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldSoftKeyboardTest.kt
index 6cdc2c7..dba0121 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldSoftKeyboardTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldSoftKeyboardTest.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.test.performClick
import androidx.compose.ui.text.input.TextFieldValue
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import org.junit.Rule
@@ -68,6 +69,7 @@
keyboardHelper.waitForKeyboardVisibility(visible = true)
}
+ @FlakyTest(bugId = 228258574)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
@Test
fun keyboardShownOnInitialFocus() {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt
index 86e5efc..60b4ec6 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt
@@ -176,8 +176,7 @@
}
}
val fontFamily = FontFamily(
- object : AndroidFont(FontLoadingStrategy.Async) {
- override val typefaceLoader: TypefaceLoader = asyncLoader
+ object : AndroidFont(FontLoadingStrategy.Async, asyncLoader) {
override val weight: FontWeight = FontWeight.Normal
override val style: FontStyle = FontStyle.Normal
},
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/Lazy.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/Lazy.android.kt
similarity index 91%
rename from compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/Lazy.android.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/Lazy.android.kt
index cfdec14..0f8c228 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/Lazy.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/Lazy.android.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package androidx.compose.foundation.lazy
+package androidx.compose.foundation.lazy.layout
import android.annotation.SuppressLint
import android.os.Parcel
import android.os.Parcelable
-internal actual fun getDefaultLazyKeyFor(index: Int): Any = DefaultLazyKey(index)
+internal actual fun getDefaultLazyLayoutKey(index: Int): Any = DefaultLazyKey(index)
@SuppressLint("BanParcelableUsage")
private data class DefaultLazyKey(private val index: Int) : Parcelable {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index 3b7236b..e2eb329 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -416,11 +416,3 @@
content = content
)
}
-
-/**
- * This should create an object meeting following requirements:
- * 1) objects created for the same index are equals and never equals for different indexes
- * 2) this class is saveable via a default SaveableStateRegistry on the platform
- * 3) this objects can't be equals to any object which could be provided by a user as a custom key
- */
-internal expect fun getDefaultLazyKeyFor(index: Int): Any
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index b5f81ac..9a88f0c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -80,10 +80,10 @@
val itemScope = remember { LazyGridItemScopeImpl() }
- val stateOfItemsProvider = rememberStateOfItemsProvider(state, content, itemScope)
+ val itemsProvider = rememberItemsProvider(state, content, itemScope)
- val spanLayoutProvider = remember(stateOfItemsProvider) {
- derivedStateOf { LazyGridSpanLayoutProvider(stateOfItemsProvider.value) }
+ val spanLayoutProvider = remember(itemsProvider) {
+ derivedStateOf { LazyGridSpanLayoutProvider(itemsProvider) }
}
val scope = rememberCoroutineScope()
@@ -93,7 +93,7 @@
state.placementAnimator = placementAnimator
val measurePolicy = rememberLazyGridMeasurePolicy(
- stateOfItemsProvider,
+ itemsProvider,
state,
overScrollController,
spanLayoutProvider,
@@ -108,13 +108,13 @@
state.isVertical = isVertical
- ScrollPositionUpdater(stateOfItemsProvider, state)
+ ScrollPositionUpdater(itemsProvider, state)
LazyLayout(
modifier = modifier
.then(state.remeasurementModifier)
.lazyGridSemantics(
- stateOfItemsProvider = stateOfItemsProvider,
+ itemsProvider = itemsProvider,
state = state,
coroutineScope = scope,
isVertical = isVertical,
@@ -143,7 +143,7 @@
),
prefetchState = state.prefetchState,
measurePolicy = measurePolicy,
- itemsProvider = { stateOfItemsProvider.value }
+ itemsProvider = itemsProvider
)
}
@@ -151,10 +151,9 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun ScrollPositionUpdater(
- stateOfItemsProvider: State<LazyGridItemsProvider>,
+ itemsProvider: LazyGridItemsProvider,
state: LazyGridState
) {
- val itemsProvider = stateOfItemsProvider.value
if (itemsProvider.itemsCount > 0) {
state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
}
@@ -163,8 +162,8 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun rememberLazyGridMeasurePolicy(
- /** State containing the items provider of the list. */
- stateOfItemsProvider: State<LazyGridItemsProvider>,
+ /** Items provider of the list. */
+ itemsProvider: LazyGridItemsProvider,
/** The state of the list. */
state: LazyGridState,
/** The overscroll controller. */
@@ -216,7 +215,6 @@
val afterContentPadding = totalMainAxisPadding - beforeContentPadding
val contentConstraints = constraints.offset(-totalHorizontalPadding, -totalVerticalPadding)
- val itemsProvider = stateOfItemsProvider.value
state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
val spanLayoutProvider = stateOfSpanLayoutProvider.value
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProvider.kt
index 7ac289d..d5ba6bf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProvider.kt
@@ -19,7 +19,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.layout.LazyLayoutItemsProvider
-@OptIn(ExperimentalFoundationApi::class)
+@ExperimentalFoundationApi
internal interface LazyGridItemsProvider : LazyLayoutItemsProvider {
/** Returns the span for the given [index] */
fun LazyGridItemSpanScope.getSpan(index: Int): GridItemSpan
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
index 945d364..f408b08 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
@@ -17,9 +17,9 @@
package androidx.compose.foundation.lazy.grid
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.lazy.getDefaultLazyKeyFor
import androidx.compose.foundation.lazy.layout.IntervalHolder
import androidx.compose.foundation.lazy.layout.IntervalList
+import androidx.compose.foundation.lazy.layout.getDefaultLazyLayoutKey
import androidx.compose.foundation.lazy.layout.intervalForIndex
import androidx.compose.foundation.lazy.layout.intervalIndexForItemIndex
import androidx.compose.runtime.Composable
@@ -30,15 +30,14 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
-import kotlinx.coroutines.flow.collect
-@OptIn(ExperimentalFoundationApi::class)
+@ExperimentalFoundationApi
@Composable
-internal fun rememberStateOfItemsProvider(
+internal fun rememberItemsProvider(
state: LazyGridState,
content: LazyGridScope.() -> Unit,
itemScope: LazyGridItemScope
-): State<LazyGridItemsProvider> {
+): LazyGridItemsProvider {
val latestContent = rememberUpdatedState(content)
val nearestItemsRangeState = remember(state) {
mutableStateOf(
@@ -51,56 +50,58 @@
// recreated when the state is updated with a new range.
.collect { nearestItemsRangeState.value = it }
}
- return remember(nearestItemsRangeState, itemScope) {
- derivedStateOf<LazyGridItemsProvider> {
- val listScope = LazyGridScopeImpl().apply(latestContent.value)
- LazyGridItemsProviderImpl(
- itemScope,
- listScope.intervals,
- listScope.hasCustomSpans,
- nearestItemsRangeState.value
- )
- }
+ return remember(nearestItemsRangeState) {
+ LazyGridItemsProviderImpl(
+ derivedStateOf {
+ val listScope = LazyGridScopeImpl().apply(latestContent.value)
+ LazyGridItemsSnapshot(
+ itemScope,
+ listScope.intervals,
+ listScope.hasCustomSpans,
+ nearestItemsRangeState.value
+ )
+ }
+ )
}
}
-@OptIn(ExperimentalFoundationApi::class)
-internal class LazyGridItemsProviderImpl(
+@ExperimentalFoundationApi
+internal class LazyGridItemsSnapshot(
private val itemScope: LazyGridItemScope,
private val intervals: IntervalList<LazyGridIntervalContent>,
- override val hasCustomSpans: Boolean,
+ val hasCustomSpans: Boolean,
nearestItemsRange: IntRange
-) : LazyGridItemsProvider {
+) {
/**
* Caches the last interval we binary searched for. We might not need to recalculate
* for subsequent queries, as they tend to be localised.
*/
private var lastInterval: IntervalHolder<LazyGridIntervalContent>? = null
- override val itemsCount get() = intervals.totalSize
+ val itemsCount get() = intervals.totalSize
- override fun getKey(index: Int): Any {
+ fun getKey(index: Int): Any {
val interval = getIntervalForIndex(index)
val localIntervalIndex = index - interval.startIndex
val key = interval.content.key?.invoke(localIntervalIndex)
- return key ?: getDefaultLazyKeyFor(index)
+ return key ?: getDefaultLazyLayoutKey(index)
}
- override fun LazyGridItemSpanScope.getSpan(index: Int): GridItemSpan {
+ fun LazyGridItemSpanScope.getSpan(index: Int): GridItemSpan {
val interval = getIntervalForIndex(index)
val localIntervalIndex = index - interval.startIndex
return interval.content.span.invoke(this, localIntervalIndex)
}
- override fun getContent(index: Int): @Composable () -> Unit {
+ fun getContent(index: Int): @Composable () -> Unit {
val interval = getIntervalForIndex(index)
val localIntervalIndex = index - interval.startIndex
return interval.content.content.invoke(itemScope, localIntervalIndex)
}
- override val keyToIndexMap: Map<Any, Int> = generateKeyToIndexMap(nearestItemsRange, intervals)
+ val keyToIndexMap: Map<Any, Int> = generateKeyToIndexMap(nearestItemsRange, intervals)
- override fun getContentType(index: Int): Any? {
+ fun getContentType(index: Int): Any? {
val interval = getIntervalForIndex(index)
val localIntervalIndex = index - interval.startIndex
return interval.content.type.invoke(localIntervalIndex)
@@ -115,6 +116,27 @@
}
}
+@ExperimentalFoundationApi
+internal class LazyGridItemsProviderImpl(
+ private val itemsSnapshot: State<LazyGridItemsSnapshot>
+) : LazyGridItemsProvider {
+
+ override val itemsCount get() = itemsSnapshot.value.itemsCount
+
+ override fun getKey(index: Int) = itemsSnapshot.value.getKey(index)
+
+ override fun LazyGridItemSpanScope.getSpan(index: Int): GridItemSpan =
+ with(itemsSnapshot.value) { getSpan(index) }
+
+ override val hasCustomSpans: Boolean get() = itemsSnapshot.value.hasCustomSpans
+
+ override fun getContent(index: Int) = itemsSnapshot.value.getContent(index)
+
+ override val keyToIndexMap: Map<Any, Int> get() = itemsSnapshot.value.keyToIndexMap
+
+ override fun getContentType(index: Int) = itemsSnapshot.value.getContentType(index)
+}
+
/**
* Traverses the interval [list] in order to create a mapping from the key to the index for all
* the indexes in the passed [range].
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
index bd6578d..83a321e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.grid
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.mutableStateOf
/**
@@ -28,6 +29,7 @@
* block as otherwise the extra remeasurement will be scheduled once we update the values in the
* end of the measure block.
*/
+@OptIn(ExperimentalFoundationApi::class)
internal class LazyGridScrollPosition(
initialIndex: Int = 0,
initialScrollOffset: Int = 0
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
index e0f1d09..19bf588 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
@@ -20,7 +20,6 @@
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.CollectionInfo
@@ -39,7 +38,7 @@
@Suppress("ComposableModifierFactory", "ModifierInspectorInfo")
@Composable
internal fun Modifier.lazyGridSemantics(
- stateOfItemsProvider: State<LazyGridItemsProvider>,
+ itemsProvider: LazyGridItemsProvider,
state: LazyGridState,
coroutineScope: CoroutineScope,
isVertical: Boolean,
@@ -47,16 +46,16 @@
userScrollEnabled: Boolean
) = this.then(
remember(
- stateOfItemsProvider,
+ itemsProvider,
state,
isVertical,
reverseScrolling,
userScrollEnabled
) {
val indexForKeyMapping: (Any) -> Int = { needle ->
- val key = stateOfItemsProvider.value::getKey
+ val key = itemsProvider::getKey
var result = -1
- for (index in 0 until stateOfItemsProvider.value.itemsCount) {
+ for (index in 0 until itemsProvider.itemsCount) {
if (key(index) == needle) {
result = index
break
@@ -77,7 +76,7 @@
if (state.canScrollForward) {
// If we can scroll further, we don't know the end yet,
// but it's upper bounded by #items + 1
- stateOfItemsProvider.value.itemsCount + 1f
+ itemsProvider.itemsCount + 1f
} else {
// If we can't scroll further, the current value is the max
state.firstVisibleItemIndex + state.firstVisibleItemScrollOffset / 100_000f
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
index b924ba2..24ecffe 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
@@ -35,7 +35,7 @@
@ExperimentalFoundationApi
@Composable
internal fun LazyLayout(
- itemsProvider: () -> LazyLayoutItemsProvider,
+ itemsProvider: LazyLayoutItemsProvider,
modifier: Modifier = Modifier,
prefetchState: LazyLayoutPrefetchState? = null,
measurePolicy: LazyLayoutMeasureScope.(Constraints) -> MeasureResult
@@ -44,7 +44,7 @@
val saveableStateHolder = rememberSaveableStateHolder()
val itemContentFactory = remember {
- LazyLayoutItemContentFactory(saveableStateHolder) { currentItemsProvider.value.invoke() }
+ LazyLayoutItemContentFactory(saveableStateHolder) { currentItemsProvider.value }
}
val subcomposeLayoutState = remember {
SubcomposeLayoutState(LazyLayoutItemReusePolicy(itemContentFactory))
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
index d4ec9b9..89a0c7f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
@@ -27,7 +27,11 @@
/** The total number of items in the lazy layout (visible or not). */
val itemsCount: Int
- /** Returns the key for the item on this index */
+ /**
+ * Returns the key for the item on this index.
+ *
+ * @see getDefaultLazyLayoutKey which you can use if the user didn't provide a key.
+ */
fun getKey(index: Int): Any
/**
@@ -42,3 +46,11 @@
**/
fun getContentType(index: Int): Any?
}
+
+/**
+ * This creates an object meeting following requirements:
+ * 1) Objects created for the same index are equals and never equals for different indexes.
+ * 2) This class is saveable via a default SaveableStateRegistry on the platform.
+ * 3) This objects can't be equals to any object which could be provided by a user as a custom key.
+ */
+internal expect fun getDefaultLazyLayoutKey(index: Int): Any
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt
index 16a423c..b346ad1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt
@@ -33,7 +33,6 @@
import androidx.compose.foundation.lazy.layout.LazyLayout
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -82,7 +81,7 @@
val itemScope: Ref<LazyItemScopeImpl> = remember { Ref() }
- val stateOfItemsProvider = rememberStateOfItemsProvider(state, content, itemScope)
+ val itemsProvider = rememberItemsProvider(state, content, itemScope)
val scope = rememberCoroutineScope()
val placementAnimator = remember(state, isVertical) {
@@ -91,7 +90,7 @@
state.placementAnimator = placementAnimator
val measurePolicy = rememberLazyListMeasurePolicy(
- stateOfItemsProvider,
+ itemsProvider,
itemScope,
state,
overScrollController,
@@ -105,13 +104,13 @@
placementAnimator
)
- ScrollPositionUpdater(stateOfItemsProvider, state)
+ ScrollPositionUpdater(itemsProvider, state)
LazyLayout(
modifier = modifier
.then(state.remeasurementModifier)
.lazyListSemantics(
- stateOfItemsProvider = stateOfItemsProvider,
+ itemsProvider = itemsProvider,
state = state,
coroutineScope = scope,
isVertical = isVertical,
@@ -140,7 +139,7 @@
),
prefetchState = state.prefetchState,
measurePolicy = measurePolicy,
- itemsProvider = { stateOfItemsProvider.value }
+ itemsProvider = itemsProvider
)
}
@@ -148,10 +147,9 @@
@ExperimentalFoundationApi
@Composable
private fun ScrollPositionUpdater(
- stateOfItemsProvider: State<LazyListItemsProvider>,
+ itemsProvider: LazyListItemsProvider,
state: LazyListState
) {
- val itemsProvider = stateOfItemsProvider.value
if (itemsProvider.itemsCount > 0) {
state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
}
@@ -160,8 +158,8 @@
@ExperimentalFoundationApi
@Composable
private fun rememberLazyListMeasurePolicy(
- /** State containing the items provider of the list. */
- stateOfItemsProvider: State<LazyListItemsProvider>,
+ /** Items provider of the list. */
+ itemsProvider: LazyListItemsProvider,
/** Value holder for the item scope used to compose items. */
itemScope: Ref<LazyItemScopeImpl>,
/** The state of the list. */
@@ -217,7 +215,6 @@
val contentConstraints =
containerConstraints.offset(-totalHorizontalPadding, -totalVerticalPadding)
- val itemsProvider = stateOfItemsProvider.value
state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
// Update the state's cached Density
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyListItemsProviderImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyListItemsProviderImpl.kt
index a10897a..66a7c51 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyListItemsProviderImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyListItemsProviderImpl.kt
@@ -19,9 +19,9 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.foundation.lazy.getDefaultLazyKeyFor
import androidx.compose.foundation.lazy.layout.IntervalHolder
import androidx.compose.foundation.lazy.layout.IntervalList
+import androidx.compose.foundation.lazy.layout.getDefaultLazyLayoutKey
import androidx.compose.foundation.lazy.layout.intervalForIndex
import androidx.compose.foundation.lazy.layout.intervalIndexForItemIndex
import androidx.compose.runtime.Composable
@@ -33,15 +33,14 @@
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.node.Ref
-import kotlinx.coroutines.flow.collect
@ExperimentalFoundationApi
@Composable
-internal fun rememberStateOfItemsProvider(
+internal fun rememberItemsProvider(
state: LazyListState,
content: LazyListScope.() -> Unit,
itemScope: Ref<LazyItemScopeImpl>
-): State<LazyListItemsProvider> {
+): LazyListItemsProvider {
val latestContent = rememberUpdatedState(content)
val nearestItemsRangeState = remember(state) {
mutableStateOf(
@@ -55,53 +54,34 @@
.collect { nearestItemsRangeState.value = it }
}
return remember(nearestItemsRangeState) {
- derivedStateOf<LazyListItemsProvider> {
- val listScope = LazyListScopeImpl().apply(latestContent.value)
- LazyListItemsProviderImpl(
- itemScope,
- listScope.intervals,
- listScope.headerIndexes,
- nearestItemsRangeState.value
- )
- }
+ LazyListItemsProviderImpl(
+ derivedStateOf {
+ val listScope = LazyListScopeImpl().apply(latestContent.value)
+ LazyListItemsSnapshot(
+ itemScope,
+ listScope.intervals,
+ listScope.headerIndexes,
+ nearestItemsRangeState.value
+ )
+ }
+ )
}
}
@ExperimentalFoundationApi
-internal class LazyListItemsProviderImpl(
+internal class LazyListItemsSnapshot(
private val itemScope: Ref<LazyItemScopeImpl>,
private val list: IntervalList<LazyListIntervalContent>,
- override val headerIndexes: List<Int>,
+ val headerIndexes: List<Int>,
nearestItemsRange: IntRange
-) : LazyListItemsProvider {
+) {
/**
* Caches the last interval we binary searched for. We might not need to recalculate
* for subsequent queries, as they tend to be localised.
*/
private var lastInterval: IntervalHolder<LazyListIntervalContent>? = null
- override val itemsCount get() = list.totalSize
-
- override fun getKey(index: Int): Any {
- val interval = getIntervalForIndex(index)
- val localIntervalIndex = index - interval.startIndex
- val key = interval.content.key?.invoke(localIntervalIndex)
- return key ?: getDefaultLazyKeyFor(index)
- }
-
- override fun getContent(index: Int): @Composable () -> Unit {
- val interval = getIntervalForIndex(index)
- val localIntervalIndex = index - interval.startIndex
- return interval.content.content.invoke(itemScope.value!!, localIntervalIndex)
- }
-
- override val keyToIndexMap: Map<Any, Int> = generateKeyToIndexMap(nearestItemsRange, list)
-
- override fun getContentType(index: Int): Any? {
- val interval = getIntervalForIndex(index)
- val localIntervalIndex = index - interval.startIndex
- return interval.content.type.invoke(localIntervalIndex)
- }
+ val itemsCount get() = list.totalSize
private fun getIntervalForIndex(itemIndex: Int) = lastInterval.let {
if (it != null && itemIndex in it.startIndex until it.startIndex + it.size) {
@@ -110,6 +90,45 @@
list.intervalForIndex(itemIndex).also { lastInterval = it }
}
}
+
+ fun getKey(index: Int): Any {
+ val interval = getIntervalForIndex(index)
+ val localIntervalIndex = index - interval.startIndex
+ val key = interval.content.key?.invoke(localIntervalIndex)
+ return key ?: getDefaultLazyLayoutKey(index)
+ }
+
+ fun getContent(index: Int): @Composable () -> Unit {
+ val interval = getIntervalForIndex(index)
+ val localIntervalIndex = index - interval.startIndex
+ return interval.content.content.invoke(itemScope.value!!, localIntervalIndex)
+ }
+
+ val keyToIndexMap: Map<Any, Int> = generateKeyToIndexMap(nearestItemsRange, list)
+
+ fun getContentType(index: Int): Any? {
+ val interval = getIntervalForIndex(index)
+ val localIntervalIndex = index - interval.startIndex
+ return interval.content.type.invoke(localIntervalIndex)
+ }
+}
+
+@ExperimentalFoundationApi
+internal class LazyListItemsProviderImpl(
+ private val itemsSnapshot: State<LazyListItemsSnapshot>
+) : LazyListItemsProvider {
+
+ override val headerIndexes: List<Int> get() = itemsSnapshot.value.headerIndexes
+
+ override val itemsCount get() = itemsSnapshot.value.itemsCount
+
+ override fun getKey(index: Int) = itemsSnapshot.value.getKey(index)
+
+ override fun getContent(index: Int) = itemsSnapshot.value.getContent(index)
+
+ override val keyToIndexMap: Map<Any, Int> get() = itemsSnapshot.value.keyToIndexMap
+
+ override fun getContentType(index: Int) = itemsSnapshot.value.getContentType(index)
}
/**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazySemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazySemantics.kt
index 6023028..ec02f79 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazySemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazySemantics.kt
@@ -21,7 +21,6 @@
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.CollectionInfo
@@ -40,7 +39,7 @@
@Suppress("ComposableModifierFactory", "ModifierInspectorInfo")
@Composable
internal fun Modifier.lazyListSemantics(
- stateOfItemsProvider: State<LazyListItemsProvider>,
+ itemsProvider: LazyListItemsProvider,
state: LazyListState,
coroutineScope: CoroutineScope,
isVertical: Boolean,
@@ -48,16 +47,16 @@
userScrollEnabled: Boolean
) = this.then(
remember(
- stateOfItemsProvider,
+ itemsProvider,
state,
isVertical,
reverseScrolling,
userScrollEnabled
) {
val indexForKeyMapping: (Any) -> Int = { needle ->
- val key = stateOfItemsProvider.value::getKey
+ val key = itemsProvider::getKey
var result = -1
- for (index in 0 until stateOfItemsProvider.value.itemsCount) {
+ for (index in 0 until itemsProvider.itemsCount) {
if (key(index) == needle) {
result = index
break
@@ -78,7 +77,7 @@
if (state.canScrollForward) {
// If we can scroll further, we don't know the end yet,
// but it's upper bounded by #items + 1
- stateOfItemsProvider.value.itemsCount + 1f
+ itemsProvider.itemsCount + 1f
} else {
// If we can't scroll further, the current value is the max
state.firstVisibleItemIndex + state.firstVisibleItemScrollOffset / 100_000f
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/Lazy.desktop.kt
similarity index 83%
rename from compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
rename to compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/Lazy.desktop.kt
index 8f78d81..94a516a 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/Lazy.desktop.kt
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-package androidx.compose.foundation.lazy
+package androidx.compose.foundation.lazy.layout
-internal actual fun getDefaultLazyKeyFor(index: Int): Any = DefaultLazyKey(index)
+internal actual fun getDefaultLazyLayoutKey(index: Int): Any = DefaultLazyKey(index)
private data class DefaultLazyKey(private val index: Int)
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
index 42b5799..eef02a1 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
@@ -524,7 +524,12 @@
getProperty("textDirection"),
getProperty("lineHeight"),
getProperty("textIndent"),
- getProperty("platformStyle")
+ getProperty("platformStyle"),
+ // ParagraphStyle and SpanStyle properties are already compared, TextStyle should have
+ // paragraph style attributes is tested in:
+ // ui-text/../androidx/compose/ui/text/TextSpanParagraphStyleTest.kt
+ getProperty("paragraphStyle"),
+ getProperty("spanStyle")
)
val textStyleProperties = TextStyle::class.memberProperties.map { Property(it) }
diff --git a/compose/material/material-window/api/current.txt b/compose/material/material-window/api/current.txt
index c1c9c45..f519e63 100644
--- a/compose/material/material-window/api/current.txt
+++ b/compose/material/material-window/api/current.txt
@@ -9,19 +9,19 @@
}
public static final class HeightSizeClass.Companion {
- method public int getCompact();
- method public int getExpanded();
- method public int getMedium();
- property public final int Compact;
- property public final int Expanded;
- property public final int Medium;
+ method public String getCompact();
+ method public String getExpanded();
+ method public String getMedium();
+ property public final String Compact;
+ property public final String Expanded;
+ property public final String Medium;
}
@androidx.compose.runtime.Immutable public final class SizeClass {
- method public int getHeight();
- method public int getWidth();
- property public final int height;
- property public final int width;
+ method public String getHeight();
+ method public String getWidth();
+ property public final String height;
+ property public final String width;
field public static final androidx.compose.material.window.SizeClass.Companion Companion;
}
@@ -36,12 +36,12 @@
}
public static final class WidthSizeClass.Companion {
- method public int getCompact();
- method public int getExpanded();
- method public int getMedium();
- property public final int Compact;
- property public final int Expanded;
- property public final int Medium;
+ method public String getCompact();
+ method public String getExpanded();
+ method public String getMedium();
+ property public final String Compact;
+ property public final String Expanded;
+ property public final String Medium;
}
}
diff --git a/compose/material/material-window/api/public_plus_experimental_current.txt b/compose/material/material-window/api/public_plus_experimental_current.txt
index 4692a13..a80d3fc 100644
--- a/compose/material/material-window/api/public_plus_experimental_current.txt
+++ b/compose/material/material-window/api/public_plus_experimental_current.txt
@@ -2,9 +2,7 @@
package androidx.compose.material.window {
public final class AndroidSizeClass_androidKt {
- method @androidx.compose.material.window.ExperimentalMaterialWindowApi @androidx.compose.runtime.Composable public static int rememberHeightSizeClass(android.app.Activity);
- method @androidx.compose.material.window.ExperimentalMaterialWindowApi @androidx.compose.runtime.Composable public static androidx.compose.material.window.SizeClass rememberSizeClass(android.app.Activity);
- method @androidx.compose.material.window.ExperimentalMaterialWindowApi @androidx.compose.runtime.Composable public static int rememberWidthSizeClass(android.app.Activity);
+ method @androidx.compose.material.window.ExperimentalMaterialWindowApi @androidx.compose.runtime.Composable public static androidx.compose.material.window.SizeClass calculateSizeClass(android.app.Activity);
}
@kotlin.RequiresOptIn(message="This material-window API is experimental and is likely to change or to be removed in" + " the future.") public @interface ExperimentalMaterialWindowApi {
@@ -15,19 +13,19 @@
}
public static final class HeightSizeClass.Companion {
- method public int getCompact();
- method public int getExpanded();
- method public int getMedium();
- property public final int Compact;
- property public final int Expanded;
- property public final int Medium;
+ method public String getCompact();
+ method public String getExpanded();
+ method public String getMedium();
+ property public final String Compact;
+ property public final String Expanded;
+ property public final String Medium;
}
@androidx.compose.runtime.Immutable public final class SizeClass {
- method public int getHeight();
- method public int getWidth();
- property public final int height;
- property public final int width;
+ method public String getHeight();
+ method public String getWidth();
+ property public final String height;
+ property public final String width;
field public static final androidx.compose.material.window.SizeClass.Companion Companion;
}
@@ -43,12 +41,12 @@
}
public static final class WidthSizeClass.Companion {
- method public int getCompact();
- method public int getExpanded();
- method public int getMedium();
- property public final int Compact;
- property public final int Expanded;
- property public final int Medium;
+ method public String getCompact();
+ method public String getExpanded();
+ method public String getMedium();
+ property public final String Compact;
+ property public final String Expanded;
+ property public final String Medium;
}
}
diff --git a/compose/material/material-window/api/restricted_current.txt b/compose/material/material-window/api/restricted_current.txt
index c1c9c45..f519e63 100644
--- a/compose/material/material-window/api/restricted_current.txt
+++ b/compose/material/material-window/api/restricted_current.txt
@@ -9,19 +9,19 @@
}
public static final class HeightSizeClass.Companion {
- method public int getCompact();
- method public int getExpanded();
- method public int getMedium();
- property public final int Compact;
- property public final int Expanded;
- property public final int Medium;
+ method public String getCompact();
+ method public String getExpanded();
+ method public String getMedium();
+ property public final String Compact;
+ property public final String Expanded;
+ property public final String Medium;
}
@androidx.compose.runtime.Immutable public final class SizeClass {
- method public int getHeight();
- method public int getWidth();
- property public final int height;
- property public final int width;
+ method public String getHeight();
+ method public String getWidth();
+ property public final String height;
+ property public final String width;
field public static final androidx.compose.material.window.SizeClass.Companion Companion;
}
@@ -36,12 +36,12 @@
}
public static final class WidthSizeClass.Companion {
- method public int getCompact();
- method public int getExpanded();
- method public int getMedium();
- property public final int Compact;
- property public final int Expanded;
- property public final int Medium;
+ method public String getCompact();
+ method public String getExpanded();
+ method public String getMedium();
+ property public final String Compact;
+ property public final String Expanded;
+ property public final String Medium;
}
}
diff --git a/compose/material/material-window/build.gradle b/compose/material/material-window/build.gradle
index 5700dd6..aeab013 100644
--- a/compose/material/material-window/build.gradle
+++ b/compose/material/material-window/build.gradle
@@ -49,6 +49,8 @@
androidTestImplementation(libs.testRunner)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.truth)
+
+ samples(project(":compose:material:material-window:material-window-samples"))
}
}
@@ -95,6 +97,9 @@
}
}
}
+ dependencies {
+ samples(project(":compose:material:material-window:material-window-samples"))
+ }
}
androidx {
diff --git a/compose/material/material-window/samples/build.gradle b/compose/material/material-window/samples/build.gradle
new file mode 100644
index 0000000..4272a48
--- /dev/null
+++ b/compose/material/material-window/samples/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXComposePlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ kotlinPlugin(project(":compose:compiler:compiler"))
+
+ implementation(libs.kotlinStdlib)
+
+ compileOnly(project(":annotation:annotation-sampled"))
+
+ implementation(project(":compose:material:material-window"))
+ implementation(project(":compose:runtime:runtime"))
+ implementation("androidx.activity:activity-compose:1.3.1")
+}
+
+androidx {
+ name = "AndroidX Compose Material Window Samples"
+ type = LibraryType.SAMPLES
+ mavenGroup = LibraryGroups.COMPOSE_MATERIAL
+ inceptionYear = "2022"
+ description = "Contains the sample code for the AndroidX Material Window APIs."
+}
+
+android {
+ namespace "androidx.compose.material.window.samples"
+}
diff --git a/compose/material/material-window/samples/src/main/AndroidManifest.xml b/compose/material/material-window/samples/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a7cd314
--- /dev/null
+++ b/compose/material/material-window/samples/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest />
diff --git a/compose/material/material-window/samples/src/main/java/androidx/compose/material/window/samples/SizeClassSamples.kt b/compose/material/material-window/samples/src/main/java/androidx/compose/material/window/samples/SizeClassSamples.kt
new file mode 100644
index 0000000..5a6b869
--- /dev/null
+++ b/compose/material/material-window/samples/src/main/java/androidx/compose/material/window/samples/SizeClassSamples.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.window.samples
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.annotation.Sampled
+import androidx.compose.material.window.ExperimentalMaterialWindowApi
+import androidx.compose.material.window.WidthSizeClass
+import androidx.compose.material.window.calculateSizeClass
+import androidx.compose.runtime.Composable
+
+@OptIn(ExperimentalMaterialWindowApi::class)
+@Sampled
+fun AndroidSizeClassSample() {
+ class MyActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ // Calculate the size class for the activity's current window. If the window size
+ // changes, for example when the device is rotated, the value returned by
+ // calculateSizeClass will also change.
+ val sizeClass = calculateSizeClass()
+ // Perform logic on the size class to decide whether to show the top app bar.
+ val showTopAppBar = sizeClass.width != WidthSizeClass.Compact
+
+ // MyScreen knows nothing about window sizes, and performs logic based on a Boolean
+ // flag.
+ MyScreen(showTopAppBar = showTopAppBar)
+ }
+ }
+ }
+}
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun MyScreen(showTopAppBar: Boolean) {}
diff --git a/compose/material/material-window/src/androidAndroidTest/kotlin/androidx/compose/material/window/AndroidSizeClassTest.kt b/compose/material/material-window/src/androidAndroidTest/kotlin/androidx/compose/material/window/AndroidSizeClassTest.kt
index fe1039b..53a46f1 100644
--- a/compose/material/material-window/src/androidAndroidTest/kotlin/androidx/compose/material/window/AndroidSizeClassTest.kt
+++ b/compose/material/material-window/src/androidAndroidTest/kotlin/androidx/compose/material/window/AndroidSizeClassTest.kt
@@ -43,7 +43,7 @@
fun widthSizeClass_correctCalculation() {
var actualWidthSizeClass: WidthSizeClass? = null
rule.setContent {
- actualWidthSizeClass = rule.activity.rememberWidthSizeClass()
+ actualWidthSizeClass = rule.activity.calculateSizeClass().width
}
rule.runOnIdle {
@@ -61,7 +61,7 @@
fun heightSizeClass_correctCalculation() {
var actualHeightSizeClass: HeightSizeClass? = null
rule.setContent {
- actualHeightSizeClass = rule.activity.rememberHeightSizeClass()
+ actualHeightSizeClass = rule.activity.calculateSizeClass().height
}
rule.runOnIdle {
@@ -83,7 +83,7 @@
val density = mutableStateOf(Density(1f))
rule.setContent {
CompositionLocalProvider(LocalDensity provides density.value) {
- actualSizeClass = rule.activity.rememberSizeClass()
+ actualSizeClass = rule.activity.calculateSizeClass()
}
}
diff --git a/compose/material/material-window/src/androidMain/kotlin/androidx/compose/material/window/AndroidSizeClass.android.kt b/compose/material/material-window/src/androidMain/kotlin/androidx/compose/material/window/AndroidSizeClass.android.kt
index 7695a08..be27854 100644
--- a/compose/material/material-window/src/androidMain/kotlin/androidx/compose/material/window/AndroidSizeClass.android.kt
+++ b/compose/material/material-window/src/androidMain/kotlin/androidx/compose/material/window/AndroidSizeClass.android.kt
@@ -21,50 +21,26 @@
import androidx.compose.ui.graphics.toComposeRect
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.runtime.remember
import androidx.window.layout.WindowMetricsCalculator
/**
- * Calculates [SizeClass] of the window.
+ * Calculates the window's [SizeClass] for this [Activity].
*
- * Whenever device configutation changes result in change of the width or height based
- * size classes, for example on device rotation or window resizing, this will return a new
- * [SizeClass] instance.
+ * A new [SizeClass] will be returned whenever a configuration change causes the width or height of
+ * the window to cross a breakpoint, such as when the device is rotated or the window is resized.
+ *
+ * @sample androidx.compose.material.window.samples.AndroidSizeClassSample
*/
@ExperimentalMaterialWindowApi
@Composable
-fun Activity.rememberSizeClass(): SizeClass {
- // observe configuration changes and recalculate size class on corresponding changes
- val configuration = LocalConfiguration.current
+fun Activity.calculateSizeClass(): SizeClass {
+ // Observe view configuration changes and recalculate the size class on each change. We can't
+ // use Activity#onConfigurationChanged as this will sometimes fail to be called on different
+ // API levels, hence why this function needs to be @Composable so we can observe the
+ // ComposeView's configuration changes.
+ LocalConfiguration.current
val density = LocalDensity.current
- return remember(
- configuration.screenLayout,
- configuration.screenHeightDp,
- configuration.screenWidthDp,
- configuration.orientation,
- configuration.densityDpi,
- density.density
- ) {
- val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
- val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
- SizeClass.calculateFromSize(size)
- }
+ val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
+ val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
+ return SizeClass.calculateFromSize(size)
}
-
-/**
- * Calculates [WidthSizeClass] of the window.
- *
- * @see rememberSizeClass
- */
-@ExperimentalMaterialWindowApi
-@Composable
-fun Activity.rememberWidthSizeClass(): WidthSizeClass = rememberSizeClass().width
-
-/**
- * Calculates [HeightSizeClass] of the window.
- *
- * @see rememberSizeClass
- */
-@ExperimentalMaterialWindowApi
-@Composable
-fun Activity.rememberHeightSizeClass(): HeightSizeClass = rememberSizeClass().height
\ No newline at end of file
diff --git a/compose/material/material-window/src/commonMain/kotlin/androidx/compose/material/window/SizeClass.kt b/compose/material/material-window/src/commonMain/kotlin/androidx/compose/material/window/SizeClass.kt
index 44aa060..840fa91 100644
--- a/compose/material/material-window/src/commonMain/kotlin/androidx/compose/material/window/SizeClass.kt
+++ b/compose/material/material-window/src/commonMain/kotlin/androidx/compose/material/window/SizeClass.kt
@@ -22,11 +22,17 @@
import androidx.compose.ui.unit.dp
/**
- * Window size classes are a set of opinionated viewport breakpoints to design, develop, and test resizable application layouts against.
+ * Window size classes are a set of opinionated viewport breakpoints to design, develop, and test
+ * responsive application layouts against.
* For more details check <a href="https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes" class="external" target="_blank">Support different screen sizes</a> documentation.
*
- * @param width width-based size class of the window
- * @param height height-based size class of the window
+ * SizeClass contains a [WidthSizeClass] and [HeightSizeClass], representing the size classes for
+ * this window's width and height respectively.
+ *
+ * See [calculateSizeClass] to calculate the size class for the Activity's current window
+ *
+ * @param width width-based window size class
+ * @param height height-based window size class
*/
@Immutable
class SizeClass private constructor(
@@ -35,7 +41,8 @@
) {
companion object {
/**
- * Calculates [SizeClass] for a given [size]. Should be used for testing purposes only
+ * Calculates [SizeClass] for a given [size]. Should be used for testing purposes only - to
+ * calculate a [SizeClass] for the Activity's current window see [calculateSizeClass].
*
* @param size of the window
* @return size class corresponding to the given width and height
@@ -71,33 +78,34 @@
}
/**
- * Width-based size class of the window.
+ * Width-based window size class.
*
* A size class represents a breakpoint that can be used to build responsive layouts. Each size
* class breakpoint represents a majority case for typical device scenarios so your layouts will
* work well on most devices and configurations.
*
- * For more details check <a href="https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes" class="external" target="_blank">Window size classes documentation</a>.
+ * For more details see <a href="https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes" class="external" target="_blank">Window size classes documentation</a>.
*/
@Immutable
@kotlin.jvm.JvmInline
-value class WidthSizeClass private constructor(private val value: Int) {
+value class WidthSizeClass private constructor(private val value: String) {
companion object {
/** Represents the majority of phones in portrait. */
- val Compact = WidthSizeClass(0)
+ val Compact = WidthSizeClass("Compact")
/**
- * Represents the majority of tablets in portrait and large unfolded inner displays in portrait.
+ * Represents the majority of tablets in portrait and large unfolded inner displays in
+ * portrait.
*/
- val Medium = WidthSizeClass(1)
+ val Medium = WidthSizeClass("Medium")
/**
* Represents the majority of tablets in landscape and large unfolded inner displays in
* landscape.
*/
- val Expanded = WidthSizeClass(2)
+ val Expanded = WidthSizeClass("Expanded")
- /** Calculates [WidthSizeClass] size class for given [width] */
+ /** Calculates the [WidthSizeClass] for a given [width] */
internal fun fromWidth(width: Dp): WidthSizeClass {
require(width >= 0.dp) { "Width must not be negative" }
return when {
@@ -110,28 +118,28 @@
}
/**
- * Height-based size class of the window.
+ * Height-based window size class.
*
* A size class represents a breakpoint that can be used to build responsive layouts. Each size
* class breakpoint represents a majority case for typical device scenarios so your layouts will
* work well on most devices and configurations.
*
- * For more details check <a href="https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes" class="external" target="_blank">Window size classes documentation</a>.
+ * For more details see <a href="https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes" class="external" target="_blank">Window size classes documentation</a>.
*/
@Immutable
@kotlin.jvm.JvmInline
-value class HeightSizeClass private constructor(private val value: Int) {
+value class HeightSizeClass private constructor(private val value: String) {
companion object {
/** Represents the majority of phones in landscape */
- val Compact = HeightSizeClass(0)
+ val Compact = HeightSizeClass("Compact")
/** Represents the majority of tablets in landscape and majority of phones in portrait */
- val Medium = HeightSizeClass(1)
+ val Medium = HeightSizeClass("Medium")
/** Represents the majority of tablets in portrait */
- val Expanded = HeightSizeClass(2)
+ val Expanded = HeightSizeClass("Expanded")
- /** Calculates [HeightSizeClass] size class for given [height] */
+ /** Calculates the [HeightSizeClass] for a given [height] */
internal fun fromHeight(height: Dp): HeightSizeClass {
require(height >= 0.dp) { "Height must not be negative" }
return when {
@@ -141,4 +149,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/compose/material/material/icons/generator/api/icons.txt b/compose/material/material/icons/generator/api/icons.txt
index d6c84bf..8ff7235 100644
--- a/compose/material/material/icons/generator/api/icons.txt
+++ b/compose/material/material/icons/generator/api/icons.txt
@@ -190,6 +190,7 @@
Filled.BikeScooter
Filled.Biotech
Filled.Blender
+Filled.Blinds
Filled.BlindsClosed
Filled.Block
Filled.Bloodtype
@@ -238,6 +239,8 @@
Filled.BrightnessHigh
Filled.BrightnessLow
Filled.BrightnessMedium
+Filled.BroadcastOnHome
+Filled.BroadcastOnPersonal
Filled.BrokenImage
Filled.BrowseGallery
Filled.BrowserNotSupported
@@ -458,6 +461,7 @@
Filled.Description
Filled.Deselect
Filled.DesignServices
+Filled.Desk
Filled.DesktopAccessDisabled
Filled.DesktopMac
Filled.DesktopWindows
@@ -1029,6 +1033,7 @@
Filled.Lock
Filled.LockClock
Filled.LockOpen
+Filled.LockPerson
Filled.LockReset
Filled.Login
Filled.LogoDev
@@ -1179,6 +1184,7 @@
Filled.NightlightRound
Filled.NightsStay
Filled.NoAccounts
+Filled.NoAdultContent
Filled.NoBackpack
Filled.NoCell
Filled.NoCrash
@@ -1957,6 +1963,7 @@
Filled.VpnKeyOff
Filled.VpnLock
Filled.Vrpano
+Filled.Wallet
Filled.Wallpaper
Filled.Warehouse
Filled.Warning
@@ -1990,6 +1997,9 @@
Filled.WheelchairPickup
Filled.WhereToVote
Filled.Widgets
+Filled.WidthFull
+Filled.WidthNormal
+Filled.WidthWide
Filled.Wifi
Filled.Wifi1Bar
Filled.Wifi2Bar
@@ -2034,6 +2044,7 @@
Filled._15mp
Filled._16mp
Filled._17mp
+Filled._18UpRating
Filled._18mp
Filled._19mp
Filled._1k
@@ -2272,6 +2283,7 @@
Outlined.BikeScooter
Outlined.Biotech
Outlined.Blender
+Outlined.Blinds
Outlined.BlindsClosed
Outlined.Block
Outlined.Bloodtype
@@ -2320,6 +2332,8 @@
Outlined.BrightnessHigh
Outlined.BrightnessLow
Outlined.BrightnessMedium
+Outlined.BroadcastOnHome
+Outlined.BroadcastOnPersonal
Outlined.BrokenImage
Outlined.BrowseGallery
Outlined.BrowserNotSupported
@@ -2540,6 +2554,7 @@
Outlined.Description
Outlined.Deselect
Outlined.DesignServices
+Outlined.Desk
Outlined.DesktopAccessDisabled
Outlined.DesktopMac
Outlined.DesktopWindows
@@ -3111,6 +3126,7 @@
Outlined.Lock
Outlined.LockClock
Outlined.LockOpen
+Outlined.LockPerson
Outlined.LockReset
Outlined.Login
Outlined.LogoDev
@@ -3261,6 +3277,7 @@
Outlined.NightlightRound
Outlined.NightsStay
Outlined.NoAccounts
+Outlined.NoAdultContent
Outlined.NoBackpack
Outlined.NoCell
Outlined.NoCrash
@@ -4039,6 +4056,7 @@
Outlined.VpnKeyOff
Outlined.VpnLock
Outlined.Vrpano
+Outlined.Wallet
Outlined.Wallpaper
Outlined.Warehouse
Outlined.Warning
@@ -4072,6 +4090,9 @@
Outlined.WheelchairPickup
Outlined.WhereToVote
Outlined.Widgets
+Outlined.WidthFull
+Outlined.WidthNormal
+Outlined.WidthWide
Outlined.Wifi
Outlined.Wifi1Bar
Outlined.Wifi2Bar
@@ -4116,6 +4137,7 @@
Outlined._15mp
Outlined._16mp
Outlined._17mp
+Outlined._18UpRating
Outlined._18mp
Outlined._19mp
Outlined._1k
@@ -4354,6 +4376,7 @@
Rounded.BikeScooter
Rounded.Biotech
Rounded.Blender
+Rounded.Blinds
Rounded.BlindsClosed
Rounded.Block
Rounded.Bloodtype
@@ -4402,6 +4425,8 @@
Rounded.BrightnessHigh
Rounded.BrightnessLow
Rounded.BrightnessMedium
+Rounded.BroadcastOnHome
+Rounded.BroadcastOnPersonal
Rounded.BrokenImage
Rounded.BrowseGallery
Rounded.BrowserNotSupported
@@ -4622,6 +4647,7 @@
Rounded.Description
Rounded.Deselect
Rounded.DesignServices
+Rounded.Desk
Rounded.DesktopAccessDisabled
Rounded.DesktopMac
Rounded.DesktopWindows
@@ -5193,6 +5219,7 @@
Rounded.Lock
Rounded.LockClock
Rounded.LockOpen
+Rounded.LockPerson
Rounded.LockReset
Rounded.Login
Rounded.LogoDev
@@ -5343,6 +5370,7 @@
Rounded.NightlightRound
Rounded.NightsStay
Rounded.NoAccounts
+Rounded.NoAdultContent
Rounded.NoBackpack
Rounded.NoCell
Rounded.NoCrash
@@ -6121,6 +6149,7 @@
Rounded.VpnKeyOff
Rounded.VpnLock
Rounded.Vrpano
+Rounded.Wallet
Rounded.Wallpaper
Rounded.Warehouse
Rounded.Warning
@@ -6154,6 +6183,9 @@
Rounded.WheelchairPickup
Rounded.WhereToVote
Rounded.Widgets
+Rounded.WidthFull
+Rounded.WidthNormal
+Rounded.WidthWide
Rounded.Wifi
Rounded.Wifi1Bar
Rounded.Wifi2Bar
@@ -6198,6 +6230,7 @@
Rounded._15mp
Rounded._16mp
Rounded._17mp
+Rounded._18UpRating
Rounded._18mp
Rounded._19mp
Rounded._1k
@@ -6436,6 +6469,7 @@
Sharp.BikeScooter
Sharp.Biotech
Sharp.Blender
+Sharp.Blinds
Sharp.BlindsClosed
Sharp.Block
Sharp.Bloodtype
@@ -6484,6 +6518,8 @@
Sharp.BrightnessHigh
Sharp.BrightnessLow
Sharp.BrightnessMedium
+Sharp.BroadcastOnHome
+Sharp.BroadcastOnPersonal
Sharp.BrokenImage
Sharp.BrowseGallery
Sharp.BrowserNotSupported
@@ -6704,6 +6740,7 @@
Sharp.Description
Sharp.Deselect
Sharp.DesignServices
+Sharp.Desk
Sharp.DesktopAccessDisabled
Sharp.DesktopMac
Sharp.DesktopWindows
@@ -7275,6 +7312,7 @@
Sharp.Lock
Sharp.LockClock
Sharp.LockOpen
+Sharp.LockPerson
Sharp.LockReset
Sharp.Login
Sharp.LogoDev
@@ -7425,6 +7463,7 @@
Sharp.NightlightRound
Sharp.NightsStay
Sharp.NoAccounts
+Sharp.NoAdultContent
Sharp.NoBackpack
Sharp.NoCell
Sharp.NoCrash
@@ -8203,6 +8242,7 @@
Sharp.VpnKeyOff
Sharp.VpnLock
Sharp.Vrpano
+Sharp.Wallet
Sharp.Wallpaper
Sharp.Warehouse
Sharp.Warning
@@ -8236,6 +8276,9 @@
Sharp.WheelchairPickup
Sharp.WhereToVote
Sharp.Widgets
+Sharp.WidthFull
+Sharp.WidthNormal
+Sharp.WidthWide
Sharp.Wifi
Sharp.Wifi1Bar
Sharp.Wifi2Bar
@@ -8280,6 +8323,7 @@
Sharp._15mp
Sharp._16mp
Sharp._17mp
+Sharp._18UpRating
Sharp._18mp
Sharp._19mp
Sharp._1k
@@ -8518,6 +8562,7 @@
TwoTone.BikeScooter
TwoTone.Biotech
TwoTone.Blender
+TwoTone.Blinds
TwoTone.BlindsClosed
TwoTone.Block
TwoTone.Bloodtype
@@ -8566,6 +8611,8 @@
TwoTone.BrightnessHigh
TwoTone.BrightnessLow
TwoTone.BrightnessMedium
+TwoTone.BroadcastOnHome
+TwoTone.BroadcastOnPersonal
TwoTone.BrokenImage
TwoTone.BrowseGallery
TwoTone.BrowserNotSupported
@@ -8786,6 +8833,7 @@
TwoTone.Description
TwoTone.Deselect
TwoTone.DesignServices
+TwoTone.Desk
TwoTone.DesktopAccessDisabled
TwoTone.DesktopMac
TwoTone.DesktopWindows
@@ -9357,6 +9405,7 @@
TwoTone.Lock
TwoTone.LockClock
TwoTone.LockOpen
+TwoTone.LockPerson
TwoTone.LockReset
TwoTone.Login
TwoTone.LogoDev
@@ -9507,6 +9556,7 @@
TwoTone.NightlightRound
TwoTone.NightsStay
TwoTone.NoAccounts
+TwoTone.NoAdultContent
TwoTone.NoBackpack
TwoTone.NoCell
TwoTone.NoCrash
@@ -10285,6 +10335,7 @@
TwoTone.VpnKeyOff
TwoTone.VpnLock
TwoTone.Vrpano
+TwoTone.Wallet
TwoTone.Wallpaper
TwoTone.Warehouse
TwoTone.Warning
@@ -10318,6 +10369,9 @@
TwoTone.WheelchairPickup
TwoTone.WhereToVote
TwoTone.Widgets
+TwoTone.WidthFull
+TwoTone.WidthNormal
+TwoTone.WidthWide
TwoTone.Wifi
TwoTone.Wifi1Bar
TwoTone.Wifi2Bar
@@ -10362,6 +10416,7 @@
TwoTone._15mp
TwoTone._16mp
TwoTone._17mp
+TwoTone._18UpRating
TwoTone._18mp
TwoTone._19mp
TwoTone._1k
diff --git a/compose/material/material/icons/generator/raw-icons/filled/18_up_rating.xml b/compose/material/material/icons/generator/raw-icons/filled/18_up_rating.xml
new file mode 100644
index 0000000..9157dca
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/18_up_rating.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,12.5h1.5v1.5h-1.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,10h1.5v1.5h-1.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM10,15H8.5v-4.5H7V9h3V15zM16,14c0,0.55 -0.45,1 -1,1h-2.5c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1H15c0.55,0 1,0.45 1,1V14z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/blinds.xml b/compose/material/material/icons/generator/raw-icons/filled/blinds.xml
new file mode 100644
index 0000000..02afa6f
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/blinds.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,19V3H4v16H2v2h20v-2H20zM16,9h2v2h-2V9zM14,11H6V9h8V11zM18,7h-2V5h2V7zM14,5v2H6V5H14zM6,19v-6h8v1.82c-0.45,0.32 -0.75,0.84 -0.75,1.43c0,0.97 0.78,1.75 1.75,1.75s1.75,-0.78 1.75,-1.75c0,-0.59 -0.3,-1.12 -0.75,-1.43V13h2v6H6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/broadcast_on_home.xml b/compose/material/material/icons/generator/raw-icons/filled/broadcast_on_home.xml
new file mode 100644
index 0000000..83c5ebb
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/broadcast_on_home.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,6c0,-1.1 -0.9,-2 -2,-2H4v2h16v2.59c0.73,0.29 1.4,0.69 2,1.17V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,9H3c-0.5,0 -1,0.5 -1,1v9c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-9C9,9.5 8.5,9 8,9zM7,18H4v-7h3V18z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.75,16.97c0.3,-0.23 0.5,-0.57 0.5,-0.97c0,-0.69 -0.56,-1.25 -1.25,-1.25s-1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5V16.97z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,13.5c1.38,0 2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16c0,-2.21 -1.79,-4 -4,-4c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77C14.5,14.62 15.62,13.5 17,13.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/broadcast_on_personal.xml b/compose/material/material/icons/generator/raw-icons/filled/broadcast_on_personal.xml
new file mode 100644
index 0000000..ddc8dafc
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/broadcast_on_personal.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,8c0.7,0 1.38,0.1 2.02,0.27L12,3L4,9v12h6.76C9.66,19.63 9,17.89 9,16C9,11.58 12.58,8 17,8z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,14.75c-0.69,0 -1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5v-5.03c0.3,-0.23 0.5,-0.57 0.5,-0.97C18.25,15.31 17.69,14.75 17,14.75z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16C21,13.79 19.21,12 17,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/desk.xml b/compose/material/material/icons/generator/raw-icons/filled/desk.xml
new file mode 100644
index 0000000..c6f9813
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/desk.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2,6v12h2V8h10v10h2v-2h4v2h2V6H2zM20,8v2h-4V8H20zM16,14v-2h4v2H16z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/lock_person.xml b/compose/material/material/icons/generator/raw-icons/filled/lock_person.xml
new file mode 100644
index 0000000..86b3106
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/lock_person.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,11c0.7,0 1.37,0.1 2,0.29V10c0,-1.1 -0.9,-2 -2,-2h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h6.26C11.47,20.87 11,19.49 11,18C11,14.13 14.13,11 18,11zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H8.9V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM18,15c0.83,0 1.5,0.67 1.5,1.5S18.83,18 18,18s-1.5,-0.67 -1.5,-1.5S17.17,15 18,15zM18,21c-1.03,0 -1.94,-0.52 -2.48,-1.32C16.25,19.26 17.09,19 18,19s1.75,0.26 2.48,0.68C19.94,20.48 19.03,21 18,21z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/no_adult_content.xml b/compose/material/material/icons/generator/raw-icons/filled/no_adult_content.xml
new file mode 100644
index 0000000..27a37a3
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/no_adult_content.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-1.85 0.63,-3.54 1.69,-4.9L7.59,9h2.83L7.1,5.69C8.46,4.63 10.15,4 12,4c4.41,0 8,3.59 8,8c0,1.85 -0.63,3.54 -1.69,4.9l-1.9,-1.9h-2.83l3.31,3.31C15.54,19.37 13.85,20 12,20C7.59,20 4,16.41 4,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.25,14l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,10l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,14l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/wallet.xml b/compose/material/material/icons/generator/raw-icons/filled/wallet.xml
new file mode 100644
index 0000000..b5ad926
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/wallet.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,4H6C3.79,4 2,5.79 2,8v8c0,2.21 1.79,4 4,4h12c2.21,0 4,-1.79 4,-4V8C22,5.79 20.21,4 18,4zM16.14,13.77c-0.24,0.2 -0.57,0.28 -0.88,0.2L4.15,11.25C4.45,10.52 5.16,10 6,10h12c0.67,0 1.26,0.34 1.63,0.84L16.14,13.77zM6,6h12c1.1,0 2,0.9 2,2v0.55C19.41,8.21 18.73,8 18,8H6C5.27,8 4.59,8.21 4,8.55V8C4,6.9 4.9,6 6,6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/width_full.xml b/compose/material/material/icons/generator/raw-icons/filled/width_full.xml
new file mode 100644
index 0000000..b7fb52b
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/width_full.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h1v12H4V6zM20,18h-1V6h1V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/width_normal.xml b/compose/material/material/icons/generator/raw-icons/filled/width_normal.xml
new file mode 100644
index 0000000..5f327b5
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/width_normal.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h4v12H4V6zM20,18h-4V6h4V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/filled/width_wide.xml b/compose/material/material/icons/generator/raw-icons/filled/width_wide.xml
new file mode 100644
index 0000000..aacd7be
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/filled/width_wide.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h2v12H4V6zM20,18h-2V6h2V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/18_up_rating.xml b/compose/material/material/icons/generator/raw-icons/outlined/18_up_rating.xml
new file mode 100644
index 0000000..d67628b
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/18_up_rating.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8.5,15l1.5,0l0,-6l-3,0l0,1.5l1.5,0z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V5h14V19z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12.5,15H15c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1h-2.5c-0.55,0 -1,0.45 -1,1v4C11.5,14.55 11.95,15 12.5,15zM13,10h1.5v1.5H13V10zM13,12.5h1.5V14H13V12.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/blinds.xml b/compose/material/material/icons/generator/raw-icons/outlined/blinds.xml
new file mode 100644
index 0000000..02afa6f
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/blinds.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,19V3H4v16H2v2h20v-2H20zM16,9h2v2h-2V9zM14,11H6V9h8V11zM18,7h-2V5h2V7zM14,5v2H6V5H14zM6,19v-6h8v1.82c-0.45,0.32 -0.75,0.84 -0.75,1.43c0,0.97 0.78,1.75 1.75,1.75s1.75,-0.78 1.75,-1.75c0,-0.59 -0.3,-1.12 -0.75,-1.43V13h2v6H6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/broadcast_on_home.xml b/compose/material/material/icons/generator/raw-icons/outlined/broadcast_on_home.xml
new file mode 100644
index 0000000..83c5ebb
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/broadcast_on_home.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,6c0,-1.1 -0.9,-2 -2,-2H4v2h16v2.59c0.73,0.29 1.4,0.69 2,1.17V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,9H3c-0.5,0 -1,0.5 -1,1v9c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-9C9,9.5 8.5,9 8,9zM7,18H4v-7h3V18z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.75,16.97c0.3,-0.23 0.5,-0.57 0.5,-0.97c0,-0.69 -0.56,-1.25 -1.25,-1.25s-1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5V16.97z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,13.5c1.38,0 2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16c0,-2.21 -1.79,-4 -4,-4c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77C14.5,14.62 15.62,13.5 17,13.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/broadcast_on_personal.xml b/compose/material/material/icons/generator/raw-icons/outlined/broadcast_on_personal.xml
new file mode 100644
index 0000000..5a24b52
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/broadcast_on_personal.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,19v-9l6,-4.5l4.08,3.06c0.81,-0.32 1.69,-0.51 2.61,-0.54L10,3L2,9v12h8.76c-0.48,-0.6 -0.88,-1.27 -1.17,-2H4z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,14.75c-0.69,0 -1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5v-5.03c0.3,-0.23 0.5,-0.57 0.5,-0.97C18.25,15.31 17.69,14.75 17,14.75z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16C21,13.79 19.21,12 17,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/desk.xml b/compose/material/material/icons/generator/raw-icons/outlined/desk.xml
new file mode 100644
index 0000000..c6f9813
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/desk.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2,6v12h2V8h10v10h2v-2h4v2h2V6H2zM20,8v2h-4V8H20zM16,14v-2h4v2H16z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/lock_person.xml b/compose/material/material/icons/generator/raw-icons/outlined/lock_person.xml
new file mode 100644
index 0000000..4e9145e
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/lock_person.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6,20V10h12v1c0.7,0 1.37,0.1 2,0.29V10c0,-1.1 -0.9,-2 -2,-2h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h6.26c-0.42,-0.6 -0.75,-1.28 -0.97,-2H6zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM18,15c0.83,0 1.5,0.67 1.5,1.5S18.83,18 18,18s-1.5,-0.67 -1.5,-1.5S17.17,15 18,15zM18,21c-1.03,0 -1.94,-0.52 -2.48,-1.32C16.25,19.26 17.09,19 18,19s1.75,0.26 2.48,0.68C19.94,20.48 19.03,21 18,21z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/no_adult_content.xml b/compose/material/material/icons/generator/raw-icons/outlined/no_adult_content.xml
new file mode 100644
index 0000000..27a37a3
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/no_adult_content.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-1.85 0.63,-3.54 1.69,-4.9L7.59,9h2.83L7.1,5.69C8.46,4.63 10.15,4 12,4c4.41,0 8,3.59 8,8c0,1.85 -0.63,3.54 -1.69,4.9l-1.9,-1.9h-2.83l3.31,3.31C15.54,19.37 13.85,20 12,20C7.59,20 4,16.41 4,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.25,14l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,10l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,14l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/wallet.xml b/compose/material/material/icons/generator/raw-icons/outlined/wallet.xml
new file mode 100644
index 0000000..b5ad926
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/wallet.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,4H6C3.79,4 2,5.79 2,8v8c0,2.21 1.79,4 4,4h12c2.21,0 4,-1.79 4,-4V8C22,5.79 20.21,4 18,4zM16.14,13.77c-0.24,0.2 -0.57,0.28 -0.88,0.2L4.15,11.25C4.45,10.52 5.16,10 6,10h12c0.67,0 1.26,0.34 1.63,0.84L16.14,13.77zM6,6h12c1.1,0 2,0.9 2,2v0.55C19.41,8.21 18.73,8 18,8H6C5.27,8 4.59,8.21 4,8.55V8C4,6.9 4.9,6 6,6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/width_full.xml b/compose/material/material/icons/generator/raw-icons/outlined/width_full.xml
new file mode 100644
index 0000000..ab802cb1
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/width_full.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,18V6h1v12H4zM7,18V6h10v12H7zM20,18h-1V6h1V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/width_normal.xml b/compose/material/material/icons/generator/raw-icons/outlined/width_normal.xml
new file mode 100644
index 0000000..65448e4
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/width_normal.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,18V6h4v12H4zM10,18V6h4v12H10zM20,18h-4V6h4V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/outlined/width_wide.xml b/compose/material/material/icons/generator/raw-icons/outlined/width_wide.xml
new file mode 100644
index 0000000..61f1a42
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/outlined/width_wide.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,18V6h2v12H4zM8,18V6h8v12H8zM20,18h-2V6h2V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/18_up_rating.xml b/compose/material/material/icons/generator/raw-icons/rounded/18_up_rating.xml
new file mode 100644
index 0000000..96b9f19
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/18_up_rating.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,12.5h1.5v1.5h-1.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,10h1.5v1.5h-1.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM9.25,15L9.25,15c-0.41,0 -0.75,-0.34 -0.75,-0.75V10.5H7.75C7.34,10.5 7,10.16 7,9.75v0C7,9.34 7.34,9 7.75,9H9c0.55,0 1,0.45 1,1v4.25C10,14.66 9.66,15 9.25,15zM16,14c0,0.55 -0.45,1 -1,1h-2.5c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1H15c0.55,0 1,0.45 1,1V14z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/blinds.xml b/compose/material/material/icons/generator/raw-icons/rounded/blinds.xml
new file mode 100644
index 0000000..493f573
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/blinds.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,19V5c0,-1.1 -0.9,-2 -2,-2H6C4.9,3 4,3.9 4,5v14H3c-0.55,0 -1,0.45 -1,1v0c0,0.55 0.45,1 1,1h18c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H20zM16,9h2v2h-2V9zM14,11H6V9h8V11zM18,7h-2V5h2V7zM14,5v2H6V5H14zM6,19v-6h8v1.82c-0.45,0.32 -0.75,0.84 -0.75,1.43c0,0.97 0.78,1.75 1.75,1.75s1.75,-0.78 1.75,-1.75c0,-0.59 -0.3,-1.12 -0.75,-1.43V13h2v6H6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/broadcast_on_home.xml b/compose/material/material/icons/generator/raw-icons/rounded/broadcast_on_home.xml
new file mode 100644
index 0000000..da23a663
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/broadcast_on_home.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,6c0,-1.1 -0.9,-2 -2,-2H5C4.45,4 4,4.45 4,5v0c0,0.55 0.45,1 1,1h15v2.59c0.73,0.29 1.4,0.69 2,1.17V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,9H3c-0.5,0 -1,0.5 -1,1v9c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-9C9,9.5 8.5,9 8,9zM7,18H4v-7h3V18z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.75,16.97c0.3,-0.23 0.5,-0.57 0.5,-0.97c0,-0.69 -0.56,-1.25 -1.25,-1.25s-1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97v4.28c0,0.41 0.34,0.75 0.75,0.75l0,0c0.41,0 0.75,-0.34 0.75,-0.75V16.97z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.54,13.56c0.98,0.21 1.76,1.03 1.93,2.02c0.11,0.64 -0.03,1.25 -0.34,1.74c-0.18,0.29 -0.13,0.67 0.12,0.91l0,0c0.34,0.33 0.9,0.29 1.16,-0.12c0.51,-0.82 0.73,-1.83 0.53,-2.9c-0.3,-1.56 -1.56,-2.83 -3.12,-3.13C15.24,11.58 13,13.53 13,16c0,0.78 0.22,1.5 0.6,2.11c0.25,0.41 0.83,0.46 1.16,0.12l0,0c0.24,-0.24 0.29,-0.63 0.11,-0.92c-0.24,-0.38 -0.37,-0.83 -0.37,-1.31C14.5,14.45 15.93,13.22 17.54,13.56z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16.25,9.54c-2.94,0.33 -5.32,2.68 -5.69,5.61c-0.23,1.82 0.29,3.51 1.3,4.82c0.27,0.35 0.8,0.37 1.12,0.06l0,0c0.27,-0.27 0.28,-0.7 0.05,-1c-0.8,-1.05 -1.2,-2.43 -0.95,-3.89c0.34,-2.03 1.95,-3.67 3.98,-4.05C19.22,10.5 22,12.93 22,16c0,1.13 -0.38,2.18 -1.02,3.02c-0.23,0.3 -0.21,0.73 0.06,1l0,0c0.31,0.31 0.84,0.3 1.11,-0.06C23,18.87 23.5,17.49 23.5,16C23.5,12.16 20.17,9.1 16.25,9.54z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/broadcast_on_personal.xml b/compose/material/material/icons/generator/raw-icons/rounded/broadcast_on_personal.xml
new file mode 100644
index 0000000..ddc8dafc
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/broadcast_on_personal.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,8c0.7,0 1.38,0.1 2.02,0.27L12,3L4,9v12h6.76C9.66,19.63 9,17.89 9,16C9,11.58 12.58,8 17,8z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,14.75c-0.69,0 -1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5v-5.03c0.3,-0.23 0.5,-0.57 0.5,-0.97C18.25,15.31 17.69,14.75 17,14.75z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16C21,13.79 19.21,12 17,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/desk.xml b/compose/material/material/icons/generator/raw-icons/rounded/desk.xml
new file mode 100644
index 0000000..31f4cda
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/desk.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2,7v10c0,0.55 0.45,1 1,1h0c0.55,0 1,-0.45 1,-1V8h10v9c0,0.55 0.45,1 1,1h0c0.55,0 1,-0.45 1,-1v-1h4v1c0,0.55 0.45,1 1,1h0c0.55,0 1,-0.45 1,-1V7c0,-0.55 -0.45,-1 -1,-1H3C2.45,6 2,6.45 2,7zM20,8v2h-4V8H20zM16,14v-2h4v2H16z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/lock_person.xml b/compose/material/material/icons/generator/raw-icons/rounded/lock_person.xml
new file mode 100644
index 0000000..86b3106
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/lock_person.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,11c0.7,0 1.37,0.1 2,0.29V10c0,-1.1 -0.9,-2 -2,-2h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h6.26C11.47,20.87 11,19.49 11,18C11,14.13 14.13,11 18,11zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H8.9V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM18,15c0.83,0 1.5,0.67 1.5,1.5S18.83,18 18,18s-1.5,-0.67 -1.5,-1.5S17.17,15 18,15zM18,21c-1.03,0 -1.94,-0.52 -2.48,-1.32C16.25,19.26 17.09,19 18,19s1.75,0.26 2.48,0.68C19.94,20.48 19.03,21 18,21z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/no_adult_content.xml b/compose/material/material/icons/generator/raw-icons/rounded/no_adult_content.xml
new file mode 100644
index 0000000..27a37a3
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/no_adult_content.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-1.85 0.63,-3.54 1.69,-4.9L7.59,9h2.83L7.1,5.69C8.46,4.63 10.15,4 12,4c4.41,0 8,3.59 8,8c0,1.85 -0.63,3.54 -1.69,4.9l-1.9,-1.9h-2.83l3.31,3.31C15.54,19.37 13.85,20 12,20C7.59,20 4,16.41 4,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.25,14l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,10l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,14l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/wallet.xml b/compose/material/material/icons/generator/raw-icons/rounded/wallet.xml
new file mode 100644
index 0000000..b5ad926
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/wallet.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,4H6C3.79,4 2,5.79 2,8v8c0,2.21 1.79,4 4,4h12c2.21,0 4,-1.79 4,-4V8C22,5.79 20.21,4 18,4zM16.14,13.77c-0.24,0.2 -0.57,0.28 -0.88,0.2L4.15,11.25C4.45,10.52 5.16,10 6,10h12c0.67,0 1.26,0.34 1.63,0.84L16.14,13.77zM6,6h12c1.1,0 2,0.9 2,2v0.55C19.41,8.21 18.73,8 18,8H6C5.27,8 4.59,8.21 4,8.55V8C4,6.9 4.9,6 6,6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/width_full.xml b/compose/material/material/icons/generator/raw-icons/rounded/width_full.xml
new file mode 100644
index 0000000..b7fb52b
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/width_full.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h1v12H4V6zM20,18h-1V6h1V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/width_normal.xml b/compose/material/material/icons/generator/raw-icons/rounded/width_normal.xml
new file mode 100644
index 0000000..5f327b5
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/width_normal.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h4v12H4V6zM20,18h-4V6h4V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/rounded/width_wide.xml b/compose/material/material/icons/generator/raw-icons/rounded/width_wide.xml
new file mode 100644
index 0000000..aacd7be
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/rounded/width_wide.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h2v12H4V6zM20,18h-2V6h2V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/18_up_rating.xml b/compose/material/material/icons/generator/raw-icons/sharp/18_up_rating.xml
new file mode 100644
index 0000000..ac13cd8
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/18_up_rating.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,12.5h1.5v1.5h-1.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,10h1.5v1.5h-1.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21,3H3v18h18V3zM10,15H8.5v-4.5H7V9h3V15zM16,14c0,0.55 -0.45,1 -1,1h-2.5c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1H15c0.55,0 1,0.45 1,1V14z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/blinds.xml b/compose/material/material/icons/generator/raw-icons/sharp/blinds.xml
new file mode 100644
index 0000000..02afa6f
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/blinds.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,19V3H4v16H2v2h20v-2H20zM16,9h2v2h-2V9zM14,11H6V9h8V11zM18,7h-2V5h2V7zM14,5v2H6V5H14zM6,19v-6h8v1.82c-0.45,0.32 -0.75,0.84 -0.75,1.43c0,0.97 0.78,1.75 1.75,1.75s1.75,-0.78 1.75,-1.75c0,-0.59 -0.3,-1.12 -0.75,-1.43V13h2v6H6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/broadcast_on_home.xml b/compose/material/material/icons/generator/raw-icons/sharp/broadcast_on_home.xml
new file mode 100644
index 0000000..c1a972d
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/broadcast_on_home.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,9.76V4H4v2h16v2.59C20.73,8.88 21.4,9.28 22,9.76z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,9H2v11h7V9zM7,18H4v-7h3V18z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.75,16.97c0.3,-0.23 0.5,-0.57 0.5,-0.97c0,-0.69 -0.56,-1.25 -1.25,-1.25s-1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5V16.97z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,13.5c1.38,0 2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16c0,-2.21 -1.79,-4 -4,-4c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77C14.5,14.62 15.62,13.5 17,13.5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/broadcast_on_personal.xml b/compose/material/material/icons/generator/raw-icons/sharp/broadcast_on_personal.xml
new file mode 100644
index 0000000..ddc8dafc
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/broadcast_on_personal.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,8c0.7,0 1.38,0.1 2.02,0.27L12,3L4,9v12h6.76C9.66,19.63 9,17.89 9,16C9,11.58 12.58,8 17,8z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,14.75c-0.69,0 -1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5v-5.03c0.3,-0.23 0.5,-0.57 0.5,-0.97C18.25,15.31 17.69,14.75 17,14.75z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16C21,13.79 19.21,12 17,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/desk.xml b/compose/material/material/icons/generator/raw-icons/sharp/desk.xml
new file mode 100644
index 0000000..c6f9813
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/desk.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2,6v12h2V8h10v10h2v-2h4v2h2V6H2zM20,8v2h-4V8H20zM16,14v-2h4v2H16z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/lock_person.xml b/compose/material/material/icons/generator/raw-icons/sharp/lock_person.xml
new file mode 100644
index 0000000..5ecd14f
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/lock_person.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16.43,11.18c1.26,-0.29 2.47,-0.21 3.57,0.12V8h-3V6.22c0,-2.61 -1.91,-4.94 -4.51,-5.19C9.51,0.74 7,3.08 7,6v2H4v14h8.26c-1.01,-1.45 -1.5,-3.3 -1.15,-5.27C11.6,14 13.74,11.79 16.43,11.18zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H8.9V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM18,15c0.83,0 1.5,0.67 1.5,1.5S18.83,18 18,18s-1.5,-0.67 -1.5,-1.5S17.17,15 18,15zM18,21c-1.03,0 -1.94,-0.52 -2.48,-1.32C16.25,19.26 17.09,19 18,19s1.75,0.26 2.48,0.68C19.94,20.48 19.03,21 18,21z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/no_adult_content.xml b/compose/material/material/icons/generator/raw-icons/sharp/no_adult_content.xml
new file mode 100644
index 0000000..27a37a3
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/no_adult_content.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-1.85 0.63,-3.54 1.69,-4.9L7.59,9h2.83L7.1,5.69C8.46,4.63 10.15,4 12,4c4.41,0 8,3.59 8,8c0,1.85 -0.63,3.54 -1.69,4.9l-1.9,-1.9h-2.83l3.31,3.31C15.54,19.37 13.85,20 12,20C7.59,20 4,16.41 4,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.25,14l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,10l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,14l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/wallet.xml b/compose/material/material/icons/generator/raw-icons/sharp/wallet.xml
new file mode 100644
index 0000000..ec2e875
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/wallet.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,4H2v16h20V4zM15.75,14.09L4,11.22V10h16v0.53L15.75,14.09zM4,6h16v2H4V6z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/width_full.xml b/compose/material/material/icons/generator/raw-icons/sharp/width_full.xml
new file mode 100644
index 0000000..271eb48e
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/width_full.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,4H2v16h20V4zM4,6h1v12H4V6zM20,18h-1V6h1V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/width_normal.xml b/compose/material/material/icons/generator/raw-icons/sharp/width_normal.xml
new file mode 100644
index 0000000..8ba036e
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/width_normal.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,4H2v16h20V4zM4,6h4v12H4V6zM20,18h-4V6h4V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/sharp/width_wide.xml b/compose/material/material/icons/generator/raw-icons/sharp/width_wide.xml
new file mode 100644
index 0000000..79c8db8
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/sharp/width_wide.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,4H2v16h20V4zM4,6h2v12H4V6zM20,18h-2V6h2V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/18_up_rating.xml b/compose/material/material/icons/generator/raw-icons/twotone/18_up_rating.xml
new file mode 100644
index 0000000..8ee33c5
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/18_up_rating.xml
@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,10h1.5v1.5h-1.5z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,12.5h1.5v1.5h-1.5z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5,19h14V5H5V19zM11.5,10c0,-0.55 0.45,-1 1,-1H15c0.55,0 1,0.45 1,1v4c0,0.55 -0.45,1 -1,1h-2.5c-0.55,0 -1,-0.45 -1,-1V10zM7,9h3v6H8.5v-4.5H7V9z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8.5,15l1.5,0l0,-6l-3,0l0,1.5l1.5,0z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V5h14V19z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12.5,15H15c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1h-2.5c-0.55,0 -1,0.45 -1,1v4C11.5,14.55 11.95,15 12.5,15zM13,10h1.5v1.5H13V10zM13,12.5h1.5V14H13V12.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/blinds.xml b/compose/material/material/icons/generator/raw-icons/twotone/blinds.xml
new file mode 100644
index 0000000..93b88c7
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/blinds.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6,9h8v2h-8z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6,5h8v2h-8z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,9h2v2h-2z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,5h2v2h-2z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,19V3H4v16H2v2h20v-2H20zM6,5h8v2H6V5zM6,9h8v2H6V9zM18,19H6v-6h8v1.82c-0.45,0.32 -0.75,0.84 -0.75,1.43c0,0.97 0.78,1.75 1.75,1.75s1.75,-0.78 1.75,-1.75c0,-0.59 -0.3,-1.12 -0.75,-1.43V13h2V19zM18,11h-2V9h2V11zM18,7h-2V5h2V7z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/broadcast_on_home.xml b/compose/material/material/icons/generator/raw-icons/twotone/broadcast_on_home.xml
new file mode 100644
index 0000000..5284e8b
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/broadcast_on_home.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,11h3v7h-3z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,8.59c0.73,0.29 1.4,0.69 2,1.17V6c0,-1.1 -0.9,-2 -2,-2H4v2h16V8.59z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,9H3c-0.5,0 -1,0.5 -1,1v9c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-9C9,9.5 8.5,9 8,9zM7,18H4v-7h3V18z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,14.75c-0.69,0 -1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5v-5.03c0.3,-0.23 0.5,-0.57 0.5,-0.97C18.25,15.31 17.69,14.75 17,14.75z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16C21,13.79 19.21,12 17,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/broadcast_on_personal.xml b/compose/material/material/icons/generator/raw-icons/twotone/broadcast_on_personal.xml
new file mode 100644
index 0000000..b216686
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/broadcast_on_personal.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,10v9h5.59C9.21,18.07 9,17.06 9,16c0,-3.39 2.11,-6.27 5.08,-7.44L10,5.5L4,10z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,19v-9l6,-4.5l4.08,3.06c0.81,-0.32 1.69,-0.51 2.61,-0.54L10,3L2,9v12h8.76c-0.48,-0.6 -0.88,-1.27 -1.17,-2H4z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,14.75c-0.69,0 -1.25,0.56 -1.25,1.25c0,0.4 0.2,0.75 0.5,0.97V22h1.5v-5.03c0.3,-0.23 0.5,-0.57 0.5,-0.97C18.25,15.31 17.69,14.75 17,14.75z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12c-2.21,0 -4,1.79 -4,4c0,1.1 0.45,2.1 1.17,2.83l1.06,-1.06c-0.45,-0.45 -0.73,-1.08 -0.73,-1.77c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.69 -0.28,1.31 -0.73,1.76l1.06,1.06C20.55,18.1 21,17.1 21,16C21,13.79 19.21,12 17,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,9.5c-3.59,0 -6.5,2.91 -6.5,6.5c0,1.79 0.73,3.42 1.9,4.6l1.06,-1.06C12.56,18.63 12,17.38 12,16c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.37 -0.56,2.62 -1.46,3.52l1.07,1.06c1.17,-1.18 1.89,-2.8 1.89,-4.58C23.5,12.41 20.59,9.5 17,9.5z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/desk.xml b/compose/material/material/icons/generator/raw-icons/twotone/desk.xml
new file mode 100644
index 0000000..90a2ae2
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/desk.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,8h4v2h-4z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,12h4v2h-4z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2,6v12h2V8h10v10h2v-2h4v2h2V6H2zM20,14h-4v-2h4V14zM20,10h-4V8h4V10z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/lock_person.xml b/compose/material/material/icons/generator/raw-icons/twotone/lock_person.xml
new file mode 100644
index 0000000..b6854d6
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/lock_person.xml
@@ -0,0 +1,18 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6,10v10h5.29C11.1,19.37 11,18.7 11,18c0,-3.87 3.13,-7 7,-7v-1H6z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6,20V10h12v1c0.7,0 1.37,0.1 2,0.29V10c0,-1.1 -0.9,-2 -2,-2h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h6.26c-0.42,-0.6 -0.75,-1.28 -0.97,-2H6zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM18,15c0.83,0 1.5,0.67 1.5,1.5S18.83,18 18,18s-1.5,-0.67 -1.5,-1.5S17.17,15 18,15zM18,21c-1.03,0 -1.94,-0.52 -2.48,-1.32C16.25,19.26 17.09,19 18,19s1.75,0.26 2.48,0.68C19.94,20.48 19.03,21 18,21z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/no_adult_content.xml b/compose/material/material/icons/generator/raw-icons/twotone/no_adult_content.xml
new file mode 100644
index 0000000..27a37a3
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/no_adult_content.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-1.85 0.63,-3.54 1.69,-4.9L7.59,9h2.83L7.1,5.69C8.46,4.63 10.15,4 12,4c4.41,0 8,3.59 8,8c0,1.85 -0.63,3.54 -1.69,4.9l-1.9,-1.9h-2.83l3.31,3.31C15.54,19.37 13.85,20 12,20C7.59,20 4,16.41 4,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.25,14l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,10l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2l1.5,0l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,14l0.75,-1l0.75,1l1.5,0l-1.5,-2l1.5,-2l-1.5,0l-0.75,1l-0.75,-1l-1.5,0l1.5,2l-1.5,2z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/wallet.xml b/compose/material/material/icons/generator/raw-icons/twotone/wallet.xml
new file mode 100644
index 0000000..0e9a8ca
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/wallet.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,10H6c-0.84,0 -1.55,0.52 -1.85,1.25l11.11,2.72c0.31,0.08 0.64,0 0.88,-0.2l3.49,-2.92C19.26,10.34 18.67,10 18,10z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,6H6C4.9,6 4,6.9 4,8v0.55C4.59,8.21 5.27,8 6,8h12c0.73,0 1.41,0.21 2,0.55V8C20,6.9 19.1,6 18,6z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,4H6C3.79,4 2,5.79 2,8v8c0,2.21 1.79,4 4,4h12c2.21,0 4,-1.79 4,-4V8C22,5.79 20.21,4 18,4zM16.14,13.77c-0.24,0.2 -0.57,0.28 -0.88,0.2L4.15,11.25C4.45,10.52 5.16,10 6,10h12c0.67,0 1.26,0.34 1.63,0.84L16.14,13.77zM20,8.55C19.41,8.21 18.73,8 18,8H6C5.27,8 4.59,8.21 4,8.55V8c0,-1.1 0.9,-2 2,-2h12c1.1,0 2,0.9 2,2V8.55z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/width_full.xml b/compose/material/material/icons/generator/raw-icons/twotone/width_full.xml
new file mode 100644
index 0000000..06ceb58
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/width_full.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7,6h10v12h-10z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM5,18H4V6h1V18zM17,18H7V6h10V18zM20,18h-1V6h1V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/width_normal.xml b/compose/material/material/icons/generator/raw-icons/twotone/width_normal.xml
new file mode 100644
index 0000000..4fdc336
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/width_normal.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M10,6h4v12h-4z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM8,18H4V6h4V18zM14,18h-4V6h4V18zM20,18h-4V6h4V18z"/>
+</vector>
diff --git a/compose/material/material/icons/generator/raw-icons/twotone/width_wide.xml b/compose/material/material/icons/generator/raw-icons/twotone/width_wide.xml
new file mode 100644
index 0000000..8daac03
--- /dev/null
+++ b/compose/material/material/icons/generator/raw-icons/twotone/width_wide.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,6h8v12h-8z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM6,18H4V6h2V18zM16,18H8V6h8V18zM20,18h-2V6h2V18z"/>
+</vector>
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt
index 33ae8af..e6adabb 100644
--- a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt
@@ -66,7 +66,8 @@
import androidx.compose.ui.window.PopupPositionProvider
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.util.UUID
import kotlin.math.roundToInt
@@ -254,7 +255,7 @@
id = android.R.id.content
ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
- ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
+ setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner())
composeView.viewTreeObserver.addOnGlobalLayoutListener(this)
// Set unique id for AbstractComposeView. This allows state restoration for the state
// defined inside the Popup via rememberSaveable()
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 34521a9..0703a7c 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -469,7 +469,7 @@
}
@androidx.compose.runtime.Stable public interface TextFieldColors {
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> backgroundColor(boolean enabled);
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> cursorColor(boolean isError);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> indicatorColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> labelColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
@@ -484,8 +484,8 @@
method public float getMinHeight();
method public float getMinWidth();
method public float getUnfocusedBorderThickness();
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
property public final float FocusedBorderThickness;
property public final float MinHeight;
property public final float MinWidth;
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 3b7112a..13eed4e 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -252,8 +252,8 @@
@androidx.compose.material3.ExperimentalMaterial3Api public final class ExposedDropdownMenuDefaults {
method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TrailingIcon(boolean expanded, optional kotlin.jvm.functions.Function0<kotlin.Unit> onIconClick);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
field public static final androidx.compose.material3.ExposedDropdownMenuDefaults INSTANCE;
}
@@ -571,7 +571,7 @@
}
@androidx.compose.runtime.Stable public interface TextFieldColors {
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> backgroundColor(boolean enabled);
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> cursorColor(boolean isError);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> indicatorColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> labelColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
@@ -590,9 +590,9 @@
method public float getMinWidth();
method public float getUnfocusedBorderThickness();
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.foundation.layout.PaddingValues outlinedTextFieldPadding(optional float start, optional float top, optional float end, optional float bottom);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.foundation.layout.PaddingValues textFieldWithLabelPadding(optional float start, optional float end, optional float top, optional float bottom);
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.foundation.layout.PaddingValues textFieldWithoutLabelPadding(optional float start, optional float top, optional float end, optional float bottom);
property public final float FocusedBorderThickness;
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 34521a9..0703a7c 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -469,7 +469,7 @@
}
@androidx.compose.runtime.Stable public interface TextFieldColors {
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> backgroundColor(boolean enabled);
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> cursorColor(boolean isError);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> indicatorColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> labelColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
@@ -484,8 +484,8 @@
method public float getMinHeight();
method public float getMinWidth();
method public float getUnfocusedBorderThickness();
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
property public final float FocusedBorderThickness;
property public final float MinHeight;
property public final float MinWidth;
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index b41dc26..97b96c6 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -49,7 +49,7 @@
api("androidx.compose.ui:ui-text:1.0.1")
// TODO: remove next 3 dependencies when b/202810604 is fixed
- implementation("androidx.savedstate:savedstate:1.1.0")
+ implementation(project(":savedstate:savedstate-ktx"))
implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
index 4aa09f2..4bd25b0 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
@@ -359,7 +359,7 @@
onValueChange = onValueChange,
modifier = modifier
.background(
- color = colors.backgroundColor(enabled).value,
+ color = colors.containerColor(enabled).value,
shape = RoundedCornerShape(
topStart = 4.0.dp,
topEnd = 4.0.dp,
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
index 2265a3a..dc0851e 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
@@ -929,14 +929,14 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun testOutlinedTextField_appliesBackgroundColor() {
+ fun testOutlinedTextField_appliesContainerColor() {
rule.setMaterialContent(lightColorScheme()) {
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier.testTag(TextFieldTag),
colors = TextFieldDefaults.outlinedTextFieldColors(
- backgroundColor = Color.Red,
+ containerColor = Color.Red,
unfocusedBorderColor = Color.Red
),
shape = RectangleShape
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
index 1eb38c1..45be968 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -40,6 +41,7 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -95,9 +97,10 @@
@Test
fun scaffold_AppbarAndContent_inColumn() {
+ var scaffoldSize: IntSize = IntSize.Zero
var appbarPosition: Offset = Offset.Zero
- var appbarSize: IntSize = IntSize.Zero
var contentPosition: Offset = Offset.Zero
+ var contentSize: IntSize = IntSize.Zero
rule.setMaterialContent(lightColorScheme()) {
Scaffold(
topBar = {
@@ -108,26 +111,32 @@
.background(color = Color.Red)
.onGloballyPositioned { positioned: LayoutCoordinates ->
appbarPosition = positioned.localToWindow(Offset.Zero)
- appbarSize = positioned.size
}
)
- }
+ },
+ modifier = Modifier
+ .onGloballyPositioned { positioned: LayoutCoordinates ->
+ scaffoldSize = positioned.size
+ }
) {
Box(
Modifier
- .fillMaxWidth()
- .height(50.dp)
+ .fillMaxSize()
.background(Color.Blue)
- .onGloballyPositioned { contentPosition = it.localToWindow(Offset.Zero) }
+ .onGloballyPositioned { positioned: LayoutCoordinates ->
+ contentPosition = positioned.positionInParent()
+ contentSize = positioned.size
+ }
)
}
}
- assertThat(appbarPosition.y + appbarSize.height.toFloat())
- .isEqualTo(contentPosition.y)
+ assertThat(appbarPosition.y).isEqualTo(contentPosition.y)
+ assertThat(scaffoldSize).isEqualTo(contentSize)
}
@Test
fun scaffold_bottomBarAndContent_inStack() {
+ var scaffoldSize: IntSize = IntSize.Zero
var appbarPosition: Offset = Offset.Zero
var appbarSize: IntSize = IntSize.Zero
var contentPosition: Offset = Offset.Zero
@@ -145,12 +154,15 @@
appbarSize = positioned.size
}
)
- }
+ },
+ modifier = Modifier
+ .onGloballyPositioned { positioned: LayoutCoordinates ->
+ scaffoldSize = positioned.size
+ }
) {
Box(
Modifier
.fillMaxSize()
- .height(50.dp)
.background(color = Color.Blue)
.onGloballyPositioned { positioned: LayoutCoordinates ->
contentPosition = positioned.positionInParent()
@@ -162,6 +174,52 @@
val appBarBottom = appbarPosition.y + appbarSize.height
val contentBottom = contentPosition.y + contentSize.height
assertThat(appBarBottom).isEqualTo(contentBottom)
+ assertThat(scaffoldSize).isEqualTo(contentSize)
+ }
+
+ @Test
+ fun scaffold_innerPadding_lambdaParam() {
+ var topBarSize: IntSize = IntSize.Zero
+ var bottomBarSize: IntSize = IntSize.Zero
+ lateinit var innerPadding: PaddingValues
+
+ rule.setContent {
+ Scaffold(
+ topBar = {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .background(color = Color.Red)
+ .onGloballyPositioned { positioned: LayoutCoordinates ->
+ topBarSize = positioned.size
+ }
+ )
+ },
+ bottomBar = {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ .background(color = Color.Red)
+ .onGloballyPositioned { positioned: LayoutCoordinates ->
+ bottomBarSize = positioned.size
+ }
+ )
+ }
+ ) {
+ innerPadding = it
+ Text("body")
+ }
+ }
+ rule.runOnIdle {
+ with(rule.density) {
+ assertThat(innerPadding.calculateTopPadding())
+ .isEqualTo(topBarSize.toSize().height.toDp())
+ assertThat(innerPadding.calculateBottomPadding())
+ .isEqualTo(bottomBarSize.toSize().height.toDp())
+ }
+ }
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index 96e46da..ac806d9 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -106,7 +106,6 @@
import com.nhaarman.mockitokotlin2.verify
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
@@ -1019,7 +1018,7 @@
onValueChange = {},
visualTransformation = PasswordVisualTransformation('\u0020'),
shape = RectangleShape,
- colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
+ colors = TextFieldDefaults.textFieldColors(containerColor = Color.White)
)
}
@@ -1038,7 +1037,7 @@
@Test
@LargeTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun testTextField_alphaNotApplied_toCustomBackgroundColorAndTransparentColors() {
+ fun testTextField_alphaNotApplied_toCustomContainerColorAndTransparentColors() {
rule.setMaterialContent(lightColorScheme()) {
Box(Modifier.background(color = Color.White)) {
@@ -1055,7 +1054,7 @@
Icon(Icons.Default.Favorite, null, tint = Color.Transparent)
},
colors = TextFieldDefaults.textFieldColors(
- backgroundColor = Color.Blue,
+ containerColor = Color.Blue,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
textColor = Color.Transparent,
@@ -1120,7 +1119,7 @@
Text("label", color = Color.Red, modifier = Modifier.background(Color.Red))
},
textStyle = TextStyle(color = Color.Blue),
- colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
+ colors = TextFieldDefaults.textFieldColors(containerColor = Color.White)
)
}
rule.onNode(SemanticsMatcher.keyIsDefined(SemanticsProperties.Text), true)
@@ -1162,7 +1161,7 @@
},
textStyle = TextStyle(color = Color.White),
colors = TextFieldDefaults.textFieldColors(
- backgroundColor = Color.White,
+ containerColor = Color.White,
unfocusedIndicatorColor = Color.Transparent
)
)
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
index f8f9dbe..3ca5f22 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
@@ -307,13 +307,13 @@
}
/**
- * Creates a [TextFieldColors] that represents the default input text, background and content
+ * Creates a [TextFieldColors] that represents the default input text, container, and content
* (including label, placeholder, leading and trailing icons) colors used in a [TextField].
*
* @param textColor Represents the color used for the input text of this text field.
* @param disabledTextColor Represents the color used for the input text of this text field when
* it's disabled.
- * @param backgroundColor Represents the background color for this text field.
+ * @param containerColor Represents the container color for this text field.
* @param cursorColor Represents the cursor color for this text field.
* @param errorCursorColor Represents the cursor color for this text field when it's in error
* state.
@@ -356,7 +356,7 @@
textColor: Color = FilledAutocompleteTokens.FieldInputTextColor.toColor(),
disabledTextColor: Color = FilledAutocompleteTokens.FieldDisabledInputTextColor.toColor()
.copy(alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity),
- backgroundColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.toColor(),
+ containerColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.toColor(),
cursorColor: Color = FilledAutocompleteTokens.TextFieldCaretColor.toColor(),
errorCursorColor: Color = FilledAutocompleteTokens.TextFieldErrorFocusCaretColor.toColor(),
focusedIndicatorColor: Color =
@@ -412,7 +412,7 @@
unfocusedTrailingIconColor = unfocusedTrailingIconColor,
disabledTrailingIconColor = disabledTrailingIconColor,
errorTrailingIconColor = errorTrailingIconColor,
- backgroundColor = backgroundColor,
+ containerColor = containerColor,
focusedLabelColor = focusedLabelColor,
unfocusedLabelColor = unfocusedLabelColor,
disabledLabelColor = disabledLabelColor,
@@ -422,14 +422,14 @@
)
/**
- * Creates a [TextFieldColors] that represents the default input text, background and content
+ * Creates a [TextFieldColors] that represents the default input text, container, and content
* (including label, placeholder, leading and trailing icons) colors used in an
* [OutlinedTextField].
*
* @param textColor Represents the color used for the input text of this text field.
* @param disabledTextColor Represents the color used for the input text of this text field when
* it's disabled.
- * @param backgroundColor Represents the background color for this text field.
+ * @param containerColor Represents the container color for this text field.
* @param cursorColor Represents the cursor color for this text field.
* @param errorCursorColor Represents the cursor color for this text field when it's in error
* state.
@@ -471,7 +471,7 @@
textColor: Color = OutlinedAutocompleteTokens.FieldInputTextColor.toColor(),
disabledTextColor: Color = OutlinedAutocompleteTokens.FieldDisabledInputTextColor.toColor()
.copy(alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity),
- backgroundColor: Color = Color.Transparent,
+ containerColor: Color = Color.Transparent,
cursorColor: Color = OutlinedAutocompleteTokens.TextFieldCaretColor.toColor(),
errorCursorColor: Color =
OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.toColor(),
@@ -526,7 +526,7 @@
unfocusedTrailingIconColor = unfocusedTrailingIconColor,
disabledTrailingIconColor = disabledTrailingIconColor,
errorTrailingIconColor = errorTrailingIconColor,
- backgroundColor = backgroundColor,
+ containerColor = containerColor,
focusedLabelColor = focusedLabelColor,
unfocusedLabelColor = unfocusedLabelColor,
disabledLabelColor = disabledLabelColor,
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.kt
index 81e7a36..2c2d28c 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.kt
@@ -66,7 +66,8 @@
import androidx.compose.ui.window.PopupPositionProvider
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.util.UUID
import kotlin.math.roundToInt
@@ -255,7 +256,7 @@
id = android.R.id.content
ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
- ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
+ setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner())
composeView.viewTreeObserver.addOnGlobalLayoutListener(this)
// Set unique id for AbstractComposeView. This allows state restoration for the state
// defined inside the Popup via rememberSaveable()
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index d1657e7..7a21db0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -163,7 +163,7 @@
} else {
modifier
}
- .background(colors.backgroundColor(enabled).value, shape)
+ .background(colors.containerColor(enabled).value, shape)
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
@@ -305,7 +305,7 @@
} else {
modifier
}
- .background(colors.backgroundColor(enabled).value, shape)
+ .background(colors.containerColor(enabled).value, shape)
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index 7eb20fa..f8446cc 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -182,17 +182,18 @@
0
}
- val bodyContentHeight = layoutHeight - topBarHeight
-
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
- val innerPadding = PaddingValues(bottom = bottomBarHeight.toDp())
+ val innerPadding = PaddingValues(
+ top = topBarHeight.toDp(),
+ bottom = bottomBarHeight.toDp()
+ )
content(innerPadding)
- }.map { it.measure(looseConstraints.copy(maxHeight = bodyContentHeight)) }
+ }.map { it.measure(looseConstraints) }
// Placing to control drawing order to match default elevation of each placeable
bodyContentPlaceables.forEach {
- it.place(0, topBarHeight)
+ it.place(0, 0)
}
topBarPlaceables.forEach {
it.place(0, 0)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index 0474c6c..1ed3361 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -149,7 +149,7 @@
* appearance / behavior of this TextField in different [Interaction]s.
* @param shape the shape of the text field's container
* @param colors [TextFieldColors] that will be used to resolve color of the text, content
- * (including label, placeholder, leading and trailing icons, indicator line) and background for
+ * (including label, placeholder, leading and trailing icons, indicator line) and container for
* this text field in different states. See [TextFieldDefaults.textFieldColors]
*/
@Composable
@@ -184,7 +184,7 @@
BasicTextField(
value = value,
modifier = modifier
- .background(colors.backgroundColor(enabled).value, shape)
+ .background(colors.containerColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
@@ -281,7 +281,7 @@
* appearance / behavior of this TextField in different [Interaction]s.
* @param shape the shape of the text field's container
* @param colors [TextFieldColors] that will be used to resolve color of the text, content
- * (including label, placeholder, leading and trailing icons, indicator line) and background for
+ * (including label, placeholder, leading and trailing icons, indicator line) and container for
* this text field in different states. See [TextFieldDefaults.textFieldColors]
*/
@Composable
@@ -316,7 +316,7 @@
BasicTextField(
value = value,
modifier = modifier
- .background(colors.backgroundColor(enabled).value, shape)
+ .background(colors.containerColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index f5a7101..97dd92b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -49,7 +49,7 @@
import androidx.compose.ui.unit.dp
/**
- * Represents the colors of the input text, background and content (including label, placeholder,
+ * Represents the colors of the input text, container, and content (including label, placeholder,
* leading and trailing icons) used in a text field in different states.
*
* See [TextFieldDefaults.textFieldColors] for the default colors used in [TextField].
@@ -67,12 +67,12 @@
fun textColor(enabled: Boolean): State<Color>
/**
- * Represents the background color for this text field.
+ * Represents the container color for this text field.
*
* @param enabled whether the text field is enabled
*/
@Composable
- fun backgroundColor(enabled: Boolean): State<Color>
+ fun containerColor(enabled: Boolean): State<Color>
/**
* Represents the color used for the placeholder of this text field.
@@ -302,7 +302,7 @@
): PaddingValues = PaddingValues(start, top, end, bottom)
/**
- * Creates a [TextFieldColors] that represents the default input text, background and content
+ * Creates a [TextFieldColors] that represents the default input text, container, and content
* (including label, placeholder, leading and trailing icons) colors used in a [TextField].
*/
@Composable
@@ -310,7 +310,7 @@
textColor: Color = FilledTextFieldTokens.InputColor.toColor(),
disabledTextColor: Color = FilledTextFieldTokens.DisabledInputColor.toColor()
.copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
- backgroundColor: Color = FilledTextFieldTokens.ContainerColor.toColor(),
+ containerColor: Color = FilledTextFieldTokens.ContainerColor.toColor(),
cursorColor: Color = FilledTextFieldTokens.CaretColor.toColor(),
errorCursorColor: Color = FilledTextFieldTokens.ErrorFocusCaretColor.toColor(),
focusedIndicatorColor: Color = FilledTextFieldTokens.FocusActiveIndicatorColor.toColor(),
@@ -354,7 +354,7 @@
unfocusedTrailingIconColor = unfocusedTrailingIconColor,
disabledTrailingIconColor = disabledTrailingIconColor,
errorTrailingIconColor = errorTrailingIconColor,
- backgroundColor = backgroundColor,
+ containerColor = containerColor,
focusedLabelColor = focusedLabelColor,
unfocusedLabelColor = unfocusedLabelColor,
disabledLabelColor = disabledLabelColor,
@@ -364,7 +364,7 @@
)
/**
- * Creates a [TextFieldColors] that represents the default input text, background and content
+ * Creates a [TextFieldColors] that represents the default input text, container, and content
* (including label, placeholder, leading and trailing icons) colors used in an
* [OutlinedTextField].
*/
@@ -373,7 +373,7 @@
textColor: Color = OutlinedTextFieldTokens.InputColor.toColor(),
disabledTextColor: Color = OutlinedTextFieldTokens.DisabledInputColor.toColor()
.copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
- backgroundColor: Color = Color.Transparent,
+ containerColor: Color = Color.Transparent,
cursorColor: Color = OutlinedTextFieldTokens.CaretColor.toColor(),
errorCursorColor: Color = OutlinedTextFieldTokens.ErrorFocusCaretColor.toColor(),
focusedBorderColor: Color = OutlinedTextFieldTokens.FocusOutlineColor.toColor(),
@@ -417,7 +417,7 @@
unfocusedTrailingIconColor = unfocusedTrailingIconColor,
disabledTrailingIconColor = disabledTrailingIconColor,
errorTrailingIconColor = errorTrailingIconColor,
- backgroundColor = backgroundColor,
+ containerColor = containerColor,
focusedLabelColor = focusedLabelColor,
unfocusedLabelColor = unfocusedLabelColor,
disabledLabelColor = disabledLabelColor,
@@ -634,7 +634,7 @@
private val unfocusedTrailingIconColor: Color,
private val disabledTrailingIconColor: Color,
private val errorTrailingIconColor: Color,
- private val backgroundColor: Color,
+ private val containerColor: Color,
private val focusedLabelColor: Color,
private val unfocusedLabelColor: Color,
private val disabledLabelColor: Color,
@@ -701,8 +701,8 @@
}
@Composable
- override fun backgroundColor(enabled: Boolean): State<Color> {
- return rememberUpdatedState(backgroundColor)
+ override fun containerColor(enabled: Boolean): State<Color> {
+ return rememberUpdatedState(containerColor)
}
@Composable
@@ -759,7 +759,7 @@
if (unfocusedTrailingIconColor != other.unfocusedTrailingIconColor) return false
if (disabledTrailingIconColor != other.disabledTrailingIconColor) return false
if (errorTrailingIconColor != other.errorTrailingIconColor) return false
- if (backgroundColor != other.backgroundColor) return false
+ if (containerColor != other.containerColor) return false
if (focusedLabelColor != other.focusedLabelColor) return false
if (unfocusedLabelColor != other.unfocusedLabelColor) return false
if (disabledLabelColor != other.disabledLabelColor) return false
@@ -787,7 +787,7 @@
result = 31 * result + unfocusedTrailingIconColor.hashCode()
result = 31 * result + disabledTrailingIconColor.hashCode()
result = 31 * result + errorTrailingIconColor.hashCode()
- result = 31 * result + backgroundColor.hashCode()
+ result = 31 * result + containerColor.hashCode()
result = 31 * result + focusedLabelColor.hashCode()
result = 31 * result + unfocusedLabelColor.hashCode()
result = 31 * result + disabledLabelColor.hashCode()
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index 27a6774..c07e136 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
@@ -889,7 +889,17 @@
parameter("fontSize", ParameterType.String, "Unspecified", index = 5)
parameter("letterSpacing", ParameterType.String, "Unspecified", index = 9)
parameter("lineHeight", ParameterType.String, "Unspecified", index = 10)
- parameter("textDecoration", ParameterType.String, "Underline", index = 15)
+ parameter("paragraphStyle", ParameterType.String, "ParagraphStyle", index = 12) {
+ parameter("lineHeight", ParameterType.String, "Unspecified", index = 0)
+ }
+ parameter("spanStyle", ParameterType.String, "SpanStyle", index = 15) {
+ parameter("background", ParameterType.String, "Unspecified")
+ parameter("color", ParameterType.Color, Color.Red.toArgb(), index = 2)
+ parameter("fontSize", ParameterType.String, "Unspecified", index = 5)
+ parameter("letterSpacing", ParameterType.String, "Unspecified", index = 9)
+ parameter("textDecoration", ParameterType.String, "Underline", index = 13)
+ }
+ parameter("textDecoration", ParameterType.String, "Underline", index = 17)
}
}
}
diff --git a/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt b/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
index 3fdda51..37b891f 100644
--- a/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
+++ b/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
@@ -197,9 +197,7 @@
override val weight: FontWeight,
override val style: FontStyle,
val bestEffort: Boolean
-) : AndroidFont(FontLoadingStrategy.Async) {
- override val typefaceLoader: TypefaceLoader
- get() = GoogleFontTypefaceLoader
+) : AndroidFont(FontLoadingStrategy.Async, GoogleFontTypefaceLoader) {
fun toFontRequest(): FontRequest {
val query = "name=${name.encode()}&weight=${weight.weight}" +
"&italic=${style.toQueryParam()}&besteffort=${bestEffortQueryParam()}"
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index af8856a..7609a75 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -86,7 +86,7 @@
public final class MultiParagraph {
ctor public MultiParagraph(androidx.compose.ui.text.MultiParagraphIntrinsics intrinsics, optional int maxLines, optional boolean ellipsis, float width);
ctor @Deprecated public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader);
- ctor public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver);
+ ctor public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis);
method public androidx.compose.ui.text.style.ResolvedTextDirection getBidiRunDirection(int offset);
method public androidx.compose.ui.geometry.Rect getBoundingBox(int offset);
method public androidx.compose.ui.geometry.Rect getCursorRect(int offset);
@@ -208,7 +208,7 @@
public final class ParagraphKt {
method @Deprecated public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader);
- method public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver);
+ method public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis);
method public static androidx.compose.ui.text.Paragraph Paragraph(androidx.compose.ui.text.ParagraphIntrinsics paragraphIntrinsics, optional int maxLines, optional boolean ellipsis, float width);
}
@@ -550,11 +550,11 @@
package androidx.compose.ui.text.font {
public abstract class AndroidFont implements androidx.compose.ui.text.font.Font {
- ctor public AndroidFont(int loadingStrategy);
+ ctor public AndroidFont(int loadingStrategy, androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader);
method public final int getLoadingStrategy();
- method public abstract androidx.compose.ui.text.font.AndroidFont.TypefaceLoader getTypefaceLoader();
+ method public final androidx.compose.ui.text.font.AndroidFont.TypefaceLoader getTypefaceLoader();
property public final int loadingStrategy;
- property public abstract androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader;
+ property public final androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader;
}
public static interface AndroidFont.TypefaceLoader {
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 7685602..47d9540 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -95,7 +95,7 @@
public final class MultiParagraph {
ctor public MultiParagraph(androidx.compose.ui.text.MultiParagraphIntrinsics intrinsics, optional int maxLines, optional boolean ellipsis, float width);
ctor @Deprecated public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader);
- ctor public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver);
+ ctor public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis);
method public androidx.compose.ui.text.style.ResolvedTextDirection getBidiRunDirection(int offset);
method public androidx.compose.ui.geometry.Rect getBoundingBox(int offset);
method public androidx.compose.ui.geometry.Rect getCursorRect(int offset);
@@ -217,7 +217,7 @@
public final class ParagraphKt {
method @Deprecated public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader);
- method public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver);
+ method public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis);
method public static androidx.compose.ui.text.Paragraph Paragraph(androidx.compose.ui.text.ParagraphIntrinsics paragraphIntrinsics, optional int maxLines, optional boolean ellipsis, float width);
}
@@ -609,11 +609,11 @@
package androidx.compose.ui.text.font {
public abstract class AndroidFont implements androidx.compose.ui.text.font.Font {
- ctor public AndroidFont(int loadingStrategy);
+ ctor public AndroidFont(int loadingStrategy, androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader);
method public final int getLoadingStrategy();
- method public abstract androidx.compose.ui.text.font.AndroidFont.TypefaceLoader getTypefaceLoader();
+ method public final androidx.compose.ui.text.font.AndroidFont.TypefaceLoader getTypefaceLoader();
property public final int loadingStrategy;
- property public abstract androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader;
+ property public final androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader;
}
public static interface AndroidFont.TypefaceLoader {
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index af8856a..7609a75 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -86,7 +86,7 @@
public final class MultiParagraph {
ctor public MultiParagraph(androidx.compose.ui.text.MultiParagraphIntrinsics intrinsics, optional int maxLines, optional boolean ellipsis, float width);
ctor @Deprecated public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader);
- ctor public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver);
+ ctor public MultiParagraph(androidx.compose.ui.text.AnnotatedString annotatedString, androidx.compose.ui.text.TextStyle style, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis);
method public androidx.compose.ui.text.style.ResolvedTextDirection getBidiRunDirection(int offset);
method public androidx.compose.ui.geometry.Rect getBoundingBox(int offset);
method public androidx.compose.ui.geometry.Rect getCursorRect(int offset);
@@ -208,7 +208,7 @@
public final class ParagraphKt {
method @Deprecated public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader);
- method public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver);
+ method public static androidx.compose.ui.text.Paragraph Paragraph(String text, androidx.compose.ui.text.TextStyle style, float width, androidx.compose.ui.unit.Density density, androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.SpanStyle>> spanStyles, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean ellipsis);
method public static androidx.compose.ui.text.Paragraph Paragraph(androidx.compose.ui.text.ParagraphIntrinsics paragraphIntrinsics, optional int maxLines, optional boolean ellipsis, float width);
}
@@ -550,11 +550,11 @@
package androidx.compose.ui.text.font {
public abstract class AndroidFont implements androidx.compose.ui.text.font.Font {
- ctor public AndroidFont(int loadingStrategy);
+ ctor public AndroidFont(int loadingStrategy, androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader);
method public final int getLoadingStrategy();
- method public abstract androidx.compose.ui.text.font.AndroidFont.TypefaceLoader getTypefaceLoader();
+ method public final androidx.compose.ui.text.font.AndroidFont.TypefaceLoader getTypefaceLoader();
property public final int loadingStrategy;
- property public abstract androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader;
+ property public final androidx.compose.ui.text.font.AndroidFont.TypefaceLoader typefaceLoader;
}
public static interface AndroidFont.TypefaceLoader {
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
index 4d0fad8..fe25aad 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
@@ -468,8 +468,7 @@
}
}
val fontFamily = FontFamily(
- object : AndroidFont(FontLoadingStrategy.Blocking) {
- override val typefaceLoader: TypefaceLoader = unstableLoader
+ object : AndroidFont(FontLoadingStrategy.Blocking, unstableLoader) {
override val weight: FontWeight = FontWeight.Normal
override val style: FontStyle = FontStyle.Normal
}
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt
index 3e69050..761092c 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt
@@ -567,8 +567,7 @@
return null
}
}
- val asyncFont = object : AndroidFont(FontLoadingStrategy.Async) {
- override val typefaceLoader: TypefaceLoader = cancellingLoader
+ val asyncFont = object : AndroidFont(FontLoadingStrategy.Async, cancellingLoader) {
override val weight: FontWeight = FontWeight.Normal
override val style: FontStyle = FontStyle.Normal
}
@@ -644,8 +643,7 @@
return Typeface.SERIF
}
}
- val font = object : AndroidFont(FontLoadingStrategy.Async) {
- override val typefaceLoader: TypefaceLoader = typefaceLoader
+ val font = object : AndroidFont(FontLoadingStrategy.Async, typefaceLoader) {
override val weight: FontWeight = FontWeight.W400
override val style: FontStyle = FontStyle.Normal
}
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/testutils/AsyncTestFonts.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/testutils/AsyncTestFonts.kt
index 616831f..c7aea13 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/testutils/AsyncTestFonts.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/testutils/AsyncTestFonts.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.graphics.Typeface
-import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.font.AndroidFont
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontLoadingStrategy.Companion.Async
@@ -132,39 +131,36 @@
}
}
-@OptIn(ExperimentalTextApi::class)
class AsyncFauxFont(
- override val typefaceLoader: AsyncTestTypefaceLoader,
+ typefaceLoader: AsyncTestTypefaceLoader,
override val weight: FontWeight = FontWeight.Normal,
override val style: FontStyle = FontStyle.Normal,
val name: String = "AsyncFauxFont"
-) : AndroidFont(Async) {
+) : AndroidFont(Async, typefaceLoader) {
override fun toString(): String {
return "$name[$weight, $style]"
}
}
-@OptIn(ExperimentalTextApi::class)
class OptionalFauxFont(
- override val typefaceLoader: AsyncTestTypefaceLoader,
+ typefaceLoader: AsyncTestTypefaceLoader,
internal val typeface: Typeface?,
override val weight: FontWeight = FontWeight.Normal,
override val style: FontStyle = FontStyle.Normal,
val name: String = "OptionalFauxFont"
-) : AndroidFont(OptionalLocal) {
+) : AndroidFont(OptionalLocal, typefaceLoader) {
override fun toString(): String {
return "$name[$weight, $style]"
}
}
-@OptIn(ExperimentalTextApi::class)
class BlockingFauxFont(
- override val typefaceLoader: AsyncTestTypefaceLoader,
+ typefaceLoader: AsyncTestTypefaceLoader,
internal val typeface: Typeface,
override val weight: FontWeight = FontWeight.Normal,
override val style: FontStyle = FontStyle.Normal,
val name: String = "BlockingFauxFont"
-) : AndroidFont(Blocking) {
+) : AndroidFont(Blocking, typefaceLoader) {
override fun toString(): String {
return "$name[$weight, $style]"
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt
index 16b12f5..a7dd397 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt
@@ -144,19 +144,16 @@
* fun Font(CustomFontDescription(...), FontWeight, FontStyle): Font
*```
*
+ * @param loadingStrategy loadingStrategy this font will provide in fallback chains
+ * @param typefaceLoader a loader that knows how to load this [AndroidFont], may be shared between
+ * several fonts
*/
abstract class AndroidFont @OptIn(ExperimentalTextApi::class) constructor(
- final override val loadingStrategy: FontLoadingStrategy
+ final override val loadingStrategy: FontLoadingStrategy,
+ val typefaceLoader: TypefaceLoader
) : Font {
/**
- * A loader that knows how to load this [AndroidFont].
- *
- * This may be shared between several fonts.
- */
- abstract val typefaceLoader: TypefaceLoader
-
- /**
* Loader for loading an [AndroidFont] and producing an [android.graphics.Typeface].
*
* This interface is not intended to be used by application developers for text display. To load
@@ -198,17 +195,12 @@
* to throw. Note that this method will never be called for fonts with
* [FontLoadingStrategy.Async].
*
- * This method may throw a [RuntimeException] if the font fails to load, though it is
- * preferred to return null if the font is [FontLoadingStrategy.OptionalLocal] for
- * performance.
- *
* It is possible for [loadBlocking] to be called for the same instance of [AndroidFont] in
* parallel. Implementations should support parallel concurrent loads, or de-dup.
*
* @param context current Android context for loading the font
* @param font the font to load which contains this loader as [AndroidFont.typefaceLoader]
* @return [android.graphics.Typeface] for loaded font, or null if the font fails to load
- * @throws RuntimeException subclass may optionally be thrown if the font fails to load
*/
fun loadBlocking(context: Context, font: AndroidFont): Typeface?
@@ -235,16 +227,17 @@
* @param context current Android context for loading the font
* @param font the font to load which contains this loader as [AndroidFont.typefaceLoader]
* @return [android.graphics.Typeface] for loaded font, or null if not available
- * @throws RuntimeException subclass may optionally be thrown if the font fails to load
*
*/
suspend fun awaitLoad(context: Context, font: AndroidFont): Typeface?
}
}
-internal abstract class AndroidPreloadedFont : AndroidFont(Blocking) {
+internal abstract class AndroidPreloadedFont : AndroidFont(
+ Blocking,
+ AndroidPreloadedFontTypefaceLoader
+) {
abstract val typefaceInternal: Typeface?
- override val typefaceLoader: TypefaceLoader = AndroidPreloadedFontTypefaceLoader
abstract val cacheKey: String?
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/DeviceFontFamilyNameFont.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/DeviceFontFamilyNameFont.kt
index 31dc85e..0ac895b 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/DeviceFontFamilyNameFont.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/DeviceFontFamilyNameFont.kt
@@ -76,8 +76,7 @@
private val familyName: DeviceFontFamilyName,
override val weight: FontWeight,
override val style: FontStyle
-) : AndroidFont(FontLoadingStrategy.OptionalLocal) {
- override val typefaceLoader: TypefaceLoader = NamedFontLoader
+) : AndroidFont(FontLoadingStrategy.OptionalLocal, NamedFontLoader) {
val resolvedTypeface: Typeface? = lookupFont(familyName.name, weight, style)
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt
index 8f7fd26..47f3237 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt
@@ -101,15 +101,15 @@
*
* @param annotatedString the text to be laid out
* @param style the [TextStyle] to be applied to the whole text
+ * @param width how wide the text is allowed to be
+ * @param density density of the device
+ * @param fontFamilyResolver to be used to load the font given in [SpanStyle]s
* @param placeholders a list of [Placeholder]s that specify ranges of text which will be
* skipped during layout and replaced with [Placeholder]. It's required that the range of each
* [Placeholder] doesn't cross paragraph boundary, otherwise [IllegalArgumentException] is
* thrown.
* @param maxLines the maximum number of lines that the text can have
* @param ellipsis whether to ellipsize text, applied only when [maxLines] is set
- * @param width how wide the text is allowed to be
- * @param density density of the device
- * @param fontFamilyResolver to be used to load the font given in [SpanStyle]s
*
* @see Placeholder
* @throws IllegalArgumentException if [ParagraphStyle.textDirection] is not set, or
@@ -118,12 +118,12 @@
constructor(
annotatedString: AnnotatedString,
style: TextStyle,
- placeholders: List<AnnotatedString.Range<Placeholder>> = listOf(),
- maxLines: Int = Int.MAX_VALUE,
- ellipsis: Boolean = false,
width: Float,
density: Density,
- fontFamilyResolver: FontFamily.Resolver
+ fontFamilyResolver: FontFamily.Resolver,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = listOf(),
+ maxLines: Int = Int.MAX_VALUE,
+ ellipsis: Boolean = false
) : this(
intrinsics = MultiParagraphIntrinsics(
annotatedString = annotatedString,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt
index 58dc8ae..8075225 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt
@@ -296,27 +296,27 @@
*
* @param text the text to be laid out
* @param style the [TextStyle] to be applied to the whole text
+ * @param width how wide the text is allowed to be
+ * @param density density of the device
+ * @param fontFamilyResolver [FontFamily.Resolver] to be used to load the font given in [SpanStyle]s
* @param spanStyles [SpanStyle]s to be applied to parts of text
* @param placeholders a list of placeholder metrics which tells [Paragraph] where should
* be left blank to leave space for inline elements.
* @param maxLines the maximum number of lines that the text can have
* @param ellipsis whether to ellipsize text, applied only when [maxLines] is set
- * @param width how wide the text is allowed to be
- * @param density density of the device
- * @param fontFamilyResolver [FontFamily.Resolver] to be used to load the font given in [SpanStyle]s
*
* @throws IllegalArgumentException if [ParagraphStyle.textDirection] is not set
*/
fun Paragraph(
text: String,
style: TextStyle,
+ width: Float,
+ density: Density,
+ fontFamilyResolver: FontFamily.Resolver,
spanStyles: List<AnnotatedString.Range<SpanStyle>> = listOf(),
placeholders: List<AnnotatedString.Range<Placeholder>> = listOf(),
maxLines: Int = DefaultMaxLines,
- ellipsis: Boolean = false,
- width: Float,
- density: Density,
- fontFamilyResolver: FontFamily.Resolver
+ ellipsis: Boolean = false
): Paragraph = ActualParagraph(
text,
style,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
index 896f35c..93005b5 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
@@ -22,9 +22,12 @@
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.text.style.lerp
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.isUnspecified
+private val DefaultLineHeight = TextUnit.Unspecified
+
/**
* Paragraph styling configuration for a paragraph. The difference between [SpanStyle] and
* `ParagraphStyle` is that, `ParagraphStyle` can be applied to a whole [Paragraph] while
@@ -38,8 +41,8 @@
* @param textAlign The alignment of the text within the lines of the paragraph.
* @param textDirection The algorithm to be used to resolve the final text direction:
* Left To Right or Right To Left.
- * @param textIndent The indentation of the paragraph.
* @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ * @param textIndent The indentation of the paragraph.
* @param platformStyle Platform specific [ParagraphStyle] parameters.
*
* @see Paragraph
@@ -57,6 +60,27 @@
@get:ExperimentalTextApi val platformStyle: PlatformParagraphStyle? = null
) {
+ /**
+ * Paragraph styling configuration for a paragraph. The difference between [SpanStyle] and
+ * `ParagraphStyle` is that, `ParagraphStyle` can be applied to a whole [Paragraph] while
+ * [SpanStyle] can be applied at the character level.
+ * Once a portion of the text is marked with a `ParagraphStyle`, that portion will be separated from
+ * the remaining as if a line feed character was added.
+ *
+ * @sample androidx.compose.ui.text.samples.ParagraphStyleSample
+ * @sample androidx.compose.ui.text.samples.ParagraphStyleAnnotatedStringsSample
+ *
+ * @param textAlign The alignment of the text within the lines of the paragraph.
+ * @param textDirection The algorithm to be used to resolve the final text direction:
+ * Left To Right or Right To Left.
+ * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ * @param textIndent The indentation of the paragraph.
+ *
+ * @see Paragraph
+ * @see AnnotatedString
+ * @see SpanStyle
+ * @see TextStyle
+ */
@OptIn(ExperimentalTextApi::class)
constructor(
textAlign: TextAlign? = null,
@@ -227,4 +251,16 @@
val startNonNull = start ?: PlatformParagraphStyle.Default
val stopNonNull = stop ?: PlatformParagraphStyle.Default
return startNonNull.lerp(stopNonNull, fraction)
-}
\ No newline at end of file
+}
+
+@OptIn(ExperimentalTextApi::class)
+internal fun resolveParagraphStyleDefaults(
+ style: ParagraphStyle,
+ direction: LayoutDirection
+) = ParagraphStyle(
+ textAlign = style.textAlign ?: TextAlign.Start,
+ textDirection = resolveTextDirection(direction, style.textDirection),
+ lineHeight = if (style.lineHeight.isUnspecified) DefaultLineHeight else style.lineHeight,
+ textIndent = style.textIndent ?: TextIndent.None,
+ platformStyle = style.platformStyle
+)
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
index a516e13..095bf2d 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
@@ -35,6 +35,15 @@
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.unit.sp
+
+/** The default font size if none is specified. */
+private val DefaultFontSize = 14.sp
+private val DefaultLetterSpacing = 0.sp
+private val DefaultBackgroundColor = Color.Transparent
+// TODO(nona): Introduce TextUnit.Original for representing "do not change the original result".
+// Need to distinguish from Inherit.
+private val DefaultColor = Color.Black
/**
* Styling configuration for a text span. This configuration only allows character level styling,
@@ -51,7 +60,7 @@
* @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
* @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
* @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight or
- * style cannot be found in the provided custom font family.
+ * style cannot be found in the provided font family.
* @param fontFamily The font family to be used when rendering the text.
* @param fontFeatureSettings The advanced typography settings provided by font. The format is the
* same as the CSS font-feature-settings attribute:
@@ -89,6 +98,38 @@
@get:ExperimentalTextApi val platformStyle: PlatformSpanStyle? = null
) {
+ /**
+ * Styling configuration for a text span. This configuration only allows character level styling,
+ * in order to set paragraph level styling such as line height, or text alignment please see
+ * [ParagraphStyle].
+ *
+ * @sample androidx.compose.ui.text.samples.SpanStyleSample
+ *
+ * @sample androidx.compose.ui.text.samples.AnnotatedStringBuilderSample
+ *
+ * @param color The text color.
+ * @param fontSize The size of glyphs (in logical pixels) to use when painting the text. This
+ * may be [TextUnit.Unspecified] for inheriting from another [SpanStyle].
+ * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
+ * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
+ * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
+ * or style cannot be found in the provided font family.
+ * @param fontFamily The font family to be used when rendering the text.
+ * @param fontFeatureSettings The advanced typography settings provided by font. The format is
+ * the same as the CSS font-feature-settings attribute:
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ * @param letterSpacing The amount of space (in em) to add between each letter.
+ * @param baselineShift The amount by which the text is shifted up from the current baseline.
+ * @param textGeometricTransform The geometric transformation applied the text.
+ * @param localeList The locale list used to select region-specific glyphs.
+ * @param background The background color for the text.
+ * @param textDecoration The decorations to paint on the text (e.g., an underline).
+ * @param shadow The shadow effect applied on the text.
+ *
+ * @see AnnotatedString
+ * @see TextStyle
+ * @see ParagraphStyle
+ */
@OptIn(ExperimentalTextApi::class)
constructor(
color: Color = Color.Unspecified,
@@ -416,3 +457,26 @@
val stopNonNull = stop ?: PlatformSpanStyle.Default
return startNonNull.lerp(stopNonNull, fraction)
}
+
+@OptIn(ExperimentalTextApi::class)
+internal fun resolveSpanStyleDefaults(style: SpanStyle) = SpanStyle(
+ color = style.color.takeOrElse { DefaultColor },
+ fontSize = if (style.fontSize.isUnspecified) DefaultFontSize else style.fontSize,
+ fontWeight = style.fontWeight ?: FontWeight.Normal,
+ fontStyle = style.fontStyle ?: FontStyle.Normal,
+ fontSynthesis = style.fontSynthesis ?: FontSynthesis.All,
+ fontFamily = style.fontFamily ?: FontFamily.Default,
+ fontFeatureSettings = style.fontFeatureSettings ?: "",
+ letterSpacing = if (style.letterSpacing.isUnspecified) {
+ DefaultLetterSpacing
+ } else {
+ style.letterSpacing
+ },
+ baselineShift = style.baselineShift ?: BaselineShift.None,
+ textGeometricTransform = style.textGeometricTransform ?: TextGeometricTransform.None,
+ localeList = style.localeList ?: LocaleList.current,
+ background = style.background.takeOrElse { DefaultBackgroundColor },
+ textDecoration = style.textDecoration ?: TextDecoration.None,
+ shadow = style.shadow ?: Shadow.None,
+ platformStyle = style.platformStyle
+)
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
index 071dd58..a1bb217 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
@@ -20,7 +20,6 @@
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontSynthesis
@@ -34,84 +33,151 @@
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.isUnspecified
-import androidx.compose.ui.unit.sp
-
-/** The default font size if none is specified. */
-private val DefaultFontSize = 14.sp
-private val DefaultLetterSpacing = 0.sp
-private val DefaultBackgroundColor = Color.Transparent
-
-// TODO(nona): Introduce TextUnit.Original for representing "do not change the original result".
-// Need to distinguish from Inherit.
-private val DefaultLineHeight = TextUnit.Unspecified
-private val DefaultColor = Color.Black
/**
* Styling configuration for a `Text`.
*
* @sample androidx.compose.ui.text.samples.TextStyleSample
*
- * @param color The text color.
- * @param fontSize The size of glyphs to use when painting the text. This
- * may be [TextUnit.Unspecified] for inheriting from another [TextStyle].
- * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
- * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
- * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight or
- * style cannot be found in the provided custom font family.
- * @param fontFamily The font family to be used when rendering the text.
- * @param fontFeatureSettings The advanced typography settings provided by font. The format is the
- * same as the CSS font-feature-settings attribute:
- * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
- * @param letterSpacing The amount of space to add between each letter.
- * @param baselineShift The amount by which the text is shifted up from the current baseline.
- * @param textGeometricTransform The geometric transformation applied the text.
- * @param localeList The locale list used to select region-specific glyphs.
- * @param background The background color for the text.
- * @param textDecoration The decorations to paint on the text (e.g., an underline).
- * @param shadow The shadow effect applied on the text.
- * @param textAlign The alignment of the text within the lines of the paragraph.
- * @param textDirection The algorithm to be used to resolve the final text and paragraph
- * direction: Left To Right or Right To Left. If no value is provided the system will use the
- * [LayoutDirection] as the primary signal.
- * @param textIndent The indentation of the paragraph.
- * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
* @param platformStyle Platform specific [TextStyle] parameters.
*
* @see AnnotatedString
* @see SpanStyle
* @see ParagraphStyle
*/
-// NOTE(text-perf-review): I suggest we implement this class as
-// class TextStyle(val spanStyle: SpanStyle, val paragraphStyle: ParagraphStyle)
-// This would allow for more efficient merge implementations where we don't have to reallocate
-// each of the parts, and we always end up calling toSpanStyle() and toParagraphStyle() anyway.
-// This would also result in a slightly better equals implementation when we are comparing things
-// with shared parts (ie, "Structural sharing")
@Immutable
-class TextStyle @ExperimentalTextApi constructor(
- val color: Color = Color.Unspecified,
- val fontSize: TextUnit = TextUnit.Unspecified,
- val fontWeight: FontWeight? = null,
- val fontStyle: FontStyle? = null,
- val fontSynthesis: FontSynthesis? = null,
- val fontFamily: FontFamily? = null,
- val fontFeatureSettings: String? = null,
- val letterSpacing: TextUnit = TextUnit.Unspecified,
- val baselineShift: BaselineShift? = null,
- val textGeometricTransform: TextGeometricTransform? = null,
- val localeList: LocaleList? = null,
- val background: Color = Color.Unspecified,
- val textDecoration: TextDecoration? = null,
- val shadow: Shadow? = null,
- val textAlign: TextAlign? = null,
- val textDirection: TextDirection? = null,
- val lineHeight: TextUnit = TextUnit.Unspecified,
- val textIndent: TextIndent? = null,
+class TextStyle
+@OptIn(ExperimentalTextApi::class)
+internal constructor(
+ internal val spanStyle: SpanStyle,
+ internal val paragraphStyle: ParagraphStyle,
@Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
@get:ExperimentalTextApi val platformStyle: PlatformTextStyle? = null
) {
@OptIn(ExperimentalTextApi::class)
+ internal constructor(
+ spanStyle: SpanStyle,
+ paragraphStyle: ParagraphStyle,
+ ) : this(
+ spanStyle = spanStyle,
+ paragraphStyle = paragraphStyle,
+ platformStyle = createPlatformTextStyleInternal(
+ spanStyle.platformStyle,
+ paragraphStyle.platformStyle
+ )
+ )
+
+ /**
+ * Styling configuration for a `Text`.
+ *
+ * @sample androidx.compose.ui.text.samples.TextStyleSample
+ *
+ * @param color The text color.
+ * @param fontSize The size of glyphs to use when painting the text. This
+ * may be [TextUnit.Unspecified] for inheriting from another [TextStyle].
+ * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
+ * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
+ * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
+ * or style cannot be found in the provided font family.
+ * @param fontFamily The font family to be used when rendering the text.
+ * @param fontFeatureSettings The advanced typography settings provided by font. The format is
+ * the same as the CSS font-feature-settings attribute:
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ * @param letterSpacing The amount of space to add between each letter.
+ * @param baselineShift The amount by which the text is shifted up from the current baseline.
+ * @param textGeometricTransform The geometric transformation applied the text.
+ * @param localeList The locale list used to select region-specific glyphs.
+ * @param background The background color for the text.
+ * @param textDecoration The decorations to paint on the text (e.g., an underline).
+ * @param shadow The shadow effect applied on the text.
+ * @param textAlign The alignment of the text within the lines of the paragraph.
+ * @param textDirection The algorithm to be used to resolve the final text and paragraph
+ * direction: Left To Right or Right To Left. If no value is provided the system will use the
+ * [LayoutDirection] as the primary signal.
+ * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ * @param textIndent The indentation of the paragraph.
+ * @param platformStyle Platform specific [TextStyle] parameters.
+ */
+ @ExperimentalTextApi
+ constructor(
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontWeight: FontWeight? = null,
+ fontStyle: FontStyle? = null,
+ fontSynthesis: FontSynthesis? = null,
+ fontFamily: FontFamily? = null,
+ fontFeatureSettings: String? = null,
+ letterSpacing: TextUnit = TextUnit.Unspecified,
+ baselineShift: BaselineShift? = null,
+ textGeometricTransform: TextGeometricTransform? = null,
+ localeList: LocaleList? = null,
+ background: Color = Color.Unspecified,
+ textDecoration: TextDecoration? = null,
+ shadow: Shadow? = null,
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ textIndent: TextIndent? = null,
+ platformStyle: PlatformTextStyle? = null
+ ) : this(
+ SpanStyle(
+ color = color,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle,
+ fontSynthesis = fontSynthesis,
+ fontFamily = fontFamily,
+ fontFeatureSettings = fontFeatureSettings,
+ letterSpacing = letterSpacing,
+ baselineShift = baselineShift,
+ textGeometricTransform = textGeometricTransform,
+ localeList = localeList,
+ background = background,
+ textDecoration = textDecoration,
+ shadow = shadow,
+ platformStyle = platformStyle?.spanStyle
+ ),
+ ParagraphStyle(
+ textAlign = textAlign,
+ textDirection = textDirection,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle?.paragraphStyle
+ ),
+ platformStyle = platformStyle
+ )
+
+ /**
+ * Styling configuration for a `Text`.
+ *
+ * @sample androidx.compose.ui.text.samples.TextStyleSample
+ *
+ * @param color The text color.
+ * @param fontSize The size of glyphs to use when painting the text. This
+ * may be [TextUnit.Unspecified] for inheriting from another [TextStyle].
+ * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
+ * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
+ * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
+ * or style cannot be found in the provided font family.
+ * @param fontFamily The font family to be used when rendering the text.
+ * @param fontFeatureSettings The advanced typography settings provided by font. The format is
+ * the same as the CSS font-feature-settings attribute:
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ * @param letterSpacing The amount of space to add between each letter.
+ * @param baselineShift The amount by which the text is shifted up from the current baseline.
+ * @param textGeometricTransform The geometric transformation applied the text.
+ * @param localeList The locale list used to select region-specific glyphs.
+ * @param background The background color for the text.
+ * @param textDecoration The decorations to paint on the text (e.g., an underline).
+ * @param shadow The shadow effect applied on the text.
+ * @param textAlign The alignment of the text within the lines of the paragraph.
+ * @param textDirection The algorithm to be used to resolve the final text and paragraph
+ * direction: Left To Right or Right To Left. If no value is provided the system will use the
+ * [LayoutDirection] as the primary signal.
+ * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ * @param textIndent The indentation of the paragraph.
+ */
+ @OptIn(ExperimentalTextApi::class)
constructor(
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
@@ -153,70 +219,11 @@
platformStyle = null
)
- @OptIn(ExperimentalTextApi::class)
- internal constructor(spanStyle: SpanStyle, paragraphStyle: ParagraphStyle) : this(
- color = spanStyle.color,
- fontSize = spanStyle.fontSize,
- fontWeight = spanStyle.fontWeight,
- fontStyle = spanStyle.fontStyle,
- fontSynthesis = spanStyle.fontSynthesis,
- fontFamily = spanStyle.fontFamily,
- fontFeatureSettings = spanStyle.fontFeatureSettings,
- letterSpacing = spanStyle.letterSpacing,
- baselineShift = spanStyle.baselineShift,
- textGeometricTransform = spanStyle.textGeometricTransform,
- localeList = spanStyle.localeList,
- background = spanStyle.background,
- textDecoration = spanStyle.textDecoration,
- shadow = spanStyle.shadow,
- textAlign = paragraphStyle.textAlign,
- textDirection = paragraphStyle.textDirection,
- lineHeight = paragraphStyle.lineHeight,
- textIndent = paragraphStyle.textIndent,
- platformStyle = createPlatformTextStyleInternal(
- spanStyle.platformStyle,
- paragraphStyle.platformStyle
- )
- )
-
- init {
- if (!lineHeight.isUnspecified) {
- // Since we are checking if it's negative, no need to convert Sp into Px at this point.
- check(lineHeight.value >= 0f) {
- "lineHeight can't be negative (${lineHeight.value})"
- }
- }
- }
-
- @OptIn(ExperimentalTextApi::class)
@Stable
- fun toSpanStyle(): SpanStyle = SpanStyle(
- color = color,
- fontSize = fontSize,
- fontWeight = fontWeight,
- fontStyle = fontStyle,
- fontSynthesis = fontSynthesis,
- fontFamily = fontFamily,
- fontFeatureSettings = fontFeatureSettings,
- letterSpacing = letterSpacing,
- baselineShift = baselineShift,
- textGeometricTransform = textGeometricTransform,
- localeList = localeList,
- background = background,
- textDecoration = textDecoration,
- shadow = shadow,
- platformStyle = platformStyle?.spanStyle
- )
+ fun toSpanStyle(): SpanStyle = spanStyle
- @OptIn(ExperimentalTextApi::class)
@Stable
- fun toParagraphStyle(): ParagraphStyle = ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
- lineHeight = lineHeight,
- textIndent = textIndent,
- platformStyle = platformStyle?.paragraphStyle
- )
+ fun toParagraphStyle(): ParagraphStyle = paragraphStyle
/**
* Returns a new text style that is a combination of this style and the given [other] style.
@@ -282,24 +289,24 @@
@OptIn(ExperimentalTextApi::class)
fun copy(
- color: Color = this.color,
- fontSize: TextUnit = this.fontSize,
- fontWeight: FontWeight? = this.fontWeight,
- fontStyle: FontStyle? = this.fontStyle,
- fontSynthesis: FontSynthesis? = this.fontSynthesis,
- fontFamily: FontFamily? = this.fontFamily,
- fontFeatureSettings: String? = this.fontFeatureSettings,
- letterSpacing: TextUnit = this.letterSpacing,
- baselineShift: BaselineShift? = this.baselineShift,
- textGeometricTransform: TextGeometricTransform? = this.textGeometricTransform,
- localeList: LocaleList? = this.localeList,
- background: Color = this.background,
- textDecoration: TextDecoration? = this.textDecoration,
- shadow: Shadow? = this.shadow,
- textAlign: TextAlign? = this.textAlign,
- textDirection: TextDirection? = this.textDirection,
- lineHeight: TextUnit = this.lineHeight,
- textIndent: TextIndent? = this.textIndent
+ color: Color = this.spanStyle.color,
+ fontSize: TextUnit = this.spanStyle.fontSize,
+ fontWeight: FontWeight? = this.spanStyle.fontWeight,
+ fontStyle: FontStyle? = this.spanStyle.fontStyle,
+ fontSynthesis: FontSynthesis? = this.spanStyle.fontSynthesis,
+ fontFamily: FontFamily? = this.spanStyle.fontFamily,
+ fontFeatureSettings: String? = this.spanStyle.fontFeatureSettings,
+ letterSpacing: TextUnit = this.spanStyle.letterSpacing,
+ baselineShift: BaselineShift? = this.spanStyle.baselineShift,
+ textGeometricTransform: TextGeometricTransform? = this.spanStyle.textGeometricTransform,
+ localeList: LocaleList? = this.spanStyle.localeList,
+ background: Color = this.spanStyle.background,
+ textDecoration: TextDecoration? = this.spanStyle.textDecoration,
+ shadow: Shadow? = this.spanStyle.shadow,
+ textAlign: TextAlign? = this.paragraphStyle.textAlign,
+ textDirection: TextDirection? = this.paragraphStyle.textDirection,
+ lineHeight: TextUnit = this.paragraphStyle.lineHeight,
+ textIndent: TextIndent? = this.paragraphStyle.textIndent
): TextStyle {
return TextStyle(
color = color,
@@ -326,24 +333,24 @@
@ExperimentalTextApi
fun copy(
- color: Color = this.color,
- fontSize: TextUnit = this.fontSize,
- fontWeight: FontWeight? = this.fontWeight,
- fontStyle: FontStyle? = this.fontStyle,
- fontSynthesis: FontSynthesis? = this.fontSynthesis,
- fontFamily: FontFamily? = this.fontFamily,
- fontFeatureSettings: String? = this.fontFeatureSettings,
- letterSpacing: TextUnit = this.letterSpacing,
- baselineShift: BaselineShift? = this.baselineShift,
- textGeometricTransform: TextGeometricTransform? = this.textGeometricTransform,
- localeList: LocaleList? = this.localeList,
- background: Color = this.background,
- textDecoration: TextDecoration? = this.textDecoration,
- shadow: Shadow? = this.shadow,
- textAlign: TextAlign? = this.textAlign,
- textDirection: TextDirection? = this.textDirection,
- lineHeight: TextUnit = this.lineHeight,
- textIndent: TextIndent? = this.textIndent,
+ color: Color = this.spanStyle.color,
+ fontSize: TextUnit = this.spanStyle.fontSize,
+ fontWeight: FontWeight? = this.spanStyle.fontWeight,
+ fontStyle: FontStyle? = this.spanStyle.fontStyle,
+ fontSynthesis: FontSynthesis? = this.spanStyle.fontSynthesis,
+ fontFamily: FontFamily? = this.spanStyle.fontFamily,
+ fontFeatureSettings: String? = this.spanStyle.fontFeatureSettings,
+ letterSpacing: TextUnit = this.spanStyle.letterSpacing,
+ baselineShift: BaselineShift? = this.spanStyle.baselineShift,
+ textGeometricTransform: TextGeometricTransform? = this.spanStyle.textGeometricTransform,
+ localeList: LocaleList? = this.spanStyle.localeList,
+ background: Color = this.spanStyle.background,
+ textDecoration: TextDecoration? = this.spanStyle.textDecoration,
+ shadow: Shadow? = this.spanStyle.shadow,
+ textAlign: TextAlign? = this.paragraphStyle.textAlign,
+ textDirection: TextDirection? = this.paragraphStyle.textDirection,
+ lineHeight: TextUnit = this.paragraphStyle.lineHeight,
+ textIndent: TextIndent? = this.paragraphStyle.textIndent,
platformStyle: PlatformTextStyle? = this.platformStyle
): TextStyle {
return TextStyle(
@@ -369,29 +376,110 @@
)
}
+ /**
+ * The text color.
+ */
+ val color: Color get() = this.spanStyle.color
+
+ /**
+ * The size of glyphs to use when painting the text. This
+ * may be [TextUnit.Unspecified] for inheriting from another [TextStyle].
+ */
+ val fontSize: TextUnit get() = this.spanStyle.fontSize
+
+ /**
+ * The typeface thickness to use when painting the text (e.g., bold).
+ */
+ val fontWeight: FontWeight? get() = this.spanStyle.fontWeight
+
+ /**
+ * The typeface variant to use when drawing the letters (e.g., italic).
+ */
+ val fontStyle: FontStyle? get() = this.spanStyle.fontStyle
+
+ /**
+ * Whether to synthesize font weight and/or style when the requested weight or
+ * style cannot be found in the provided font family.
+ */
+ val fontSynthesis: FontSynthesis? get() = this.spanStyle.fontSynthesis
+
+ /**
+ * The font family to be used when rendering the text.
+ */
+ val fontFamily: FontFamily? get() = this.spanStyle.fontFamily
+
+ /**
+ * The advanced typography settings provided by font. The format is the
+ * same as the CSS font-feature-settings attribute:
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ */
+ val fontFeatureSettings: String? get() = this.spanStyle.fontFeatureSettings
+
+ /**
+ * The amount of space to add between each letter.
+ */
+ val letterSpacing: TextUnit get() = this.spanStyle.letterSpacing
+
+ /**
+ * The amount by which the text is shifted up from the current baseline.
+ */
+ val baselineShift: BaselineShift? get() = this.spanStyle.baselineShift
+
+ /**
+ * The geometric transformation applied the text.
+ */
+ val textGeometricTransform: TextGeometricTransform? get() =
+ this.spanStyle.textGeometricTransform
+
+ /**
+ * The locale list used to select region-specific glyphs.
+ */
+ val localeList: LocaleList? get() = this.spanStyle.localeList
+
+ /**
+ * The background color for the text.
+ */
+ val background: Color get() = this.spanStyle.background
+
+ /**
+ * The decorations to paint on the text (e.g., an underline).
+ */
+ val textDecoration: TextDecoration? get() = this.spanStyle.textDecoration
+
+ /**
+ * The shadow effect applied on the text.
+ */
+ val shadow: Shadow? get() = this.spanStyle.shadow
+
+ /**
+ * The alignment of the text within the lines of the paragraph.
+ */
+ val textAlign: TextAlign? get() = this.paragraphStyle.textAlign
+
+ /**
+ * The algorithm to be used to resolve the final text and paragraph
+ * direction: Left To Right or Right To Left. If no value is provided the system will use the
+ * [LayoutDirection] as the primary signal.
+ */
+ val textDirection: TextDirection? get() = this.paragraphStyle.textDirection
+
+ /**
+ * Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ */
+ val lineHeight: TextUnit get() = this.paragraphStyle.lineHeight
+
+ /**
+ * The indentation of the paragraph.
+ */
+ val textIndent: TextIndent? get() = this.paragraphStyle.textIndent
+
@OptIn(ExperimentalTextApi::class)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TextStyle) return false
- if (color != other.color) return false
- if (fontSize != other.fontSize) return false
- if (fontWeight != other.fontWeight) return false
- if (fontStyle != other.fontStyle) return false
- if (fontSynthesis != other.fontSynthesis) return false
- if (fontFamily != other.fontFamily) return false
- if (fontFeatureSettings != other.fontFeatureSettings) return false
- if (letterSpacing != other.letterSpacing) return false
- if (baselineShift != other.baselineShift) return false
- if (textGeometricTransform != other.textGeometricTransform) return false
- if (localeList != other.localeList) return false
- if (background != other.background) return false
- if (textDecoration != other.textDecoration) return false
- if (shadow != other.shadow) return false
- if (textAlign != other.textAlign) return false
- if (textDirection != other.textDirection) return false
- if (lineHeight != other.lineHeight) return false
- if (textIndent != other.textIndent) return false
+ if (spanStyle != other.spanStyle) return false
+ if (paragraphStyle != other.paragraphStyle) return false
if (platformStyle != other.platformStyle) return false
return true
@@ -399,24 +487,8 @@
@OptIn(ExperimentalTextApi::class)
override fun hashCode(): Int {
- var result = color.hashCode()
- result = 31 * result + fontSize.hashCode()
- result = 31 * result + (fontWeight?.hashCode() ?: 0)
- result = 31 * result + (fontStyle?.hashCode() ?: 0)
- result = 31 * result + (fontSynthesis?.hashCode() ?: 0)
- result = 31 * result + (fontFamily?.hashCode() ?: 0)
- result = 31 * result + (fontFeatureSettings?.hashCode() ?: 0)
- result = 31 * result + letterSpacing.hashCode()
- result = 31 * result + (baselineShift?.hashCode() ?: 0)
- result = 31 * result + (textGeometricTransform?.hashCode() ?: 0)
- result = 31 * result + (localeList?.hashCode() ?: 0)
- result = 31 * result + background.hashCode()
- result = 31 * result + (textDecoration?.hashCode() ?: 0)
- result = 31 * result + (shadow?.hashCode() ?: 0)
- result = 31 * result + (textAlign?.hashCode() ?: 0)
- result = 31 * result + (textDirection?.hashCode() ?: 0)
- result = 31 * result + lineHeight.hashCode()
- result = 31 * result + (textIndent?.hashCode() ?: 0)
+ var result = spanStyle.hashCode()
+ result = 31 * result + paragraphStyle.hashCode()
result = 31 * result + (platformStyle?.hashCode() ?: 0)
return result
}
@@ -484,28 +556,8 @@
*/
@OptIn(ExperimentalTextApi::class)
fun resolveDefaults(style: TextStyle, direction: LayoutDirection) = TextStyle(
- color = style.color.takeOrElse { DefaultColor },
- fontSize = if (style.fontSize.isUnspecified) DefaultFontSize else style.fontSize,
- fontWeight = style.fontWeight ?: FontWeight.Normal,
- fontStyle = style.fontStyle ?: FontStyle.Normal,
- fontSynthesis = style.fontSynthesis ?: FontSynthesis.All,
- fontFamily = style.fontFamily ?: FontFamily.Default,
- fontFeatureSettings = style.fontFeatureSettings ?: "",
- letterSpacing = if (style.letterSpacing.isUnspecified) {
- DefaultLetterSpacing
- } else {
- style.letterSpacing
- },
- baselineShift = style.baselineShift ?: BaselineShift.None,
- textGeometricTransform = style.textGeometricTransform ?: TextGeometricTransform.None,
- localeList = style.localeList ?: LocaleList.current,
- background = style.background.takeOrElse { DefaultBackgroundColor },
- textDecoration = style.textDecoration ?: TextDecoration.None,
- shadow = style.shadow ?: Shadow.None,
- textAlign = style.textAlign ?: TextAlign.Start,
- textDirection = resolveTextDirection(direction, style.textDirection),
- lineHeight = if (style.lineHeight.isUnspecified) DefaultLineHeight else style.lineHeight,
- textIndent = style.textIndent ?: TextIndent.None,
+ spanStyle = resolveSpanStyleDefaults(style.spanStyle),
+ paragraphStyle = resolveParagraphStyleDefaults(style.paragraphStyle, direction),
platformStyle = style.platformStyle
)
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
index 3011228..a061646 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
@@ -80,7 +80,7 @@
* Platform specific [FontFamily] will resolve according to platform behavior, as documented
* for each [FontFamily].
*
- * @param fontFamily family to resolve
+ * @param fontFamily family to resolve. If `null` will use [FontFamily.Default]
* @param fontWeight desired font weight
* @param fontStyle desired font style
* @param fontSynthesis configuration for font synthesis
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextSpanParagraphStyleTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextSpanParagraphStyleTest.kt
index 0244e40..78a0d34 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextSpanParagraphStyleTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextSpanParagraphStyleTest.kt
@@ -17,59 +17,112 @@
package androidx.compose.ui.text
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assert_
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
+import kotlin.reflect.KProperty1
import kotlin.reflect.KType
+import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
@RunWith(JUnit4::class)
class TextSpanParagraphStyleTest {
@Test
- fun spanStyle_is_covered_by_TextStyle() {
+ fun spanStyle_constructor_is_covered_by_TextStyle() {
val spanStyleParameters = constructorParams(SpanStyle::class)
- val textStyleParameters = constructorParams(TextStyle::class)
- // for every SpanStyle parameter, expecting that parameter to be in TextStyle
- // this guards that if a parameter is added to SpanStyle, it should be added
- // to TextStyle
- assertThat(textStyleParameters).containsAtLeastElementsIn(spanStyleParameters)
+ for (constructor in TextStyle::class.constructors) {
+ val textStyleParameters = constructorParams(constructor)
+ // for every SpanStyle parameter, expecting that parameter to be in TextStyle
+ // this guards that if a parameter is added to SpanStyle, it should be added
+ // to TextStyle
+ if (textStyleParameters.containsAll(spanStyleParameters)) return
+ }
+
+ assert_().fail()
+ }
+
+ @Test
+ fun spanStyle_properties_is_covered_by_TextStyle() {
+ val spanStyleProperties = memberProperties(SpanStyle::class)
+ val textStyleProperties = memberProperties(TextStyle::class)
+ assertThat(textStyleProperties).containsAtLeastElementsIn(spanStyleProperties)
}
@Test
fun paragraphStyle_is_covered_by_TextStyle() {
val paragraphStyleParameters = constructorParams(ParagraphStyle::class)
- val textStyleParameters = constructorParams(TextStyle::class)
- // for every ParagraphStyle parameter, expecting that parameter to be in TextStyle
- // this guards that if a parameter is added to ParagraphStyle, it should be added
- // to TextStyle
- assertThat(textStyleParameters).containsAtLeastElementsIn(paragraphStyleParameters)
+ for (constructor in TextStyle::class.constructors) {
+ val textStyleParameters = constructorParams(constructor)
+ // for every ParagraphStyle parameter, expecting that parameter to be in TextStyle
+ // this guards that if a parameter is added to ParagraphStyle, it should be added
+ // to TextStyle
+ if (textStyleParameters.containsAll(paragraphStyleParameters)) return
+ }
+
+ assert_().fail()
+ }
+
+ @Test
+ fun paragraphStyle_properties_is_covered_by_TextStyle() {
+ val paragraphStyleProperties = memberProperties(ParagraphStyle::class)
+ val textStyleProperties = memberProperties(TextStyle::class)
+ assertThat(textStyleProperties).containsAtLeastElementsIn(paragraphStyleProperties)
}
@Test
fun textStyle_covered_by_ParagraphStyle_and_SpanStyle() {
val spanStyleParameters = constructorParams(SpanStyle::class)
val paragraphStyleParameters = constructorParams(ParagraphStyle::class)
- val textStyleParameters = constructorParams(TextStyle::class)
+ val allParameters = spanStyleParameters + paragraphStyleParameters
- // for every TextStyle parameter, expecting that parameter to be in either ParagraphStyle
- // or SpanStyle
- // this guards that if a parameter is added to TextStyle, it should be added
- // to one of SpanStyle or ParagraphStyle
- assertThat(spanStyleParameters + paragraphStyleParameters).containsAtLeastElementsIn(
- textStyleParameters
- )
+ for (constructor in TextStyle::class.constructors) {
+ val textStyleParameters = constructorParams(constructor)
+ // for every TextStyle parameter, expecting that parameter to be in either ParagraphStyle
+ // or SpanStyle
+ // this guards that if a parameter is added to TextStyle, it should be added
+ // to one of SpanStyle or ParagraphStyle
+ if (allParameters.containsAll(textStyleParameters) &&
+ textStyleParameters.containsAll(allParameters)
+ ) return
+ }
+
+ assert_().fail()
+ }
+
+ @Test
+ fun testStyle_properties_is_covered_by_ParagraphStyle_and_SpanStyle() {
+ val spanStyleProperties = memberProperties(SpanStyle::class)
+ val paragraphStyleProperties = memberProperties(ParagraphStyle::class)
+ val allProperties = spanStyleProperties + paragraphStyleProperties
+ val textStyleProperties = memberProperties(TextStyle::class).filter {
+ it.name != "spanStyle" && it.name != "paragraphStyle"
+ }
+ assertThat(allProperties).containsAtLeastElementsIn(textStyleProperties)
}
private fun <T : Any> constructorParams(clazz: KClass<T>): List<Parameter> {
- return clazz.primaryConstructor?.parameters?.map { Parameter(it) }?.filter {
+ return clazz.primaryConstructor?.let { constructorParams(it) } ?: listOf()
+ }
+
+ private fun <T : Any> constructorParams(constructor: KFunction<T>): List<Parameter> {
+ return constructor.parameters.map { Parameter(it) }.filter {
// types of platformStyle is different for each of TextStyle/ParagraphStyle/SpanStyle
"platformStyle" != it.name
- } ?: listOf()
+ }
+ }
+
+ private fun <T : Any> memberProperties(clazz: KClass<T>): Collection<Property> {
+ return clazz.memberProperties.map { Property(it) }.filter {
+ // types of platformStyle is different for each of TextStyle/ParagraphStyle/SpanStyle
+ "platformStyle" != it.name
+ }
}
private data class Parameter(
@@ -87,4 +140,11 @@
parameter.kind
)
}
+
+ private data class Property(
+ val name: String?,
+ val type: KType
+ ) {
+ constructor(parameter: KProperty1<*, *>) : this(parameter.name, parameter.returnType)
+ }
}
diff --git a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
index 655d964..20cc5c90 100644
--- a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
+++ b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
@@ -67,7 +67,7 @@
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.lang.reflect.Method
private const val TOOLS_NS_URI = "http://schemas.android.com/tools"
@@ -663,7 +663,7 @@
private fun init(attrs: AttributeSet) {
// ComposeView and lifecycle initialization
ViewTreeLifecycleOwner.set(this, FakeSavedStateRegistryOwner)
- ViewTreeSavedStateRegistryOwner.set(this, FakeSavedStateRegistryOwner)
+ setViewTreeSavedStateRegistryOwner(FakeSavedStateRegistryOwner)
ViewTreeViewModelStoreOwner.set(this, FakeViewModelStoreOwner)
addView(composeView)
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index aa7c3ed..f7fe2bb 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -59,7 +59,7 @@
implementation("androidx.core:core:1.5.0")
implementation('androidx.collection:collection:1.0.0')
implementation("androidx.customview:customview-poolingcontainer:1.0.0-alpha01")
- implementation project(":savedstate:savedstate")
+ implementation project(":savedstate:savedstate-ktx")
implementation("androidx.lifecycle:lifecycle-common-java8:2.3.0")
implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
@@ -146,7 +146,7 @@
implementation(libs.kotlinCoroutinesAndroid)
implementation("androidx.customview:customview-poolingcontainer:1.0.0-alpha01")
- implementation project(":savedstate:savedstate")
+ implementation project(":savedstate:savedstate-ktx")
implementation("androidx.lifecycle:lifecycle-common-java8:2.3.0")
implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierLocalSameLayoutNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierLocalSameLayoutNodeTest.kt
index 8d0171f..413e05a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierLocalSameLayoutNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierLocalSameLayoutNodeTest.kt
@@ -420,4 +420,32 @@
// Assert.
rule.runOnIdle { assertThat(readString).isEqualTo(providedValue) }
}
+
+ /**
+ * We don't want the same modifier local invalidated multiple times for the same change.
+ */
+ @Test
+ fun modifierLocalCallsOnce() {
+ var calls = 0
+ val localString = modifierLocalOf { defaultValue }
+ val provider1 = Modifier.modifierLocalProvider(localString) { "ProvidedValue" }
+ val provider2 = Modifier.modifierLocalProvider(localString) { "Another ProvidedValue" }
+ var providerChoice by mutableStateOf(provider1)
+ val consumer = Modifier.modifierLocalConsumer {
+ localString.current // read the value
+ calls++
+ }
+ rule.setContent {
+ Box(providerChoice.then(consumer))
+ }
+
+ rule.runOnIdle {
+ calls = 0
+ providerChoice = provider2
+ }
+
+ rule.runOnIdle {
+ assertThat(calls).isEqualTo(1)
+ }
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/owners/SavedStateRegistryOwnerInFragmentTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/owners/SavedStateRegistryOwnerInFragmentTest.kt
index 59b7685..0a290e5 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/owners/SavedStateRegistryOwnerInFragmentTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/owners/SavedStateRegistryOwnerInFragmentTest.kt
@@ -25,7 +25,7 @@
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentContainerView
import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import org.junit.Assert.assertEquals
@@ -64,9 +64,8 @@
.replace(100, fragment)
.commit()
}
-
assertTrue(fragment.latch.await(1, TimeUnit.SECONDS))
- assertEquals(ViewTreeSavedStateRegistryOwner.get(fragment.view!!), fragment.owner)
+ assertEquals(fragment.requireView().findViewTreeSavedStateRegistryOwner(), fragment.owner)
}
class TestFragment : Fragment() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index e32c9a5..9344d54 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -71,7 +71,7 @@
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.test.espresso.Espresso
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
@@ -507,7 +507,7 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
childViewTreeSavedStateRegistryOwner =
- ViewTreeSavedStateRegistryOwner.get(this)
+ findViewTreeSavedStateRegistryOwner()
}
}
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index c27e78a..a9db922 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -154,7 +154,7 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import java.lang.reflect.Method
import kotlin.math.roundToInt
@@ -458,7 +458,7 @@
/**
* List of lambdas to be called when [onEndApplyChanges] is called.
*/
- private val endApplyChangesListeners = mutableVectorOf<() -> Unit>()
+ private val endApplyChangesListeners = mutableVectorOf<(() -> Unit)?>()
/**
* Runnable used to update the pointer position after layout. If
@@ -617,18 +617,26 @@
if (childAndroidViews != null) {
clearChildInvalidObservations(childAndroidViews)
}
- // Iterate through the whole list, even if listeners are added.
- var i = 0
- while (i < endApplyChangesListeners.size) {
- val listener = endApplyChangesListeners[i]
- listener()
- i++
+ // Listeners can add more items to the list and we want to ensure that they
+ // are executed after being added, so loop until the list is empty
+ while (endApplyChangesListeners.isNotEmpty()) {
+ val size = endApplyChangesListeners.size
+ for (i in 0 until size) {
+ val listener = endApplyChangesListeners[i]
+ // null out the item so that if the listener is re-added then we execute it again.
+ endApplyChangesListeners[i] = null
+ listener?.invoke()
+ }
+ // Remove all the items that were visited. Removing items shifts all items after
+ // to the front of the list, so removing in a chunk is cheaper than removing one-by-one
+ endApplyChangesListeners.removeRange(0, size)
}
- endApplyChangesListeners.clear()
}
override fun registerOnEndApplyChangesListener(listener: () -> Unit) {
- endApplyChangesListeners += listener
+ if (listener !in endApplyChangesListeners) {
+ endApplyChangesListeners += listener
+ }
}
private fun clearChildInvalidObservations(viewGroup: ViewGroup) {
@@ -1038,7 +1046,7 @@
}
val lifecycleOwner = ViewTreeLifecycleOwner.get(this)
- val savedStateRegistryOwner = ViewTreeSavedStateRegistryOwner.get(this)
+ val savedStateRegistryOwner = findViewTreeSavedStateRegistryOwner()
val oldViewTreeOwners = viewTreeOwners
// We need to change the ViewTreeOwner if there isn't one yet (null)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.android.kt
index d528187..dd222dfa 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.android.kt
@@ -16,7 +16,7 @@
package androidx.compose.ui.platform
-import android.app.Application
+import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Looper
@@ -91,12 +91,14 @@
return found
}
-private val animationScale = mutableMapOf<Application, StateFlow<Float>>()
+private val animationScale = mutableMapOf<Context, StateFlow<Float>>()
-private fun getAnimationScaleFlowFor(application: Application): StateFlow<Float> {
+// Callers of this function should pass an application context. Passing an activity context might
+// result in activity leaks.
+private fun getAnimationScaleFlowFor(applicationContext: Context): StateFlow<Float> {
return synchronized(animationScale) {
- animationScale.getOrPut(application) {
- val resolver = application.applicationContext.contentResolver
+ animationScale.getOrPut(applicationContext) {
+ val resolver = applicationContext.contentResolver
val animationScaleUri =
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)
val channel = Channel<Unit>(CONFLATED)
@@ -113,7 +115,7 @@
try {
for (value in channel) {
val newValue = Settings.Global.getFloat(
- application.contentResolver,
+ applicationContext.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1f
)
@@ -126,7 +128,7 @@
MainScope(),
SharingStarted.WhileSubscribed(),
Settings.Global.getFloat(
- application.contentResolver,
+ applicationContext.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1f
)
@@ -376,16 +378,13 @@
var durationScaleJob: Job? = null
try {
durationScaleJob = systemDurationScaleSettingConsumer?.let {
- // We need the following nullability check because the cast will
- // fail in layoutlib. A long-term plan for this workaround is
- // being tracked by b/227155163.
- (context.applicationContext as? Application)?.let { app ->
- val durationScaleStateFlow = getAnimationScaleFlowFor(app)
- it.scaleFactor = durationScaleStateFlow.value
- launch {
- durationScaleStateFlow.collect { scaleFactor ->
- it.scaleFactor = scaleFactor
- }
+ val durationScaleStateFlow = getAnimationScaleFlowFor(
+ context.applicationContext
+ )
+ it.scaleFactor = durationScaleStateFlow.value
+ launch {
+ durationScaleStateFlow.collect { scaleFactor ->
+ it.scaleFactor = scaleFactor
}
}
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
index ac97836..535e5208 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
@@ -47,7 +47,7 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import kotlin.math.roundToInt
/**
@@ -132,12 +132,12 @@
}
}
- /** Sets the [ViewTreeSavedStateRegistryOwner] for this view. */
+ /** Sets the ViewTreeSavedStateRegistryOwner for this view. */
var savedStateRegistryOwner: SavedStateRegistryOwner? = null
set(value) {
if (value !== field) {
field = value
- ViewTreeSavedStateRegistryOwner.set(this, value)
+ setViewTreeSavedStateRegistryOwner(value)
}
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
index 23f948e..8b858b4 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
@@ -57,7 +57,8 @@
import androidx.compose.ui.util.fastMaxBy
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.util.UUID
import kotlin.math.roundToInt
@@ -324,9 +325,8 @@
setContentView(dialogLayout)
ViewTreeLifecycleOwner.set(dialogLayout, ViewTreeLifecycleOwner.get(composeView))
ViewTreeViewModelStoreOwner.set(dialogLayout, ViewTreeViewModelStoreOwner.get(composeView))
- ViewTreeSavedStateRegistryOwner.set(
- dialogLayout,
- ViewTreeSavedStateRegistryOwner.get(composeView)
+ dialogLayout.setViewTreeSavedStateRegistryOwner(
+ composeView.findViewTreeSavedStateRegistryOwner()
)
// Initial setup
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index a7f86df..e285cf1 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -75,7 +75,8 @@
import androidx.compose.ui.util.fastMap
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import kotlinx.coroutines.android.awaitFrame
import kotlinx.coroutines.isActive
import org.jetbrains.annotations.TestOnly
@@ -420,7 +421,7 @@
id = android.R.id.content
ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
- ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
+ setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner())
// Set unique id for AbstractComposeView. This allows state restoration for the state
// defined inside the Popup via rememberSaveable()
setTag(R.id.compose_view_saveable_id_tag, "Popup:$popupId")
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
index 6daab6f..875cc70 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
@@ -101,6 +101,9 @@
noLocalProvidedFor("LocalFontLoader")
}
+/**
+ * The CompositionLocal for compose font resolution from FontFamily.
+ */
val LocalFontFamilyResolver = staticCompositionLocalOf<FontFamily.Resolver> {
noLocalProvidedFor("LocalFontFamilyResolver")
}
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
index 726fe77..fedee8c 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
@@ -190,7 +190,7 @@
private val pointerInputEventProcessor = PointerInputEventProcessor(root)
private val measureAndLayoutDelegate = MeasureAndLayoutDelegate(root)
- private val endApplyChangesListeners = mutableVectorOf<() -> Unit>()
+ private val endApplyChangesListeners = mutableVectorOf<(() -> Unit)?>()
init {
snapshotObserver.startObserving()
@@ -436,18 +436,26 @@
}
override fun onEndApplyChanges() {
- // Iterate through the whole list, even if listeners are added.
- var i = 0
- while (i < endApplyChangesListeners.size) {
- val listener = endApplyChangesListeners[i]
- listener()
- i++
+ // Listeners can add more items to the list and we want to ensure that they
+ // are executed after being added, so loop until the list is empty
+ while (endApplyChangesListeners.isNotEmpty()) {
+ val size = endApplyChangesListeners.size
+ for (i in 0 until size) {
+ val listener = endApplyChangesListeners[i]
+ // null out the item so that if the listener is re-added then we execute it again.
+ endApplyChangesListeners[i] = null
+ listener?.invoke()
+ }
+ // Remove all the items that were visited. Removing items shifts all items after
+ // to the front of the list, so removing in a chunk is cheaper than removing one-by-one
+ endApplyChangesListeners.removeRange(0, size)
}
- endApplyChangesListeners.clear()
}
override fun registerOnEndApplyChangesListener(listener: () -> Unit) {
- endApplyChangesListeners += listener
+ if (listener !in endApplyChangesListeners) {
+ endApplyChangesListeners += listener
+ }
}
override fun registerOnLayoutCompletedListener(listener: Owner.OnLayoutCompletedListener) {
diff --git a/core/core-animation-testing/api/1.0.0-beta01.txt b/core/core-animation-testing/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..348efb2
--- /dev/null
+++ b/core/core-animation-testing/api/1.0.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public final class AnimatorTestRule implements org.junit.rules.TestRule {
+ ctor public AnimatorTestRule();
+ method public void advanceTimeBy(long);
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description);
+ method public long getCurrentTime();
+ }
+
+}
+
diff --git a/core/core-animation-testing/api/public_plus_experimental_1.0.0-beta01.txt b/core/core-animation-testing/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..348efb2
--- /dev/null
+++ b/core/core-animation-testing/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public final class AnimatorTestRule implements org.junit.rules.TestRule {
+ ctor public AnimatorTestRule();
+ method public void advanceTimeBy(long);
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description);
+ method public long getCurrentTime();
+ }
+
+}
+
diff --git a/core/core-animation-testing/api/res-1.0.0-beta01.txt b/core/core-animation-testing/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-animation-testing/api/res-1.0.0-beta01.txt
diff --git a/core/core-animation-testing/api/restricted_1.0.0-beta01.txt b/core/core-animation-testing/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..348efb2
--- /dev/null
+++ b/core/core-animation-testing/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public final class AnimatorTestRule implements org.junit.rules.TestRule {
+ ctor public AnimatorTestRule();
+ method public void advanceTimeBy(long);
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description);
+ method public long getCurrentTime();
+ }
+
+}
+
diff --git a/core/core-animation/api/1.0.0-beta01.txt b/core/core-animation/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..3477c13
--- /dev/null
+++ b/core/core-animation/api/1.0.0-beta01.txt
@@ -0,0 +1,372 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public class AccelerateDecelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AccelerateDecelerateInterpolator();
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class AccelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AccelerateInterpolator();
+ ctor public AccelerateInterpolator(float);
+ ctor public AccelerateInterpolator(android.content.Context, android.util.AttributeSet);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public abstract class Animator implements java.lang.Cloneable {
+ ctor public Animator();
+ method public void addListener(androidx.core.animation.Animator.AnimatorListener);
+ method public void addPauseListener(androidx.core.animation.Animator.AnimatorPauseListener);
+ method public void addUpdateListener(androidx.core.animation.Animator.AnimatorUpdateListener);
+ method public void cancel();
+ method public androidx.core.animation.Animator clone();
+ method public void end();
+ method public abstract long getDuration();
+ method public androidx.core.animation.Interpolator? getInterpolator();
+ method public abstract long getStartDelay();
+ method public long getTotalDuration();
+ method public boolean isPaused();
+ method public abstract boolean isRunning();
+ method public boolean isStarted();
+ method public void pause();
+ method public void removeAllListeners();
+ method public void removeAllUpdateListeners();
+ method public void removeListener(androidx.core.animation.Animator.AnimatorListener);
+ method public void removePauseListener(androidx.core.animation.Animator.AnimatorPauseListener);
+ method public void removeUpdateListener(androidx.core.animation.Animator.AnimatorUpdateListener);
+ method public void resume();
+ method public abstract androidx.core.animation.Animator setDuration(@IntRange(from=0) long);
+ method public abstract void setInterpolator(androidx.core.animation.Interpolator?);
+ method public abstract void setStartDelay(@IntRange(from=0) long);
+ method public void setTarget(Object?);
+ method public void setupEndValues();
+ method public void setupStartValues();
+ method public void start();
+ field public static final long DURATION_INFINITE = -1L; // 0xffffffffffffffffL
+ }
+
+ public static interface Animator.AnimatorListener {
+ method public void onAnimationCancel(androidx.core.animation.Animator);
+ method public default void onAnimationEnd(androidx.core.animation.Animator, boolean);
+ method public void onAnimationEnd(androidx.core.animation.Animator);
+ method public void onAnimationRepeat(androidx.core.animation.Animator);
+ method public default void onAnimationStart(androidx.core.animation.Animator, boolean);
+ method public void onAnimationStart(androidx.core.animation.Animator);
+ }
+
+ public static interface Animator.AnimatorPauseListener {
+ method public void onAnimationPause(androidx.core.animation.Animator);
+ method public void onAnimationResume(androidx.core.animation.Animator);
+ }
+
+ public static interface Animator.AnimatorUpdateListener {
+ method public void onAnimationUpdate(androidx.core.animation.Animator);
+ }
+
+ public class AnimatorInflater {
+ method public static androidx.core.animation.Animator loadAnimator(android.content.Context, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static androidx.core.animation.Animator loadAnimator(android.content.res.Resources, android.content.res.Resources.Theme?, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static androidx.core.animation.Interpolator loadInterpolator(android.content.Context, @AnimatorRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
+ }
+
+ public abstract class AnimatorListenerAdapter implements androidx.core.animation.Animator.AnimatorListener androidx.core.animation.Animator.AnimatorPauseListener {
+ ctor public AnimatorListenerAdapter();
+ method public void onAnimationCancel(androidx.core.animation.Animator);
+ method public void onAnimationEnd(androidx.core.animation.Animator);
+ method public void onAnimationPause(androidx.core.animation.Animator);
+ method public void onAnimationRepeat(androidx.core.animation.Animator);
+ method public void onAnimationResume(androidx.core.animation.Animator);
+ method public void onAnimationStart(androidx.core.animation.Animator);
+ }
+
+ public final class AnimatorSet extends androidx.core.animation.Animator {
+ ctor public AnimatorSet();
+ method public boolean canReverse();
+ method public androidx.core.animation.AnimatorSet clone();
+ method public java.util.ArrayList<androidx.core.animation.Animator!> getChildAnimations();
+ method public long getCurrentPlayTime();
+ method public long getDuration();
+ method public long getStartDelay();
+ method public boolean isRunning();
+ method public androidx.core.animation.AnimatorSet.Builder play(androidx.core.animation.Animator);
+ method public void playSequentially(androidx.core.animation.Animator!...);
+ method public void playSequentially(java.util.List<androidx.core.animation.Animator!>);
+ method public void playTogether(androidx.core.animation.Animator!...);
+ method public void playTogether(java.util.Collection<androidx.core.animation.Animator!>);
+ method public void reverse();
+ method public void setCurrentPlayTime(long);
+ method public androidx.core.animation.AnimatorSet setDuration(long);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public void setStartDelay(long);
+ }
+
+ public class AnimatorSet.Builder {
+ method public androidx.core.animation.AnimatorSet.Builder after(androidx.core.animation.Animator);
+ method public androidx.core.animation.AnimatorSet.Builder after(long);
+ method public androidx.core.animation.AnimatorSet.Builder before(androidx.core.animation.Animator);
+ method public androidx.core.animation.AnimatorSet.Builder with(androidx.core.animation.Animator);
+ }
+
+ public class AnticipateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AnticipateInterpolator();
+ ctor public AnticipateInterpolator(float);
+ ctor public AnticipateInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class AnticipateOvershootInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AnticipateOvershootInterpolator();
+ ctor public AnticipateOvershootInterpolator(float);
+ ctor public AnticipateOvershootInterpolator(float, float);
+ ctor public AnticipateOvershootInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+ method public Integer evaluate(float, Integer, Integer);
+ method public static androidx.core.animation.ArgbEvaluator getInstance();
+ }
+
+ public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T,V> {
+ ctor public BidirectionalTypeConverter(Class<T!>, Class<V!>);
+ method public abstract T convertBack(V);
+ method public androidx.core.animation.BidirectionalTypeConverter<V!,T!> invert();
+ }
+
+ public class BounceInterpolator implements androidx.core.animation.Interpolator {
+ ctor public BounceInterpolator();
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class CycleInterpolator implements androidx.core.animation.Interpolator {
+ ctor public CycleInterpolator(float);
+ ctor public CycleInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class DecelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public DecelerateInterpolator();
+ ctor public DecelerateInterpolator(float);
+ ctor public DecelerateInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]> {
+ ctor public FloatArrayEvaluator();
+ ctor public FloatArrayEvaluator(float[]?);
+ method public float[] evaluate(float, float[], float[]);
+ }
+
+ public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float> {
+ method public Float evaluate(float, Float, Float);
+ method public static androidx.core.animation.FloatEvaluator getInstance();
+ }
+
+ public abstract class FloatProperty<T> extends android.util.Property<T,java.lang.Float> {
+ ctor public FloatProperty(String);
+ ctor public FloatProperty();
+ method public final void set(T, Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]> {
+ ctor public IntArrayEvaluator();
+ ctor public IntArrayEvaluator(int[]?);
+ method public int[] evaluate(float, int[], int[]);
+ }
+
+ public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+ method public Integer evaluate(float, Integer, Integer);
+ method public static androidx.core.animation.IntEvaluator getInstance();
+ }
+
+ public abstract class IntProperty<T> extends android.util.Property<T,java.lang.Integer> {
+ ctor public IntProperty(String);
+ ctor public IntProperty();
+ method public final void set(T, Integer);
+ method public abstract void setValue(T, int);
+ }
+
+ public interface Interpolator {
+ method public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public abstract class Keyframe<T> implements java.lang.Cloneable {
+ ctor public Keyframe();
+ method public abstract androidx.core.animation.Keyframe<T!> clone();
+ method @FloatRange(from=0, to=1) public float getFraction();
+ method public androidx.core.animation.Interpolator? getInterpolator();
+ method public Class<?> getType();
+ method public abstract T? getValue();
+ method public boolean hasValue();
+ method public static androidx.core.animation.Keyframe<java.lang.Float!> ofFloat(@FloatRange(from=0, to=1) float, float);
+ method public static androidx.core.animation.Keyframe<java.lang.Float!> ofFloat(@FloatRange(from=0, to=1) float);
+ method public static androidx.core.animation.Keyframe<java.lang.Integer!> ofInt(@FloatRange(from=0, to=1) float, int);
+ method public static androidx.core.animation.Keyframe<java.lang.Integer!> ofInt(@FloatRange(from=0, to=1) float);
+ method public static <T> androidx.core.animation.Keyframe<T!> ofObject(@FloatRange(from=0, to=1) float, T?);
+ method public static <T> androidx.core.animation.Keyframe<T!> ofObject(@FloatRange(from=0, to=1) float);
+ method public void setFraction(@FloatRange(from=0, to=1) float);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public abstract void setValue(T?);
+ }
+
+ public class LinearInterpolator implements androidx.core.animation.Interpolator {
+ ctor public LinearInterpolator();
+ ctor public LinearInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class ObjectAnimator extends androidx.core.animation.ValueAnimator {
+ ctor public ObjectAnimator();
+ method public androidx.core.animation.ObjectAnimator clone();
+ method public String getPropertyName();
+ method public Object? getTarget();
+ method public static androidx.core.animation.ObjectAnimator ofArgb(Object, String, int...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofArgb(T, android.util.Property<T!,java.lang.Integer!>, int...);
+ method public static androidx.core.animation.ObjectAnimator ofFloat(Object, String, float...);
+ method public static androidx.core.animation.ObjectAnimator ofFloat(Object, String?, String?, android.graphics.Path);
+ method public static <T> androidx.core.animation.ObjectAnimator ofFloat(T, android.util.Property<T!,java.lang.Float!>, float...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofFloat(T, android.util.Property<T!,java.lang.Float!>?, android.util.Property<T!,java.lang.Float!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofInt(Object, String, int...);
+ method public static androidx.core.animation.ObjectAnimator ofInt(Object, String, String, android.graphics.Path);
+ method public static <T> androidx.core.animation.ObjectAnimator ofInt(T, android.util.Property<T!,java.lang.Integer!>, int...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofInt(T, android.util.Property<T!,java.lang.Integer!>?, android.util.Property<T!,java.lang.Integer!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, float[]![]);
+ method public static androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, androidx.core.animation.TypeConverter<T!,float[]!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, int[]![]);
+ method public static androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, androidx.core.animation.TypeConverter<T!,int[]!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static androidx.core.animation.ObjectAnimator ofObject(Object, String, androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.ObjectAnimator ofObject(Object, String, androidx.core.animation.TypeConverter<android.graphics.PointF!,?>?, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T, V> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,V!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T, V, P> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,P!>, androidx.core.animation.TypeConverter<V!,P!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method public static <T, V> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,V!>, androidx.core.animation.TypeConverter<android.graphics.PointF!,V!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofPropertyValuesHolder(Object, androidx.core.animation.PropertyValuesHolder!...);
+ method public void setAutoCancel(boolean);
+ method public androidx.core.animation.ObjectAnimator setDuration(long);
+ method public void setProperty(android.util.Property);
+ method public void setPropertyName(String);
+ }
+
+ public class OvershootInterpolator implements androidx.core.animation.Interpolator {
+ ctor public OvershootInterpolator();
+ ctor public OvershootInterpolator(float);
+ ctor public OvershootInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class PathInterpolator implements androidx.core.animation.Interpolator {
+ ctor public PathInterpolator(android.graphics.Path);
+ ctor public PathInterpolator(float, float);
+ ctor public PathInterpolator(float, float, float, float);
+ ctor public PathInterpolator(android.content.Context, android.util.AttributeSet?, org.xmlpull.v1.XmlPullParser);
+ ctor public PathInterpolator(android.content.res.Resources, android.content.res.Resources.Theme?, android.util.AttributeSet?, org.xmlpull.v1.XmlPullParser);
+ method public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF> {
+ ctor public PointFEvaluator();
+ ctor public PointFEvaluator(android.graphics.PointF);
+ method public android.graphics.PointF evaluate(float, android.graphics.PointF, android.graphics.PointF);
+ }
+
+ public class PropertyValuesHolder implements java.lang.Cloneable {
+ method public androidx.core.animation.PropertyValuesHolder clone();
+ method public String getPropertyName();
+ method public static androidx.core.animation.PropertyValuesHolder ofFloat(String, float...);
+ method public static androidx.core.animation.PropertyValuesHolder ofFloat(android.util.Property<?,java.lang.Float!>, float...);
+ method public static androidx.core.animation.PropertyValuesHolder ofInt(String, int...);
+ method public static androidx.core.animation.PropertyValuesHolder ofInt(android.util.Property<?,java.lang.Integer!>, int...);
+ method @java.lang.SafeVarargs public static androidx.core.animation.PropertyValuesHolder ofKeyframe(String, androidx.core.animation.Keyframe!...);
+ method @java.lang.SafeVarargs public static androidx.core.animation.PropertyValuesHolder ofKeyframe(android.util.Property, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, float[]![]);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, androidx.core.animation.TypeConverter<V!,float[]!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, androidx.core.animation.TypeConverter<T!,float[]!>?, androidx.core.animation.TypeEvaluator<T!>, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiInt(String, int[]![]);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiInt(String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofMultiInt(String, androidx.core.animation.TypeConverter<V!,int[]!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.PropertyValuesHolder ofMultiInt(String, androidx.core.animation.TypeConverter<T!,int[]!>?, androidx.core.animation.TypeEvaluator<T!>, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofObject(String, androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofObject(String, androidx.core.animation.TypeConverter<android.graphics.PointF!,?>?, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T, V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property<?,V!>, androidx.core.animation.TypeConverter<T!,V!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static <V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property<?,V!>, androidx.core.animation.TypeConverter<android.graphics.PointF!,V!>?, android.graphics.Path);
+ method public void setConverter(androidx.core.animation.TypeConverter?);
+ method public void setEvaluator(androidx.core.animation.TypeEvaluator);
+ method public void setFloatValues(float...);
+ method public void setIntValues(int...);
+ method public void setKeyframes(androidx.core.animation.Keyframe!...);
+ method public void setObjectValues(java.lang.Object!...);
+ method public void setProperty(android.util.Property);
+ method public void setPropertyName(String);
+ }
+
+ public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect> {
+ ctor public RectEvaluator();
+ ctor public RectEvaluator(android.graphics.Rect);
+ method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
+ }
+
+ public class TimeAnimator extends androidx.core.animation.ValueAnimator {
+ ctor public TimeAnimator();
+ method public void setTimeListener(androidx.core.animation.TimeAnimator.TimeListener?);
+ }
+
+ public static interface TimeAnimator.TimeListener {
+ method public void onTimeUpdate(androidx.core.animation.TimeAnimator, long, long);
+ }
+
+ public abstract class TypeConverter<T, V> {
+ ctor public TypeConverter(Class<T!>, Class<V!>);
+ method public abstract V convert(T);
+ }
+
+ public interface TypeEvaluator<T> {
+ method public T evaluate(float, T, T);
+ }
+
+ public class ValueAnimator extends androidx.core.animation.Animator {
+ ctor public ValueAnimator();
+ method public static boolean areAnimatorsEnabled();
+ method public androidx.core.animation.ValueAnimator clone();
+ method public float getAnimatedFraction();
+ method public Object getAnimatedValue();
+ method public Object? getAnimatedValue(String);
+ method public long getCurrentPlayTime();
+ method public long getDuration();
+ method public static long getFrameDelay();
+ method public String getNameForTrace();
+ method public int getRepeatCount();
+ method public int getRepeatMode();
+ method public long getStartDelay();
+ method public androidx.core.animation.PropertyValuesHolder![] getValues();
+ method public boolean isRunning();
+ method public static androidx.core.animation.ValueAnimator ofArgb(int...);
+ method public static androidx.core.animation.ValueAnimator ofFloat(float...);
+ method public static androidx.core.animation.ValueAnimator ofInt(int...);
+ method public static androidx.core.animation.ValueAnimator ofObject(androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.ValueAnimator ofPropertyValuesHolder(androidx.core.animation.PropertyValuesHolder!...);
+ method public void reverse();
+ method public void setCurrentFraction(float);
+ method public void setCurrentPlayTime(long);
+ method public androidx.core.animation.ValueAnimator setDuration(long);
+ method public void setEvaluator(androidx.core.animation.TypeEvaluator);
+ method public void setFloatValues(float...);
+ method public static void setFrameDelay(long);
+ method public void setIntValues(int...);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public void setNameForTrace(String);
+ method public void setObjectValues(java.lang.Object!...);
+ method public void setRepeatCount(int);
+ method public void setRepeatMode(int);
+ method public void setStartDelay(long);
+ method public void setValues(androidx.core.animation.PropertyValuesHolder!...);
+ field public static final int INFINITE = -1; // 0xffffffff
+ field public static final int RESTART = 1; // 0x1
+ field public static final int REVERSE = 2; // 0x2
+ }
+
+}
+
diff --git a/core/core-animation/api/public_plus_experimental_1.0.0-beta01.txt b/core/core-animation/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..3477c13
--- /dev/null
+++ b/core/core-animation/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1,372 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public class AccelerateDecelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AccelerateDecelerateInterpolator();
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class AccelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AccelerateInterpolator();
+ ctor public AccelerateInterpolator(float);
+ ctor public AccelerateInterpolator(android.content.Context, android.util.AttributeSet);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public abstract class Animator implements java.lang.Cloneable {
+ ctor public Animator();
+ method public void addListener(androidx.core.animation.Animator.AnimatorListener);
+ method public void addPauseListener(androidx.core.animation.Animator.AnimatorPauseListener);
+ method public void addUpdateListener(androidx.core.animation.Animator.AnimatorUpdateListener);
+ method public void cancel();
+ method public androidx.core.animation.Animator clone();
+ method public void end();
+ method public abstract long getDuration();
+ method public androidx.core.animation.Interpolator? getInterpolator();
+ method public abstract long getStartDelay();
+ method public long getTotalDuration();
+ method public boolean isPaused();
+ method public abstract boolean isRunning();
+ method public boolean isStarted();
+ method public void pause();
+ method public void removeAllListeners();
+ method public void removeAllUpdateListeners();
+ method public void removeListener(androidx.core.animation.Animator.AnimatorListener);
+ method public void removePauseListener(androidx.core.animation.Animator.AnimatorPauseListener);
+ method public void removeUpdateListener(androidx.core.animation.Animator.AnimatorUpdateListener);
+ method public void resume();
+ method public abstract androidx.core.animation.Animator setDuration(@IntRange(from=0) long);
+ method public abstract void setInterpolator(androidx.core.animation.Interpolator?);
+ method public abstract void setStartDelay(@IntRange(from=0) long);
+ method public void setTarget(Object?);
+ method public void setupEndValues();
+ method public void setupStartValues();
+ method public void start();
+ field public static final long DURATION_INFINITE = -1L; // 0xffffffffffffffffL
+ }
+
+ public static interface Animator.AnimatorListener {
+ method public void onAnimationCancel(androidx.core.animation.Animator);
+ method public default void onAnimationEnd(androidx.core.animation.Animator, boolean);
+ method public void onAnimationEnd(androidx.core.animation.Animator);
+ method public void onAnimationRepeat(androidx.core.animation.Animator);
+ method public default void onAnimationStart(androidx.core.animation.Animator, boolean);
+ method public void onAnimationStart(androidx.core.animation.Animator);
+ }
+
+ public static interface Animator.AnimatorPauseListener {
+ method public void onAnimationPause(androidx.core.animation.Animator);
+ method public void onAnimationResume(androidx.core.animation.Animator);
+ }
+
+ public static interface Animator.AnimatorUpdateListener {
+ method public void onAnimationUpdate(androidx.core.animation.Animator);
+ }
+
+ public class AnimatorInflater {
+ method public static androidx.core.animation.Animator loadAnimator(android.content.Context, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static androidx.core.animation.Animator loadAnimator(android.content.res.Resources, android.content.res.Resources.Theme?, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static androidx.core.animation.Interpolator loadInterpolator(android.content.Context, @AnimatorRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
+ }
+
+ public abstract class AnimatorListenerAdapter implements androidx.core.animation.Animator.AnimatorListener androidx.core.animation.Animator.AnimatorPauseListener {
+ ctor public AnimatorListenerAdapter();
+ method public void onAnimationCancel(androidx.core.animation.Animator);
+ method public void onAnimationEnd(androidx.core.animation.Animator);
+ method public void onAnimationPause(androidx.core.animation.Animator);
+ method public void onAnimationRepeat(androidx.core.animation.Animator);
+ method public void onAnimationResume(androidx.core.animation.Animator);
+ method public void onAnimationStart(androidx.core.animation.Animator);
+ }
+
+ public final class AnimatorSet extends androidx.core.animation.Animator {
+ ctor public AnimatorSet();
+ method public boolean canReverse();
+ method public androidx.core.animation.AnimatorSet clone();
+ method public java.util.ArrayList<androidx.core.animation.Animator!> getChildAnimations();
+ method public long getCurrentPlayTime();
+ method public long getDuration();
+ method public long getStartDelay();
+ method public boolean isRunning();
+ method public androidx.core.animation.AnimatorSet.Builder play(androidx.core.animation.Animator);
+ method public void playSequentially(androidx.core.animation.Animator!...);
+ method public void playSequentially(java.util.List<androidx.core.animation.Animator!>);
+ method public void playTogether(androidx.core.animation.Animator!...);
+ method public void playTogether(java.util.Collection<androidx.core.animation.Animator!>);
+ method public void reverse();
+ method public void setCurrentPlayTime(long);
+ method public androidx.core.animation.AnimatorSet setDuration(long);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public void setStartDelay(long);
+ }
+
+ public class AnimatorSet.Builder {
+ method public androidx.core.animation.AnimatorSet.Builder after(androidx.core.animation.Animator);
+ method public androidx.core.animation.AnimatorSet.Builder after(long);
+ method public androidx.core.animation.AnimatorSet.Builder before(androidx.core.animation.Animator);
+ method public androidx.core.animation.AnimatorSet.Builder with(androidx.core.animation.Animator);
+ }
+
+ public class AnticipateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AnticipateInterpolator();
+ ctor public AnticipateInterpolator(float);
+ ctor public AnticipateInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class AnticipateOvershootInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AnticipateOvershootInterpolator();
+ ctor public AnticipateOvershootInterpolator(float);
+ ctor public AnticipateOvershootInterpolator(float, float);
+ ctor public AnticipateOvershootInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+ method public Integer evaluate(float, Integer, Integer);
+ method public static androidx.core.animation.ArgbEvaluator getInstance();
+ }
+
+ public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T,V> {
+ ctor public BidirectionalTypeConverter(Class<T!>, Class<V!>);
+ method public abstract T convertBack(V);
+ method public androidx.core.animation.BidirectionalTypeConverter<V!,T!> invert();
+ }
+
+ public class BounceInterpolator implements androidx.core.animation.Interpolator {
+ ctor public BounceInterpolator();
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class CycleInterpolator implements androidx.core.animation.Interpolator {
+ ctor public CycleInterpolator(float);
+ ctor public CycleInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class DecelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public DecelerateInterpolator();
+ ctor public DecelerateInterpolator(float);
+ ctor public DecelerateInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]> {
+ ctor public FloatArrayEvaluator();
+ ctor public FloatArrayEvaluator(float[]?);
+ method public float[] evaluate(float, float[], float[]);
+ }
+
+ public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float> {
+ method public Float evaluate(float, Float, Float);
+ method public static androidx.core.animation.FloatEvaluator getInstance();
+ }
+
+ public abstract class FloatProperty<T> extends android.util.Property<T,java.lang.Float> {
+ ctor public FloatProperty(String);
+ ctor public FloatProperty();
+ method public final void set(T, Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]> {
+ ctor public IntArrayEvaluator();
+ ctor public IntArrayEvaluator(int[]?);
+ method public int[] evaluate(float, int[], int[]);
+ }
+
+ public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+ method public Integer evaluate(float, Integer, Integer);
+ method public static androidx.core.animation.IntEvaluator getInstance();
+ }
+
+ public abstract class IntProperty<T> extends android.util.Property<T,java.lang.Integer> {
+ ctor public IntProperty(String);
+ ctor public IntProperty();
+ method public final void set(T, Integer);
+ method public abstract void setValue(T, int);
+ }
+
+ public interface Interpolator {
+ method public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public abstract class Keyframe<T> implements java.lang.Cloneable {
+ ctor public Keyframe();
+ method public abstract androidx.core.animation.Keyframe<T!> clone();
+ method @FloatRange(from=0, to=1) public float getFraction();
+ method public androidx.core.animation.Interpolator? getInterpolator();
+ method public Class<?> getType();
+ method public abstract T? getValue();
+ method public boolean hasValue();
+ method public static androidx.core.animation.Keyframe<java.lang.Float!> ofFloat(@FloatRange(from=0, to=1) float, float);
+ method public static androidx.core.animation.Keyframe<java.lang.Float!> ofFloat(@FloatRange(from=0, to=1) float);
+ method public static androidx.core.animation.Keyframe<java.lang.Integer!> ofInt(@FloatRange(from=0, to=1) float, int);
+ method public static androidx.core.animation.Keyframe<java.lang.Integer!> ofInt(@FloatRange(from=0, to=1) float);
+ method public static <T> androidx.core.animation.Keyframe<T!> ofObject(@FloatRange(from=0, to=1) float, T?);
+ method public static <T> androidx.core.animation.Keyframe<T!> ofObject(@FloatRange(from=0, to=1) float);
+ method public void setFraction(@FloatRange(from=0, to=1) float);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public abstract void setValue(T?);
+ }
+
+ public class LinearInterpolator implements androidx.core.animation.Interpolator {
+ ctor public LinearInterpolator();
+ ctor public LinearInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class ObjectAnimator extends androidx.core.animation.ValueAnimator {
+ ctor public ObjectAnimator();
+ method public androidx.core.animation.ObjectAnimator clone();
+ method public String getPropertyName();
+ method public Object? getTarget();
+ method public static androidx.core.animation.ObjectAnimator ofArgb(Object, String, int...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofArgb(T, android.util.Property<T!,java.lang.Integer!>, int...);
+ method public static androidx.core.animation.ObjectAnimator ofFloat(Object, String, float...);
+ method public static androidx.core.animation.ObjectAnimator ofFloat(Object, String?, String?, android.graphics.Path);
+ method public static <T> androidx.core.animation.ObjectAnimator ofFloat(T, android.util.Property<T!,java.lang.Float!>, float...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofFloat(T, android.util.Property<T!,java.lang.Float!>?, android.util.Property<T!,java.lang.Float!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofInt(Object, String, int...);
+ method public static androidx.core.animation.ObjectAnimator ofInt(Object, String, String, android.graphics.Path);
+ method public static <T> androidx.core.animation.ObjectAnimator ofInt(T, android.util.Property<T!,java.lang.Integer!>, int...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofInt(T, android.util.Property<T!,java.lang.Integer!>?, android.util.Property<T!,java.lang.Integer!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, float[]![]);
+ method public static androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, androidx.core.animation.TypeConverter<T!,float[]!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, int[]![]);
+ method public static androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, androidx.core.animation.TypeConverter<T!,int[]!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static androidx.core.animation.ObjectAnimator ofObject(Object, String, androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.ObjectAnimator ofObject(Object, String, androidx.core.animation.TypeConverter<android.graphics.PointF!,?>?, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T, V> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,V!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T, V, P> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,P!>, androidx.core.animation.TypeConverter<V!,P!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method public static <T, V> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,V!>, androidx.core.animation.TypeConverter<android.graphics.PointF!,V!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofPropertyValuesHolder(Object, androidx.core.animation.PropertyValuesHolder!...);
+ method public void setAutoCancel(boolean);
+ method public androidx.core.animation.ObjectAnimator setDuration(long);
+ method public void setProperty(android.util.Property);
+ method public void setPropertyName(String);
+ }
+
+ public class OvershootInterpolator implements androidx.core.animation.Interpolator {
+ ctor public OvershootInterpolator();
+ ctor public OvershootInterpolator(float);
+ ctor public OvershootInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class PathInterpolator implements androidx.core.animation.Interpolator {
+ ctor public PathInterpolator(android.graphics.Path);
+ ctor public PathInterpolator(float, float);
+ ctor public PathInterpolator(float, float, float, float);
+ ctor public PathInterpolator(android.content.Context, android.util.AttributeSet?, org.xmlpull.v1.XmlPullParser);
+ ctor public PathInterpolator(android.content.res.Resources, android.content.res.Resources.Theme?, android.util.AttributeSet?, org.xmlpull.v1.XmlPullParser);
+ method public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF> {
+ ctor public PointFEvaluator();
+ ctor public PointFEvaluator(android.graphics.PointF);
+ method public android.graphics.PointF evaluate(float, android.graphics.PointF, android.graphics.PointF);
+ }
+
+ public class PropertyValuesHolder implements java.lang.Cloneable {
+ method public androidx.core.animation.PropertyValuesHolder clone();
+ method public String getPropertyName();
+ method public static androidx.core.animation.PropertyValuesHolder ofFloat(String, float...);
+ method public static androidx.core.animation.PropertyValuesHolder ofFloat(android.util.Property<?,java.lang.Float!>, float...);
+ method public static androidx.core.animation.PropertyValuesHolder ofInt(String, int...);
+ method public static androidx.core.animation.PropertyValuesHolder ofInt(android.util.Property<?,java.lang.Integer!>, int...);
+ method @java.lang.SafeVarargs public static androidx.core.animation.PropertyValuesHolder ofKeyframe(String, androidx.core.animation.Keyframe!...);
+ method @java.lang.SafeVarargs public static androidx.core.animation.PropertyValuesHolder ofKeyframe(android.util.Property, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, float[]![]);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, androidx.core.animation.TypeConverter<V!,float[]!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, androidx.core.animation.TypeConverter<T!,float[]!>?, androidx.core.animation.TypeEvaluator<T!>, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiInt(String, int[]![]);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiInt(String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofMultiInt(String, androidx.core.animation.TypeConverter<V!,int[]!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.PropertyValuesHolder ofMultiInt(String, androidx.core.animation.TypeConverter<T!,int[]!>?, androidx.core.animation.TypeEvaluator<T!>, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofObject(String, androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofObject(String, androidx.core.animation.TypeConverter<android.graphics.PointF!,?>?, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T, V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property<?,V!>, androidx.core.animation.TypeConverter<T!,V!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static <V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property<?,V!>, androidx.core.animation.TypeConverter<android.graphics.PointF!,V!>?, android.graphics.Path);
+ method public void setConverter(androidx.core.animation.TypeConverter?);
+ method public void setEvaluator(androidx.core.animation.TypeEvaluator);
+ method public void setFloatValues(float...);
+ method public void setIntValues(int...);
+ method public void setKeyframes(androidx.core.animation.Keyframe!...);
+ method public void setObjectValues(java.lang.Object!...);
+ method public void setProperty(android.util.Property);
+ method public void setPropertyName(String);
+ }
+
+ public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect> {
+ ctor public RectEvaluator();
+ ctor public RectEvaluator(android.graphics.Rect);
+ method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
+ }
+
+ public class TimeAnimator extends androidx.core.animation.ValueAnimator {
+ ctor public TimeAnimator();
+ method public void setTimeListener(androidx.core.animation.TimeAnimator.TimeListener?);
+ }
+
+ public static interface TimeAnimator.TimeListener {
+ method public void onTimeUpdate(androidx.core.animation.TimeAnimator, long, long);
+ }
+
+ public abstract class TypeConverter<T, V> {
+ ctor public TypeConverter(Class<T!>, Class<V!>);
+ method public abstract V convert(T);
+ }
+
+ public interface TypeEvaluator<T> {
+ method public T evaluate(float, T, T);
+ }
+
+ public class ValueAnimator extends androidx.core.animation.Animator {
+ ctor public ValueAnimator();
+ method public static boolean areAnimatorsEnabled();
+ method public androidx.core.animation.ValueAnimator clone();
+ method public float getAnimatedFraction();
+ method public Object getAnimatedValue();
+ method public Object? getAnimatedValue(String);
+ method public long getCurrentPlayTime();
+ method public long getDuration();
+ method public static long getFrameDelay();
+ method public String getNameForTrace();
+ method public int getRepeatCount();
+ method public int getRepeatMode();
+ method public long getStartDelay();
+ method public androidx.core.animation.PropertyValuesHolder![] getValues();
+ method public boolean isRunning();
+ method public static androidx.core.animation.ValueAnimator ofArgb(int...);
+ method public static androidx.core.animation.ValueAnimator ofFloat(float...);
+ method public static androidx.core.animation.ValueAnimator ofInt(int...);
+ method public static androidx.core.animation.ValueAnimator ofObject(androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.ValueAnimator ofPropertyValuesHolder(androidx.core.animation.PropertyValuesHolder!...);
+ method public void reverse();
+ method public void setCurrentFraction(float);
+ method public void setCurrentPlayTime(long);
+ method public androidx.core.animation.ValueAnimator setDuration(long);
+ method public void setEvaluator(androidx.core.animation.TypeEvaluator);
+ method public void setFloatValues(float...);
+ method public static void setFrameDelay(long);
+ method public void setIntValues(int...);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public void setNameForTrace(String);
+ method public void setObjectValues(java.lang.Object!...);
+ method public void setRepeatCount(int);
+ method public void setRepeatMode(int);
+ method public void setStartDelay(long);
+ method public void setValues(androidx.core.animation.PropertyValuesHolder!...);
+ field public static final int INFINITE = -1; // 0xffffffff
+ field public static final int RESTART = 1; // 0x1
+ field public static final int REVERSE = 2; // 0x2
+ }
+
+}
+
diff --git a/core/core-animation/api/res-1.0.0-beta01.txt b/core/core-animation/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-animation/api/res-1.0.0-beta01.txt
diff --git a/core/core-animation/api/restricted_1.0.0-beta01.txt b/core/core-animation/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..3477c13
--- /dev/null
+++ b/core/core-animation/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,372 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public class AccelerateDecelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AccelerateDecelerateInterpolator();
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class AccelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AccelerateInterpolator();
+ ctor public AccelerateInterpolator(float);
+ ctor public AccelerateInterpolator(android.content.Context, android.util.AttributeSet);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public abstract class Animator implements java.lang.Cloneable {
+ ctor public Animator();
+ method public void addListener(androidx.core.animation.Animator.AnimatorListener);
+ method public void addPauseListener(androidx.core.animation.Animator.AnimatorPauseListener);
+ method public void addUpdateListener(androidx.core.animation.Animator.AnimatorUpdateListener);
+ method public void cancel();
+ method public androidx.core.animation.Animator clone();
+ method public void end();
+ method public abstract long getDuration();
+ method public androidx.core.animation.Interpolator? getInterpolator();
+ method public abstract long getStartDelay();
+ method public long getTotalDuration();
+ method public boolean isPaused();
+ method public abstract boolean isRunning();
+ method public boolean isStarted();
+ method public void pause();
+ method public void removeAllListeners();
+ method public void removeAllUpdateListeners();
+ method public void removeListener(androidx.core.animation.Animator.AnimatorListener);
+ method public void removePauseListener(androidx.core.animation.Animator.AnimatorPauseListener);
+ method public void removeUpdateListener(androidx.core.animation.Animator.AnimatorUpdateListener);
+ method public void resume();
+ method public abstract androidx.core.animation.Animator setDuration(@IntRange(from=0) long);
+ method public abstract void setInterpolator(androidx.core.animation.Interpolator?);
+ method public abstract void setStartDelay(@IntRange(from=0) long);
+ method public void setTarget(Object?);
+ method public void setupEndValues();
+ method public void setupStartValues();
+ method public void start();
+ field public static final long DURATION_INFINITE = -1L; // 0xffffffffffffffffL
+ }
+
+ public static interface Animator.AnimatorListener {
+ method public void onAnimationCancel(androidx.core.animation.Animator);
+ method public default void onAnimationEnd(androidx.core.animation.Animator, boolean);
+ method public void onAnimationEnd(androidx.core.animation.Animator);
+ method public void onAnimationRepeat(androidx.core.animation.Animator);
+ method public default void onAnimationStart(androidx.core.animation.Animator, boolean);
+ method public void onAnimationStart(androidx.core.animation.Animator);
+ }
+
+ public static interface Animator.AnimatorPauseListener {
+ method public void onAnimationPause(androidx.core.animation.Animator);
+ method public void onAnimationResume(androidx.core.animation.Animator);
+ }
+
+ public static interface Animator.AnimatorUpdateListener {
+ method public void onAnimationUpdate(androidx.core.animation.Animator);
+ }
+
+ public class AnimatorInflater {
+ method public static androidx.core.animation.Animator loadAnimator(android.content.Context, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static androidx.core.animation.Animator loadAnimator(android.content.res.Resources, android.content.res.Resources.Theme?, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static androidx.core.animation.Interpolator loadInterpolator(android.content.Context, @AnimatorRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
+ }
+
+ public abstract class AnimatorListenerAdapter implements androidx.core.animation.Animator.AnimatorListener androidx.core.animation.Animator.AnimatorPauseListener {
+ ctor public AnimatorListenerAdapter();
+ method public void onAnimationCancel(androidx.core.animation.Animator);
+ method public void onAnimationEnd(androidx.core.animation.Animator);
+ method public void onAnimationPause(androidx.core.animation.Animator);
+ method public void onAnimationRepeat(androidx.core.animation.Animator);
+ method public void onAnimationResume(androidx.core.animation.Animator);
+ method public void onAnimationStart(androidx.core.animation.Animator);
+ }
+
+ public final class AnimatorSet extends androidx.core.animation.Animator {
+ ctor public AnimatorSet();
+ method public boolean canReverse();
+ method public androidx.core.animation.AnimatorSet clone();
+ method public java.util.ArrayList<androidx.core.animation.Animator!> getChildAnimations();
+ method public long getCurrentPlayTime();
+ method public long getDuration();
+ method public long getStartDelay();
+ method public boolean isRunning();
+ method public androidx.core.animation.AnimatorSet.Builder play(androidx.core.animation.Animator);
+ method public void playSequentially(androidx.core.animation.Animator!...);
+ method public void playSequentially(java.util.List<androidx.core.animation.Animator!>);
+ method public void playTogether(androidx.core.animation.Animator!...);
+ method public void playTogether(java.util.Collection<androidx.core.animation.Animator!>);
+ method public void reverse();
+ method public void setCurrentPlayTime(long);
+ method public androidx.core.animation.AnimatorSet setDuration(long);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public void setStartDelay(long);
+ }
+
+ public class AnimatorSet.Builder {
+ method public androidx.core.animation.AnimatorSet.Builder after(androidx.core.animation.Animator);
+ method public androidx.core.animation.AnimatorSet.Builder after(long);
+ method public androidx.core.animation.AnimatorSet.Builder before(androidx.core.animation.Animator);
+ method public androidx.core.animation.AnimatorSet.Builder with(androidx.core.animation.Animator);
+ }
+
+ public class AnticipateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AnticipateInterpolator();
+ ctor public AnticipateInterpolator(float);
+ ctor public AnticipateInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class AnticipateOvershootInterpolator implements androidx.core.animation.Interpolator {
+ ctor public AnticipateOvershootInterpolator();
+ ctor public AnticipateOvershootInterpolator(float);
+ ctor public AnticipateOvershootInterpolator(float, float);
+ ctor public AnticipateOvershootInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+ method public Integer evaluate(float, Integer, Integer);
+ method public static androidx.core.animation.ArgbEvaluator getInstance();
+ }
+
+ public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T,V> {
+ ctor public BidirectionalTypeConverter(Class<T!>, Class<V!>);
+ method public abstract T convertBack(V);
+ method public androidx.core.animation.BidirectionalTypeConverter<V!,T!> invert();
+ }
+
+ public class BounceInterpolator implements androidx.core.animation.Interpolator {
+ ctor public BounceInterpolator();
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class CycleInterpolator implements androidx.core.animation.Interpolator {
+ ctor public CycleInterpolator(float);
+ ctor public CycleInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class DecelerateInterpolator implements androidx.core.animation.Interpolator {
+ ctor public DecelerateInterpolator();
+ ctor public DecelerateInterpolator(float);
+ ctor public DecelerateInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]> {
+ ctor public FloatArrayEvaluator();
+ ctor public FloatArrayEvaluator(float[]?);
+ method public float[] evaluate(float, float[], float[]);
+ }
+
+ public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float> {
+ method public Float evaluate(float, Float, Float);
+ method public static androidx.core.animation.FloatEvaluator getInstance();
+ }
+
+ public abstract class FloatProperty<T> extends android.util.Property<T,java.lang.Float> {
+ ctor public FloatProperty(String);
+ ctor public FloatProperty();
+ method public final void set(T, Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]> {
+ ctor public IntArrayEvaluator();
+ ctor public IntArrayEvaluator(int[]?);
+ method public int[] evaluate(float, int[], int[]);
+ }
+
+ public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+ method public Integer evaluate(float, Integer, Integer);
+ method public static androidx.core.animation.IntEvaluator getInstance();
+ }
+
+ public abstract class IntProperty<T> extends android.util.Property<T,java.lang.Integer> {
+ ctor public IntProperty(String);
+ ctor public IntProperty();
+ method public final void set(T, Integer);
+ method public abstract void setValue(T, int);
+ }
+
+ public interface Interpolator {
+ method public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public abstract class Keyframe<T> implements java.lang.Cloneable {
+ ctor public Keyframe();
+ method public abstract androidx.core.animation.Keyframe<T!> clone();
+ method @FloatRange(from=0, to=1) public float getFraction();
+ method public androidx.core.animation.Interpolator? getInterpolator();
+ method public Class<?> getType();
+ method public abstract T? getValue();
+ method public boolean hasValue();
+ method public static androidx.core.animation.Keyframe<java.lang.Float!> ofFloat(@FloatRange(from=0, to=1) float, float);
+ method public static androidx.core.animation.Keyframe<java.lang.Float!> ofFloat(@FloatRange(from=0, to=1) float);
+ method public static androidx.core.animation.Keyframe<java.lang.Integer!> ofInt(@FloatRange(from=0, to=1) float, int);
+ method public static androidx.core.animation.Keyframe<java.lang.Integer!> ofInt(@FloatRange(from=0, to=1) float);
+ method public static <T> androidx.core.animation.Keyframe<T!> ofObject(@FloatRange(from=0, to=1) float, T?);
+ method public static <T> androidx.core.animation.Keyframe<T!> ofObject(@FloatRange(from=0, to=1) float);
+ method public void setFraction(@FloatRange(from=0, to=1) float);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public abstract void setValue(T?);
+ }
+
+ public class LinearInterpolator implements androidx.core.animation.Interpolator {
+ ctor public LinearInterpolator();
+ ctor public LinearInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public final class ObjectAnimator extends androidx.core.animation.ValueAnimator {
+ ctor public ObjectAnimator();
+ method public androidx.core.animation.ObjectAnimator clone();
+ method public String getPropertyName();
+ method public Object? getTarget();
+ method public static androidx.core.animation.ObjectAnimator ofArgb(Object, String, int...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofArgb(T, android.util.Property<T!,java.lang.Integer!>, int...);
+ method public static androidx.core.animation.ObjectAnimator ofFloat(Object, String, float...);
+ method public static androidx.core.animation.ObjectAnimator ofFloat(Object, String?, String?, android.graphics.Path);
+ method public static <T> androidx.core.animation.ObjectAnimator ofFloat(T, android.util.Property<T!,java.lang.Float!>, float...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofFloat(T, android.util.Property<T!,java.lang.Float!>?, android.util.Property<T!,java.lang.Float!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofInt(Object, String, int...);
+ method public static androidx.core.animation.ObjectAnimator ofInt(Object, String, String, android.graphics.Path);
+ method public static <T> androidx.core.animation.ObjectAnimator ofInt(T, android.util.Property<T!,java.lang.Integer!>, int...);
+ method public static <T> androidx.core.animation.ObjectAnimator ofInt(T, android.util.Property<T!,java.lang.Integer!>?, android.util.Property<T!,java.lang.Integer!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, float[]![]);
+ method public static androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.ObjectAnimator ofMultiFloat(Object, String, androidx.core.animation.TypeConverter<T!,float[]!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, int[]![]);
+ method public static androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.ObjectAnimator ofMultiInt(Object, String, androidx.core.animation.TypeConverter<T!,int[]!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static androidx.core.animation.ObjectAnimator ofObject(Object, String, androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.ObjectAnimator ofObject(Object, String, androidx.core.animation.TypeConverter<android.graphics.PointF!,?>?, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <T, V> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,V!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T, V, P> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,P!>, androidx.core.animation.TypeConverter<V!,P!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method public static <T, V> androidx.core.animation.ObjectAnimator ofObject(T, android.util.Property<T!,V!>, androidx.core.animation.TypeConverter<android.graphics.PointF!,V!>?, android.graphics.Path);
+ method public static androidx.core.animation.ObjectAnimator ofPropertyValuesHolder(Object, androidx.core.animation.PropertyValuesHolder!...);
+ method public void setAutoCancel(boolean);
+ method public androidx.core.animation.ObjectAnimator setDuration(long);
+ method public void setProperty(android.util.Property);
+ method public void setPropertyName(String);
+ }
+
+ public class OvershootInterpolator implements androidx.core.animation.Interpolator {
+ ctor public OvershootInterpolator();
+ ctor public OvershootInterpolator(float);
+ ctor public OvershootInterpolator(android.content.Context, android.util.AttributeSet?);
+ method @FloatRange(from=0) public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class PathInterpolator implements androidx.core.animation.Interpolator {
+ ctor public PathInterpolator(android.graphics.Path);
+ ctor public PathInterpolator(float, float);
+ ctor public PathInterpolator(float, float, float, float);
+ ctor public PathInterpolator(android.content.Context, android.util.AttributeSet?, org.xmlpull.v1.XmlPullParser);
+ ctor public PathInterpolator(android.content.res.Resources, android.content.res.Resources.Theme?, android.util.AttributeSet?, org.xmlpull.v1.XmlPullParser);
+ method public float getInterpolation(@FloatRange(from=0, to=1) float);
+ }
+
+ public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF> {
+ ctor public PointFEvaluator();
+ ctor public PointFEvaluator(android.graphics.PointF);
+ method public android.graphics.PointF evaluate(float, android.graphics.PointF, android.graphics.PointF);
+ }
+
+ public class PropertyValuesHolder implements java.lang.Cloneable {
+ method public androidx.core.animation.PropertyValuesHolder clone();
+ method public String getPropertyName();
+ method public static androidx.core.animation.PropertyValuesHolder ofFloat(String, float...);
+ method public static androidx.core.animation.PropertyValuesHolder ofFloat(android.util.Property<?,java.lang.Float!>, float...);
+ method public static androidx.core.animation.PropertyValuesHolder ofInt(String, int...);
+ method public static androidx.core.animation.PropertyValuesHolder ofInt(android.util.Property<?,java.lang.Integer!>, int...);
+ method @java.lang.SafeVarargs public static androidx.core.animation.PropertyValuesHolder ofKeyframe(String, androidx.core.animation.Keyframe!...);
+ method @java.lang.SafeVarargs public static androidx.core.animation.PropertyValuesHolder ofKeyframe(android.util.Property, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, float[]![]);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, androidx.core.animation.TypeConverter<V!,float[]!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.PropertyValuesHolder ofMultiFloat(String, androidx.core.animation.TypeConverter<T!,float[]!>?, androidx.core.animation.TypeEvaluator<T!>, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiInt(String, int[]![]);
+ method public static androidx.core.animation.PropertyValuesHolder ofMultiInt(String, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofMultiInt(String, androidx.core.animation.TypeConverter<V!,int[]!>, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T> androidx.core.animation.PropertyValuesHolder ofMultiInt(String, androidx.core.animation.TypeConverter<T!,int[]!>?, androidx.core.animation.TypeEvaluator<T!>, androidx.core.animation.Keyframe!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofObject(String, androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.PropertyValuesHolder ofObject(String, androidx.core.animation.TypeConverter<android.graphics.PointF!,?>?, android.graphics.Path);
+ method @java.lang.SafeVarargs public static <V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property, androidx.core.animation.TypeEvaluator<V!>, V!...);
+ method @java.lang.SafeVarargs public static <T, V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property<?,V!>, androidx.core.animation.TypeConverter<T!,V!>, androidx.core.animation.TypeEvaluator<T!>, T!...);
+ method public static <V> androidx.core.animation.PropertyValuesHolder ofObject(android.util.Property<?,V!>, androidx.core.animation.TypeConverter<android.graphics.PointF!,V!>?, android.graphics.Path);
+ method public void setConverter(androidx.core.animation.TypeConverter?);
+ method public void setEvaluator(androidx.core.animation.TypeEvaluator);
+ method public void setFloatValues(float...);
+ method public void setIntValues(int...);
+ method public void setKeyframes(androidx.core.animation.Keyframe!...);
+ method public void setObjectValues(java.lang.Object!...);
+ method public void setProperty(android.util.Property);
+ method public void setPropertyName(String);
+ }
+
+ public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect> {
+ ctor public RectEvaluator();
+ ctor public RectEvaluator(android.graphics.Rect);
+ method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
+ }
+
+ public class TimeAnimator extends androidx.core.animation.ValueAnimator {
+ ctor public TimeAnimator();
+ method public void setTimeListener(androidx.core.animation.TimeAnimator.TimeListener?);
+ }
+
+ public static interface TimeAnimator.TimeListener {
+ method public void onTimeUpdate(androidx.core.animation.TimeAnimator, long, long);
+ }
+
+ public abstract class TypeConverter<T, V> {
+ ctor public TypeConverter(Class<T!>, Class<V!>);
+ method public abstract V convert(T);
+ }
+
+ public interface TypeEvaluator<T> {
+ method public T evaluate(float, T, T);
+ }
+
+ public class ValueAnimator extends androidx.core.animation.Animator {
+ ctor public ValueAnimator();
+ method public static boolean areAnimatorsEnabled();
+ method public androidx.core.animation.ValueAnimator clone();
+ method public float getAnimatedFraction();
+ method public Object getAnimatedValue();
+ method public Object? getAnimatedValue(String);
+ method public long getCurrentPlayTime();
+ method public long getDuration();
+ method public static long getFrameDelay();
+ method public String getNameForTrace();
+ method public int getRepeatCount();
+ method public int getRepeatMode();
+ method public long getStartDelay();
+ method public androidx.core.animation.PropertyValuesHolder![] getValues();
+ method public boolean isRunning();
+ method public static androidx.core.animation.ValueAnimator ofArgb(int...);
+ method public static androidx.core.animation.ValueAnimator ofFloat(float...);
+ method public static androidx.core.animation.ValueAnimator ofInt(int...);
+ method public static androidx.core.animation.ValueAnimator ofObject(androidx.core.animation.TypeEvaluator, java.lang.Object!...);
+ method public static androidx.core.animation.ValueAnimator ofPropertyValuesHolder(androidx.core.animation.PropertyValuesHolder!...);
+ method public void reverse();
+ method public void setCurrentFraction(float);
+ method public void setCurrentPlayTime(long);
+ method public androidx.core.animation.ValueAnimator setDuration(long);
+ method public void setEvaluator(androidx.core.animation.TypeEvaluator);
+ method public void setFloatValues(float...);
+ method public static void setFrameDelay(long);
+ method public void setIntValues(int...);
+ method public void setInterpolator(androidx.core.animation.Interpolator?);
+ method public void setNameForTrace(String);
+ method public void setObjectValues(java.lang.Object!...);
+ method public void setRepeatCount(int);
+ method public void setRepeatMode(int);
+ method public void setStartDelay(long);
+ method public void setValues(androidx.core.animation.PropertyValuesHolder!...);
+ field public static final int INFINITE = -1; // 0xffffffff
+ field public static final int RESTART = 1; // 0x1
+ field public static final int REVERSE = 2; // 0x2
+ }
+
+}
+
diff --git a/core/core-animation/build.gradle b/core/core-animation/build.gradle
index 0b22cb9..45da746 100644
--- a/core/core-animation/build.gradle
+++ b/core/core-animation/build.gradle
@@ -25,6 +25,7 @@
api("androidx.annotation:annotation:1.2.0")
implementation("androidx.core:core:1.3.1")
implementation("androidx.collection:collection:1.1.0")
+ implementation("androidx.tracing:tracing:1.0.0")
androidTestImplementation(libs.testExtJunit, excludes.espresso)
androidTestImplementation(libs.testRules, excludes.espresso)
diff --git a/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java b/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java
index 35db26b..4654d57 100644
--- a/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java
+++ b/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java
@@ -27,7 +27,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.core.os.TraceCompat;
+import androidx.tracing.Trace;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1164,7 +1164,7 @@
}
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
- TraceCompat.endSection();
+ Trace.endSection();
}
/**
@@ -1173,7 +1173,7 @@
*/
private void startAnimation() {
- TraceCompat.beginSection(getNameForTrace());
+ Trace.beginSection(getNameForTrace());
mAnimationEndRequested = false;
initAnimation();
diff --git a/development/referenceDocs/stageReferenceDocsWithDackka.sh b/development/referenceDocs/stageReferenceDocsWithDackka.sh
index 304343e..d6bd89e 100755
--- a/development/referenceDocs/stageReferenceDocsWithDackka.sh
+++ b/development/referenceDocs/stageReferenceDocsWithDackka.sh
@@ -47,9 +47,6 @@
#
# Each directory's spelling must match the library's directory in
# frameworks/support.
-#
-# This list should match, or be a subset of, the list of libraries defined in
-# https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt;l=568
readonly javaLibraryDirs=(
"activity"
"annotation"
@@ -59,11 +56,14 @@
"core"
"drawerlayout"
"fragment"
+ "interpolator"
"lifecycle"
"metrics"
"navigation"
"paging"
"vectordrawable"
+ "viewpager"
+ "viewpager2"
"wear"
"window"
)
@@ -77,11 +77,14 @@
"core"
"drawerlayout"
"fragment"
+ "interpolator"
"lifecycle"
"metrics"
"navigation"
"paging"
"vectordrawable"
+ "viewpager"
+ "viewpager2"
"wear"
"window"
)
diff --git a/development/update_playground.sh b/development/update_playground.sh
index 8d4c8ba..2df4eac 100755
--- a/development/update_playground.sh
+++ b/development/update_playground.sh
@@ -1,21 +1,29 @@
#!/bin/bash
MODE=${1:-s}
+
+# Needed for compat between GNU sed (Linux default) and BSD sed (OSX default).
+# - GNU sed requires no space between value for -i
+# - BSD sed requires that a value be passed for -i.
+function fn_sed_inplace {
+ sed -i.bak "$@" && rm "${@: -1}.bak"
+}
+
function fn_update_snapshot {
BUILDID_ANDROIDX=`curl -s https://androidx.dev/snapshots/builds | sed -nr 's|.*snapshots/builds/([0-9]*).*|\1|gp' | head -n 1`
echo "Updating snapshot id: $BUILDID_ANDROIDX"
- sed -i '' "s/androidx.playground.snapshotBuildId=.*/androidx.playground.snapshotBuildId=$BUILDID_ANDROIDX/g" playground-common/playground.properties
+ fn_sed_inplace "s/androidx.playground.snapshotBuildId=.*/androidx.playground.snapshotBuildId=$BUILDID_ANDROIDX/g" playground-common/playground.properties
}
function fn_update_metalava {
BUILDID_METALAVA=`curl -s https://androidx.dev/metalava/builds | sed -nr 's|.*metalava/builds/([0-9]*).*|\1|gp' | head -n 1`
echo "Updating metalava id: $BUILDID_METALAVA"
- sed -i '' "s/androidx.playground.metalavaBuildId=.*/androidx.playground.metalavaBuildId=$BUILDID_METALAVA/g" playground-common/playground.properties
+ fn_sed_inplace "s/androidx.playground.metalavaBuildId=.*/androidx.playground.metalavaBuildId=$BUILDID_METALAVA/g" playground-common/playground.properties
}
function fn_update_dokka {
BUILDID_DOKKA=`curl -s https://androidx.dev/dokka/builds | sed -nr 's|.*dokka/builds/([0-9]*).*|\1|gp' | head -n 1`
echo "Updating dokka id: $BUILDID_DOKKA"
- sed -i '' "s/androidx.playground.dokkaBuildId=.*/androidx.playground.dokkaBuildId=$BUILDID_DOKKA/g" playground-common/playground.properties
+ fn_sed_inplace "s/androidx.playground.dokkaBuildId=.*/androidx.playground.dokkaBuildId=$BUILDID_DOKKA/g" playground-common/playground.properties
}
if [ "$MODE" == "s" ]; then
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 925cdf1..023f597 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -64,6 +64,7 @@
samples(project(":compose:material:material-icons-core:material-icons-core-samples"))
docs(project(":compose:material:material-ripple"))
docs(project(":compose:material:material-window"))
+ samples(project(":compose:material:material-window:material-window-samples"))
samples(project(":compose:material:material:material-samples"))
docs(project(":compose:runtime:runtime"))
docs(project(":compose:runtime:runtime-livedata"))
diff --git a/draganddrop/draganddrop/build.gradle b/draganddrop/draganddrop/build.gradle
index 9595f67..5e8aa01 100644
--- a/draganddrop/draganddrop/build.gradle
+++ b/draganddrop/draganddrop/build.gradle
@@ -26,7 +26,6 @@
api("androidx.appcompat:appcompat:1.4.0")
api("androidx.core:core:1.7.0")
annotationProcessor(libs.nullaway)
- implementation("androidx.core:core:1.3.0-beta01")
androidTestImplementation(libs.robolectric)
androidTestImplementation(libs.mockitoAndroid)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentViewTreeTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentViewTreeTest.kt
index 4bc2f6b..e6c8cc6 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentViewTreeTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentViewTreeTest.kt
@@ -23,7 +23,7 @@
import androidx.fragment.test.R
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -52,7 +52,7 @@
.that(ViewTreeViewModelStoreOwner.get(decorView))
.isNotNull()
assertWithMessage("DialogFragment dialog should have a ViewTreeSavedStateRegistryOwner")
- .that(ViewTreeSavedStateRegistryOwner.get(decorView))
+ .that(decorView.findViewTreeSavedStateRegistryOwner())
.isNotNull()
}
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
index 1e0fda7..9f26148 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
@@ -30,7 +30,7 @@
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -285,7 +285,7 @@
ViewTreeViewModelStoreOwner.get(it)
}
observedTreeViewSavedStateRegistryOwner = fragment.view?.let {
- ViewTreeSavedStateRegistryOwner.get(it)
+ it.findViewTreeSavedStateRegistryOwner()
}
}
@@ -314,10 +314,8 @@
" after commitNow"
)
.that(
- ViewTreeSavedStateRegistryOwner.get(
- fragment.view
- ?: error("no fragment view created")
- )
+ (fragment.view ?: error("no fragment view created"))
+ .findViewTreeSavedStateRegistryOwner()
)
.isSameInstanceAs(fragment.viewLifecycleOwner)
@@ -406,7 +404,7 @@
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
onViewCreatedLifecycleOwner = ViewTreeLifecycleOwner.get(view)
onViewCreatedViewModelStoreOwner = ViewTreeViewModelStoreOwner.get(view)
- onViewCreatedSavedStateRegistryOwner = ViewTreeSavedStateRegistryOwner.get(view)
+ onViewCreatedSavedStateRegistryOwner = view.findViewTreeSavedStateRegistryOwner()
}
}
@@ -464,7 +462,7 @@
var restoredState: String? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val savedStateRegistryOwner = ViewTreeSavedStateRegistryOwner.get(view)!!
+ val savedStateRegistryOwner = view.findViewTreeSavedStateRegistryOwner()!!
val savedStateLifecycle = savedStateRegistryOwner.lifecycle
val savedStateRegistry = savedStateRegistryOwner.savedStateRegistry
savedStateLifecycle.addObserver(
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index 9ad94e3..4f77155 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -101,10 +101,8 @@
public final class ProgressIndicatorDefaults {
method public androidx.glance.unit.ColorProvider getBackgroundColorProvider();
- method public long getColor();
method public androidx.glance.unit.ColorProvider getIndicatorColorProvider();
property public final androidx.glance.unit.ColorProvider BackgroundColorProvider;
- property public final long Color;
property public final androidx.glance.unit.ColorProvider IndicatorColorProvider;
field public static final androidx.glance.appwidget.ProgressIndicatorDefaults INSTANCE;
}
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index 89ed7bd..a46cf1d 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -109,10 +109,8 @@
public final class ProgressIndicatorDefaults {
method public androidx.glance.unit.ColorProvider getBackgroundColorProvider();
- method public long getColor();
method public androidx.glance.unit.ColorProvider getIndicatorColorProvider();
property public final androidx.glance.unit.ColorProvider BackgroundColorProvider;
- property public final long Color;
property public final androidx.glance.unit.ColorProvider IndicatorColorProvider;
field public static final androidx.glance.appwidget.ProgressIndicatorDefaults INSTANCE;
}
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index 9ad94e3..4f77155 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -101,10 +101,8 @@
public final class ProgressIndicatorDefaults {
method public androidx.glance.unit.ColorProvider getBackgroundColorProvider();
- method public long getColor();
method public androidx.glance.unit.ColorProvider getIndicatorColorProvider();
property public final androidx.glance.unit.ColorProvider BackgroundColorProvider;
- property public final long Color;
property public final androidx.glance.unit.ColorProvider IndicatorColorProvider;
field public static final androidx.glance.appwidget.ProgressIndicatorDefaults INSTANCE;
}
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LinearProgressIndicator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LinearProgressIndicator.kt
index 216c487..5980189 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LinearProgressIndicator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LinearProgressIndicator.kt
@@ -101,7 +101,7 @@
* Default color for [LinearProgressIndicator].
* [Material color specification](https://material.io/design/color/the-color-system.html#color-theme-creation)
*/
- val Color = Color(0xFF6200EE)
+ private val Color = Color(0xFF6200EE)
/**
* Default ColorProvider for the progress indicator in [LinearProgressIndicator].
diff --git a/glance/glance-appwidget/src/androidMain/res/values/styles.xml b/glance/glance-appwidget/src/androidMain/res/values/styles.xml
index 2a3f46d..b662b0d 100644
--- a/glance/glance-appwidget/src/androidMain/res/values/styles.xml
+++ b/glance/glance-appwidget/src/androidMain/res/values/styles.xml
@@ -16,27 +16,40 @@
<resources>
<style name="Glance.AppWidget.Box" parent=""/>
- <style name="Glance.AppWidget.Button" parent=""/>
- <style name="Glance.AppWidget.CheckBox" parent=""/>
- <style name="Glance.AppWidget.CheckBoxBackport" parent=""/>
+
+ <style name="Base.Glance.AppWidget.Button" parent="">
+ <!--
+ For all buttons and compound buttons, we reset the foreground (ripple) set by the
+ parent Glance.AppWidget theme, since buttons have ripple backgrounds by default.
+ -->
+ <item name="android:foreground">@null</item>
+ </style>
+
+ <style name="Glance.AppWidget.Button" parent="Base.Glance.AppWidget.Button"/>
+ <style name="Glance.AppWidget.CheckBox" parent="Base.Glance.AppWidget.Button"/>
+ <style name="Glance.AppWidget.CheckBoxBackport" parent="Base.Glance.AppWidget.Button"/>
<style name="Glance.AppWidget.CheckBoxIcon" parent=""/>
<style name="Glance.AppWidget.CheckBoxText" parent=""/>
+
<style name="Glance.AppWidget.Column" parent=""/>
<style name="Glance.AppWidget.List" parent=""/>
<style name="Glance.AppWidget.Row" parent=""/>
- <style name="Glance.AppWidget.Switch" parent=""/>
- <style name="Glance.AppWidget.SwitchBackport" parent=""/>
+
+ <style name="Glance.AppWidget.Switch" parent="Base.Glance.AppWidget.Button"/>
+ <style name="Glance.AppWidget.SwitchBackport" parent="Base.Glance.AppWidget.Button"/>
<style name="Glance.AppWidget.SwitchText" parent=""/>
<style name="Glance.AppWidget.SwitchThumb" parent=""/>
<style name="Glance.AppWidget.SwitchTrack" parent=""/>
+
<style name="Glance.AppWidget.Text" parent=""/>
<style name="Glance.AppWidget.LinearProgressIndicator"
parent="@android:style/Widget.ProgressBar.Horizontal"/>
<style name="Glance.AppWidget.CircularProgressIndicator" parent=""/>
<style name="Glance.AppWidget.VerticalGrid" parent=""/>
- <style name="Glance.AppWidget.RadioButton" parent="" />
- <style name="Glance.AppWidget.RadioButtonIcon" parent="" />
- <style name="Glance.AppWidget.RadioButtonText" parent="" />
+
+ <style name="Glance.AppWidget.RadioButton" parent="Base.Glance.AppWidget.Button"/>
+ <style name="Glance.AppWidget.RadioButtonIcon" parent=""/>
+ <style name="Glance.AppWidget.RadioButtonText" parent=""/>
<style name="Base.Glance.AppWidget.Background" parent="">
<item name="android:id">@android:id/background</item>
diff --git a/glance/glance-appwidget/src/main/res/values-v29/themes.xml b/glance/glance-appwidget/src/main/res/values-v29/themes.xml
index 4cce739..345d2ec 100644
--- a/glance/glance-appwidget/src/main/res/values-v29/themes.xml
+++ b/glance/glance-appwidget/src/main/res/values-v29/themes.xml
@@ -15,5 +15,12 @@
-->
<resources>
- <style name="Glance.AppWidget.Theme" parent="android:Theme.DeviceDefault.DayNight" />
+ <style name="Glance.AppWidget.Theme" parent="android:Theme.DeviceDefault.DayNight">
+ <!--
+ The following line has the effect of displaying ripples for all clickable views.
+ This is reset in the widget styles for buttons since they display ripple backgrounds
+ by default.
+ -->
+ <item name="android:foreground">?android:attr/selectableItemBackground</item>
+ </style>
</resources>
diff --git a/glance/glance-appwidget/src/main/res/values/themes.xml b/glance/glance-appwidget/src/main/res/values/themes.xml
index efd5acd..4743274 100644
--- a/glance/glance-appwidget/src/main/res/values/themes.xml
+++ b/glance/glance-appwidget/src/main/res/values/themes.xml
@@ -15,5 +15,7 @@
-->
<resources>
- <style name="Glance.AppWidget.Theme" parent="android:Theme.DeviceDefault"/>
+ <style name="Glance.AppWidget.Theme" parent="android:Theme.DeviceDefault">
+ <item name="android:foreground">?android:attr/selectableItemBackground</item>
+ </style>
</resources>
diff --git a/health/health-data-client/api/current.txt b/health/health-data-client/api/current.txt
index fbe2584..cb314ab 100644
--- a/health/health-data-client/api/current.txt
+++ b/health/health-data-client/api/current.txt
@@ -3,6 +3,7 @@
public interface HealthDataClient {
method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.data.client.records.Record> recordType, java.util.List<java.lang.String> uidsList, java.util.List<java.lang.String> clientIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.data.client.records.Record> recordType, androidx.health.data.client.time.TimeRangeFilter timeRangeFilter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public default static androidx.health.data.client.HealthDataClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> packageNames);
method public default static androidx.health.data.client.HealthDataClient getOrCreate(android.content.Context context);
method public suspend Object? insertRecords(java.util.List<? extends androidx.health.data.client.records.Record> records, kotlin.coroutines.Continuation<? super androidx.health.data.client.response.InsertRecordsResponse>);
@@ -176,6 +177,90 @@
field public static final String STANDING_UP = "standing_up";
}
+ public final class BodyTemperature implements androidx.health.data.client.records.Record {
+ ctor public BodyTemperature(double temperatureDegreesCelsius, optional String? measurementLocation, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public String? getMeasurementLocation();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public double getTemperatureDegreesCelsius();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final String? measurementLocation;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public final double temperatureDegreesCelsius;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
+ public final class BodyTemperatureMeasurementLocations {
+ field public static final String ARMPIT = "armpit";
+ field public static final String EAR = "ear";
+ field public static final String FINGER = "finger";
+ field public static final String FOREHEAD = "forehead";
+ field public static final androidx.health.data.client.records.BodyTemperatureMeasurementLocations INSTANCE;
+ field public static final String MOUTH = "mouth";
+ field public static final String RECTUM = "rectum";
+ field public static final String TEMPORAL_ARTERY = "temporal_artery";
+ field public static final String TOE = "toe";
+ field public static final String VAGINA = "vagina";
+ field public static final String WRIST = "wrist";
+ }
+
+ public final class Distance implements androidx.health.data.client.records.Record {
+ ctor public Distance(double distanceMeters, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getDistanceMeters();
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public final double distanceMeters;
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
+ public final class ElevationGained implements androidx.health.data.client.records.Record {
+ ctor public ElevationGained(double elevationMeters, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getElevationMeters();
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public final double elevationMeters;
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
+ public final class Height implements androidx.health.data.client.records.Record {
+ ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getHeightMeters();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final double heightMeters;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
+ public final class HipCircumference implements androidx.health.data.client.records.Record {
+ ctor public HipCircumference(double circumferenceMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getCircumferenceMeters();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final double circumferenceMeters;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
public final class OvulationTestResults {
field public static final androidx.health.data.client.records.OvulationTestResults INSTANCE;
field public static final String NEGATIVE = "negative";
@@ -226,3 +311,26 @@
}
+package androidx.health.data.client.time {
+
+ public final class TimeRangeFilter {
+ method public static androidx.health.data.client.time.TimeRangeFilter after(java.time.Instant startTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter after(java.time.LocalDateTime startTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter before(java.time.Instant endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter before(java.time.LocalDateTime endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter between(java.time.Instant startTime, java.time.Instant endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter between(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime);
+ field public static final androidx.health.data.client.time.TimeRangeFilter.Companion Companion;
+ }
+
+ public static final class TimeRangeFilter.Companion {
+ method public androidx.health.data.client.time.TimeRangeFilter after(java.time.Instant startTime);
+ method public androidx.health.data.client.time.TimeRangeFilter after(java.time.LocalDateTime startTime);
+ method public androidx.health.data.client.time.TimeRangeFilter before(java.time.Instant endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter before(java.time.LocalDateTime endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter between(java.time.Instant startTime, java.time.Instant endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter between(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime);
+ }
+
+}
+
diff --git a/health/health-data-client/api/public_plus_experimental_current.txt b/health/health-data-client/api/public_plus_experimental_current.txt
index fbe2584..cb314ab 100644
--- a/health/health-data-client/api/public_plus_experimental_current.txt
+++ b/health/health-data-client/api/public_plus_experimental_current.txt
@@ -3,6 +3,7 @@
public interface HealthDataClient {
method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.data.client.records.Record> recordType, java.util.List<java.lang.String> uidsList, java.util.List<java.lang.String> clientIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.data.client.records.Record> recordType, androidx.health.data.client.time.TimeRangeFilter timeRangeFilter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public default static androidx.health.data.client.HealthDataClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> packageNames);
method public default static androidx.health.data.client.HealthDataClient getOrCreate(android.content.Context context);
method public suspend Object? insertRecords(java.util.List<? extends androidx.health.data.client.records.Record> records, kotlin.coroutines.Continuation<? super androidx.health.data.client.response.InsertRecordsResponse>);
@@ -176,6 +177,90 @@
field public static final String STANDING_UP = "standing_up";
}
+ public final class BodyTemperature implements androidx.health.data.client.records.Record {
+ ctor public BodyTemperature(double temperatureDegreesCelsius, optional String? measurementLocation, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public String? getMeasurementLocation();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public double getTemperatureDegreesCelsius();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final String? measurementLocation;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public final double temperatureDegreesCelsius;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
+ public final class BodyTemperatureMeasurementLocations {
+ field public static final String ARMPIT = "armpit";
+ field public static final String EAR = "ear";
+ field public static final String FINGER = "finger";
+ field public static final String FOREHEAD = "forehead";
+ field public static final androidx.health.data.client.records.BodyTemperatureMeasurementLocations INSTANCE;
+ field public static final String MOUTH = "mouth";
+ field public static final String RECTUM = "rectum";
+ field public static final String TEMPORAL_ARTERY = "temporal_artery";
+ field public static final String TOE = "toe";
+ field public static final String VAGINA = "vagina";
+ field public static final String WRIST = "wrist";
+ }
+
+ public final class Distance implements androidx.health.data.client.records.Record {
+ ctor public Distance(double distanceMeters, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getDistanceMeters();
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public final double distanceMeters;
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
+ public final class ElevationGained implements androidx.health.data.client.records.Record {
+ ctor public ElevationGained(double elevationMeters, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getElevationMeters();
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public final double elevationMeters;
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
+ public final class Height implements androidx.health.data.client.records.Record {
+ ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getHeightMeters();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final double heightMeters;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
+ public final class HipCircumference implements androidx.health.data.client.records.Record {
+ ctor public HipCircumference(double circumferenceMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getCircumferenceMeters();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final double circumferenceMeters;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
public final class OvulationTestResults {
field public static final androidx.health.data.client.records.OvulationTestResults INSTANCE;
field public static final String NEGATIVE = "negative";
@@ -226,3 +311,26 @@
}
+package androidx.health.data.client.time {
+
+ public final class TimeRangeFilter {
+ method public static androidx.health.data.client.time.TimeRangeFilter after(java.time.Instant startTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter after(java.time.LocalDateTime startTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter before(java.time.Instant endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter before(java.time.LocalDateTime endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter between(java.time.Instant startTime, java.time.Instant endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter between(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime);
+ field public static final androidx.health.data.client.time.TimeRangeFilter.Companion Companion;
+ }
+
+ public static final class TimeRangeFilter.Companion {
+ method public androidx.health.data.client.time.TimeRangeFilter after(java.time.Instant startTime);
+ method public androidx.health.data.client.time.TimeRangeFilter after(java.time.LocalDateTime startTime);
+ method public androidx.health.data.client.time.TimeRangeFilter before(java.time.Instant endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter before(java.time.LocalDateTime endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter between(java.time.Instant startTime, java.time.Instant endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter between(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime);
+ }
+
+}
+
diff --git a/health/health-data-client/api/restricted_current.txt b/health/health-data-client/api/restricted_current.txt
index fa506c2..978ba42 100644
--- a/health/health-data-client/api/restricted_current.txt
+++ b/health/health-data-client/api/restricted_current.txt
@@ -3,6 +3,7 @@
public interface HealthDataClient {
method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.data.client.records.Record> recordType, java.util.List<java.lang.String> uidsList, java.util.List<java.lang.String> clientIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.data.client.records.Record> recordType, androidx.health.data.client.time.TimeRangeFilter timeRangeFilter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public default static androidx.health.data.client.HealthDataClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> packageNames);
method public default static androidx.health.data.client.HealthDataClient getOrCreate(android.content.Context context);
method public suspend Object? insertRecords(java.util.List<? extends androidx.health.data.client.records.Record> records, kotlin.coroutines.Continuation<? super androidx.health.data.client.response.InsertRecordsResponse>);
@@ -176,6 +177,90 @@
field public static final String STANDING_UP = "standing_up";
}
+ public final class BodyTemperature implements androidx.health.data.client.records.InstantaneousRecord {
+ ctor public BodyTemperature(double temperatureDegreesCelsius, optional String? measurementLocation, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public String? getMeasurementLocation();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public double getTemperatureDegreesCelsius();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final String? measurementLocation;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public final double temperatureDegreesCelsius;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
+ public final class BodyTemperatureMeasurementLocations {
+ field public static final String ARMPIT = "armpit";
+ field public static final String EAR = "ear";
+ field public static final String FINGER = "finger";
+ field public static final String FOREHEAD = "forehead";
+ field public static final androidx.health.data.client.records.BodyTemperatureMeasurementLocations INSTANCE;
+ field public static final String MOUTH = "mouth";
+ field public static final String RECTUM = "rectum";
+ field public static final String TEMPORAL_ARTERY = "temporal_artery";
+ field public static final String TOE = "toe";
+ field public static final String VAGINA = "vagina";
+ field public static final String WRIST = "wrist";
+ }
+
+ public final class Distance implements androidx.health.data.client.records.IntervalRecord {
+ ctor public Distance(double distanceMeters, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getDistanceMeters();
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public final double distanceMeters;
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
+ public final class ElevationGained implements androidx.health.data.client.records.IntervalRecord {
+ ctor public ElevationGained(double elevationMeters, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getElevationMeters();
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public final double elevationMeters;
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
+ public final class Height implements androidx.health.data.client.records.InstantaneousRecord {
+ ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getHeightMeters();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final double heightMeters;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
+ public final class HipCircumference implements androidx.health.data.client.records.InstantaneousRecord {
+ ctor public HipCircumference(double circumferenceMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.data.client.metadata.Metadata metadata);
+ method public double getCircumferenceMeters();
+ method public androidx.health.data.client.metadata.Metadata getMetadata();
+ method public java.time.Instant getTime();
+ method public java.time.ZoneOffset? getZoneOffset();
+ property public final double circumferenceMeters;
+ property public androidx.health.data.client.metadata.Metadata metadata;
+ property public java.time.Instant time;
+ property public java.time.ZoneOffset? zoneOffset;
+ }
+
@kotlin.PublishedApi internal interface InstantaneousRecord extends androidx.health.data.client.records.Record {
method public java.time.Instant getTime();
method public java.time.ZoneOffset? getZoneOffset();
@@ -244,3 +329,26 @@
}
+package androidx.health.data.client.time {
+
+ public final class TimeRangeFilter {
+ method public static androidx.health.data.client.time.TimeRangeFilter after(java.time.Instant startTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter after(java.time.LocalDateTime startTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter before(java.time.Instant endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter before(java.time.LocalDateTime endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter between(java.time.Instant startTime, java.time.Instant endTime);
+ method public static androidx.health.data.client.time.TimeRangeFilter between(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime);
+ field public static final androidx.health.data.client.time.TimeRangeFilter.Companion Companion;
+ }
+
+ public static final class TimeRangeFilter.Companion {
+ method public androidx.health.data.client.time.TimeRangeFilter after(java.time.Instant startTime);
+ method public androidx.health.data.client.time.TimeRangeFilter after(java.time.LocalDateTime startTime);
+ method public androidx.health.data.client.time.TimeRangeFilter before(java.time.Instant endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter before(java.time.LocalDateTime endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter between(java.time.Instant startTime, java.time.Instant endTime);
+ method public androidx.health.data.client.time.TimeRangeFilter between(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime);
+ }
+
+}
+
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/HealthDataClient.kt b/health/health-data-client/src/main/java/androidx/health/data/client/HealthDataClient.kt
index 2a4f50c..c328812 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/HealthDataClient.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/HealthDataClient.kt
@@ -21,11 +21,13 @@
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RestrictTo
import androidx.health.data.client.aggregate.AggregateDataRow
+import androidx.health.data.client.aggregate.AggregateDataRowGroupByDuration
import androidx.health.data.client.aggregate.AggregateMetric
import androidx.health.data.client.impl.HealthDataClientImpl
import androidx.health.data.client.metadata.DataOrigin
import androidx.health.data.client.permission.Permission
import androidx.health.data.client.records.Record
+import androidx.health.data.client.request.AggregateGroupByDurationRequest
import androidx.health.data.client.request.AggregateRequest
import androidx.health.data.client.request.ChangesTokenRequest
import androidx.health.data.client.request.ReadRecordsRequest
@@ -75,8 +77,7 @@
* @throws IOException For any disk I/O issues.
* @throws IllegalStateException If service is not available.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- suspend fun updateRecords(records: List<Record>)
+ @RestrictTo(RestrictTo.Scope.LIBRARY) suspend fun updateRecords(records: List<Record>)
/**
* Deletes one or more [Record] by their identifiers. Deletion of multiple [Record] is executed
@@ -99,13 +100,9 @@
)
/**
- * Deletes one or more [Record] points of the given [recordType] in the given range
- * (automatically filtered to [Record] belonging to this application). Deletion of multiple
- * [Record] is executed in a transaction - if one fails, none is deleted.
- *
- * When a field is null in [TimeRangeFilter] then the filtered range is open-ended in that
- * direction. Hence if all fields are null in [TimeRangeFilter] then all data of the requested
- * [Record] type is deleted.
+ * Deletes any [Record] points of the given [recordType] in the given [timeRangeFilter]
+ * (automatically filtered to [Record] belonging to the calling application). Deletion of
+ * multiple [Record] is executed in a transaction - if one fails, none is deleted.
*
* @param recordType Which type of [Record] to delete, such as `Steps::class`
* @param timeRangeFilter The [TimeRangeFilter] to delete from
@@ -114,7 +111,6 @@
* @throws IOException For any disk I/O issues.
* @throws IllegalStateException If service is not available.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
suspend fun deleteRecords(recordType: KClass<out Record>, timeRangeFilter: TimeRangeFilter)
/**
@@ -150,10 +146,10 @@
* Reads [AggregateMetric]s according to requested read criteria: [Record]s from
* [dataOriginFilter] and within [timeRangeFilter].
*
- * @param request [AggregateRequest] object specifying [AggregateMetric]s to aggregate other
+ * @param request [AggregateRequest] object specifying [AggregateMetric]s to aggregate and other
* filters.
*
- * @return a response containing a [AggregateDataRow].
+ * @return the [AggregateDataRow] that contains aggregated values.
* @throws RemoteException For any IPC transportation failures.
* @throws SecurityException For requests with unpermitted access.
* @throws IOException For any disk I/O issues.
@@ -162,7 +158,31 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
suspend fun aggregate(request: AggregateRequest): AggregateDataRow
- // TODO(b/221725298): Adds overload with groupBy that return a list
+ /**
+ * Reads [AggregateMetric]s according to requested read criteria specified in
+ * [AggregateGroupByDurationRequest].
+ *
+ * This method is similar to [aggregate] but instead of returning one [AggregateDataRow] for the
+ * entire query's time interval, it returns a list of [AggregateDataRowGroupByDuration], with
+ * each row keyed by start and end time. For example: steps for today bucketed by hours.
+ *
+ * A [AggregateDataRowGroupByDuration] is returned only if there are [Record] points to
+ * aggregate within start and end time of the row.
+ *
+ * @param request [AggregateGroupByDurationRequest] object specifying [AggregateMetric]s to
+ * aggregate and other filters.
+ *
+ * @return a list of [AggregateDataRowGroupByDuration]s, each contains aggregated values and
+ * start/end time of the row. The list is sorted by time in ascending order.
+ * @throws RemoteException For any IPC transportation failures.
+ * @throws SecurityException For requests with unpermitted access.
+ * @throws IOException For any disk I/O issues.
+ * @throws IllegalStateException If service is not available.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ suspend fun aggregateGroupByDuration(
+ request: AggregateGroupByDurationRequest,
+ ): List<AggregateDataRowGroupByDuration>
/**
* Retrieves a changes-token, representing a point in time in the underlying Android Health
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRow.kt b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRow.kt
index d4f6f1c..4590160 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRow.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRow.kt
@@ -27,8 +27,6 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
class AggregateDataRow
internal constructor(
- // TODO(b/219327548): Accommodate optional aggregate groupBy keys (time range) when we add
- // groupBy.
internal val longValues: Map<String, Long>,
internal val doubleValues: Map<String, Double>,
/** List of [DataOrigin]s that contributed to the aggregation result. */
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRowGroupByDuration.kt b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRowGroupByDuration.kt
new file mode 100644
index 0000000..1b46315
--- /dev/null
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateDataRowGroupByDuration.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.aggregate
+
+import androidx.annotation.RestrictTo
+import java.time.Instant
+import java.time.ZoneOffset
+
+/**
+ * Represents an aggregation result row.
+ *
+ * See [HealthDataClient.aggregateGroupByDuration]
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressWarnings("NewApi")
+class AggregateDataRowGroupByDuration
+internal constructor(
+ public val data: AggregateDataRow,
+ public val startTime: Instant,
+ public val endTime: Instant,
+ public val zoneOffset: ZoneOffset,
+) {
+ init {
+ require(startTime.isBefore(endTime)) { "start time must be before end time" }
+ }
+}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateMetric.kt b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateMetric.kt
index 15d1f63..94decdf 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateMetric.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/AggregateMetric.kt
@@ -25,14 +25,14 @@
/** Aggregation of the metric, for SDK internal use only. */
val aggregationSuffix: String
/** Optional field name of the metric, for SDK internal use only */
- val fieldName: String?
+ val aggregateFieldName: String?
}
/** Serve for internal use to look up a metric value from metric value dictionary. */
internal val AggregateMetric.metricKey: String
get() {
- return if (fieldName != null) {
- "${dataTypeName}_${fieldName}_$aggregationSuffix"
+ return if (aggregateFieldName != null) {
+ "${dataTypeName}_${aggregateFieldName}_$aggregationSuffix"
} else {
"${dataTypeName}_$aggregationSuffix"
}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DoubleAggregateMetric.kt b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DoubleAggregateMetric.kt
index aa11c81..e16de76 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DoubleAggregateMetric.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DoubleAggregateMetric.kt
@@ -27,5 +27,5 @@
internal constructor(
override val dataTypeName: String,
override val aggregationSuffix: String,
- override val fieldName: String? = null
+ override val aggregateFieldName: String? = null
) : AggregateMetric
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DurationAggregateMetric.kt b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DurationAggregateMetric.kt
index e6d3e0a..ac50645 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DurationAggregateMetric.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/DurationAggregateMetric.kt
@@ -24,7 +24,9 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
class DurationAggregateMetric
-internal constructor(override val dataTypeName: String, override val aggregationSuffix: String) :
- AggregateMetric {
- override val fieldName: String? = null
+internal constructor(
+ override val dataTypeName: String,
+ override val aggregationSuffix: String,
+) : AggregateMetric {
+ override val aggregateFieldName: String? = null
}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/LongAggregateMetric.kt b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/LongAggregateMetric.kt
index bef9fe8..41f16ef 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/LongAggregateMetric.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/aggregate/LongAggregateMetric.kt
@@ -27,5 +27,5 @@
internal constructor(
override val dataTypeName: String,
override val aggregationSuffix: String,
- override val fieldName: String? = null
+ override val aggregateFieldName: String? = null
) : AggregateMetric
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/impl/HealthDataClientImpl.kt b/health/health-data-client/src/main/java/androidx/health/data/client/impl/HealthDataClientImpl.kt
index 8416bf0..1668ac1 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/impl/HealthDataClientImpl.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/impl/HealthDataClientImpl.kt
@@ -17,6 +17,9 @@
import androidx.health.data.client.HealthDataClient
import androidx.health.data.client.aggregate.AggregateDataRow
+import androidx.health.data.client.aggregate.AggregateDataRowGroupByDuration
+import androidx.health.data.client.impl.converters.aggregate.retrieveAggregateDataRow
+import androidx.health.data.client.impl.converters.aggregate.toAggregateDataRowGroupByDuration
import androidx.health.data.client.impl.converters.datatype.toDataTypeIdPairProtoList
import androidx.health.data.client.impl.converters.datatype.toDataTypeName
import androidx.health.data.client.impl.converters.permission.toJetpackPermission
@@ -24,14 +27,14 @@
import androidx.health.data.client.impl.converters.records.toProto
import androidx.health.data.client.impl.converters.records.toRecord
import androidx.health.data.client.impl.converters.request.toDeleteDataRangeRequestProto
+import androidx.health.data.client.impl.converters.request.toProto
import androidx.health.data.client.impl.converters.request.toReadDataRangeRequestProto
import androidx.health.data.client.impl.converters.request.toReadDataRequestProto
import androidx.health.data.client.impl.converters.response.toChangesResponse
import androidx.health.data.client.impl.converters.response.toReadRecordsResponse
-import androidx.health.data.client.impl.converters.time.toProto
-import androidx.health.data.client.metadata.DataOrigin
import androidx.health.data.client.permission.Permission
import androidx.health.data.client.records.Record
+import androidx.health.data.client.request.AggregateGroupByDurationRequest
import androidx.health.data.client.request.AggregateRequest
import androidx.health.data.client.request.ChangesTokenRequest
import androidx.health.data.client.request.ReadRecordsRequest
@@ -74,7 +77,7 @@
override suspend fun deleteRecords(
recordType: KClass<out Record>,
uidsList: List<String>,
- clientIdsList: List<String>
+ clientIdsList: List<String>,
) {
delegate
.deleteData(
@@ -86,7 +89,7 @@
override suspend fun deleteRecords(
recordType: KClass<out Record>,
- timeRangeFilter: TimeRangeFilter
+ timeRangeFilter: TimeRangeFilter,
) {
delegate.deleteDataRange(toDeleteDataRangeRequestProto(recordType, timeRangeFilter)).await()
}
@@ -94,7 +97,7 @@
@Suppress("UNCHECKED_CAST") // Safe to cast as the type should match
override suspend fun <T : Record> readRecord(
recordType: KClass<T>,
- uid: String
+ uid: String,
): ReadRecordResponse<T> {
val proto = delegate.readData(toReadDataRequestProto(recordType, uid)).await()
return ReadRecordResponse(toRecord(proto) as T)
@@ -136,42 +139,21 @@
}
override suspend fun <T : Record> readRecords(
- request: ReadRecordsRequest<T>
+ request: ReadRecordsRequest<T>,
): ReadRecordsResponse<T> {
val proto = delegate.readDataRange(toReadDataRangeRequestProto(request)).await()
return toReadRecordsResponse(proto)
}
override suspend fun aggregate(request: AggregateRequest): AggregateDataRow {
- val responseProto =
- delegate
- .aggregate(
- RequestProto.AggregateDataRequest.newBuilder()
- .setTimeSpec(request.timeRangeFilter.toProto())
- .addAllDataOrigin(
- request.dataOriginFilter.map {
- DataProto.DataOrigin.newBuilder()
- .setApplicationId(it.packageName)
- .build()
- }
- )
- .addAllMetricSpec(
- request.metrics.map {
- RequestProto.AggregateMetricSpec.newBuilder()
- .setDataTypeName(it.dataTypeName)
- .setAggregationType(it.aggregationSuffix)
- .setFieldName(it.fieldName ?: "")
- .build()
- }
- )
- .build()
- )
- .await()
- val rowProto = responseProto.rowsList.first()
- return AggregateDataRow(
- longValues = rowProto.longValuesMap,
- doubleValues = rowProto.doubleValuesMap,
- dataOrigins = rowProto.dataOriginsList.map { DataOrigin(it.applicationId) }
- )
+ val responseProto = delegate.aggregate(request.toProto()).await()
+ return responseProto.rowsList.first().retrieveAggregateDataRow()
+ }
+
+ override suspend fun aggregateGroupByDuration(
+ request: AggregateGroupByDurationRequest,
+ ): List<AggregateDataRowGroupByDuration> {
+ val responseProto = delegate.aggregate(request.toProto()).await()
+ return responseProto.rowsList.map { it.toAggregateDataRowGroupByDuration() }.toList()
}
}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/aggregate/AggregateMetricToProto.kt b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/aggregate/AggregateMetricToProto.kt
new file mode 100644
index 0000000..c2d0811
--- /dev/null
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/aggregate/AggregateMetricToProto.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.impl.converters.aggregate
+
+import androidx.health.data.client.aggregate.AggregateMetric
+import androidx.health.platform.client.proto.RequestProto
+
+fun AggregateMetric.toProto(): RequestProto.AggregateMetricSpec =
+ RequestProto.AggregateMetricSpec.newBuilder()
+ .setDataTypeName(dataTypeName)
+ .setAggregationType(aggregationSuffix)
+ .apply { aggregateFieldName?.let { fieldName = it } }
+ .build()
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt
new file mode 100644
index 0000000..3953711
--- /dev/null
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.impl.converters.aggregate
+
+import androidx.health.data.client.aggregate.AggregateDataRow
+import androidx.health.data.client.aggregate.AggregateDataRowGroupByDuration
+import androidx.health.data.client.metadata.DataOrigin
+import androidx.health.platform.client.proto.DataProto
+import java.time.Instant
+import java.time.ZoneOffset
+
+// ZoneOffset.ofTotalSeconds() has been banned but safe here for serialization.
+@SuppressWarnings("GoodTime, NewApi")
+fun DataProto.AggregateDataRow.toAggregateDataRowGroupByDuration():
+ AggregateDataRowGroupByDuration {
+ require(hasStartTimeEpochMs()) { "start time must be set" }
+ require(hasEndTimeEpochMs()) { "end time must be set" }
+
+ return AggregateDataRowGroupByDuration(
+ data = retrieveAggregateDataRow(),
+ startTime = Instant.ofEpochMilli(startTimeEpochMs),
+ endTime = Instant.ofEpochMilli(endTimeEpochMs),
+ zoneOffset = ZoneOffset.ofTotalSeconds(zoneOffsetSeconds)
+ )
+}
+
+fun DataProto.AggregateDataRow.retrieveAggregateDataRow() =
+ AggregateDataRow(
+ longValues = longValuesMap,
+ doubleValues = doubleValuesMap,
+ dataOrigins = dataOriginsList.map { DataOrigin(it.applicationId) }
+ )
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/ProtoToRecordConverters.kt b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/ProtoToRecordConverters.kt
index 749d25b..e0849d3 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -441,6 +441,7 @@
vitaminKGrams = getDouble("vitaminK"),
zincGrams = getDouble("zinc"),
mealType = getEnum("mealType"),
+ name = getString("name"),
startTime = startTime,
startZoneOffset = startZoneOffset,
endTime = endTime,
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/RecordToProtoConverters.kt b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/RecordToProtoConverters.kt
index e5afdd3..6265121 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/records/RecordToProtoConverters.kt
@@ -445,6 +445,7 @@
putValues("zinc", doubleVal(zincGrams))
}
mealType?.let { putValues("mealType", enumVal(it)) }
+ name?.let { putValues("name", stringVal(it)) }
}
.build()
is Repetitions ->
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/request/AggregateRequestToProto.kt b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/request/AggregateRequestToProto.kt
new file mode 100644
index 0000000..1b66da6
--- /dev/null
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/impl/converters/request/AggregateRequestToProto.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.impl.converters.request
+
+import androidx.health.data.client.impl.converters.aggregate.toProto
+import androidx.health.data.client.impl.converters.time.toProto
+import androidx.health.data.client.metadata.DataOrigin
+import androidx.health.data.client.request.AggregateGroupByDurationRequest
+import androidx.health.data.client.request.AggregateRequest
+import androidx.health.platform.client.proto.DataProto
+import androidx.health.platform.client.proto.RequestProto
+
+fun AggregateRequest.toProto(): RequestProto.AggregateDataRequest =
+ RequestProto.AggregateDataRequest.newBuilder()
+ .setTimeSpec(timeRangeFilter.toProto())
+ .addAllDataOrigin(dataOriginFilter.toProtoList())
+ .addAllMetricSpec(metrics.map { it.toProto() })
+ .build()
+
+@SuppressWarnings("NewApi")
+fun AggregateGroupByDurationRequest.toProto(): RequestProto.AggregateDataRequest =
+ RequestProto.AggregateDataRequest.newBuilder()
+ .setTimeSpec(timeRangeFilter.toProto())
+ .addAllDataOrigin(dataOriginFilter.toProtoList())
+ .addAllMetricSpec(metrics.map { it.toProto() })
+ .setSliceDurationMillis(timeRangeSlicer.toMillis())
+ .build()
+
+private fun List<DataOrigin>.toProtoList() =
+ this.map { DataProto.DataOrigin.newBuilder().setApplicationId(it.packageName).build() }
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperature.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperature.kt
index 71825a8..a7dc556 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperature.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperature.kt
@@ -15,7 +15,6 @@
*/
package androidx.health.data.client.records
-import androidx.annotation.RestrictTo
import androidx.health.data.client.metadata.Metadata
import java.time.Instant
import java.time.ZoneOffset
@@ -24,7 +23,6 @@
* Captures the body temperature of a user. Each record represents a single instantaneous body
* temperature measurement.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class BodyTemperature(
/** Temperature in degrees Celsius. Required field. Valid range: 0-100. */
public val temperatureDegreesCelsius: Double,
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperatureMeasurementLocations.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperatureMeasurementLocations.kt
index 84918dd..707ad7f 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperatureMeasurementLocations.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/BodyTemperatureMeasurementLocations.kt
@@ -19,7 +19,6 @@
import androidx.annotation.StringDef
/** Where on the user's body a temperature measurement was taken from. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public object BodyTemperatureMeasurementLocations {
const val ARMPIT = "armpit"
const val FINGER = "finger"
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/Distance.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/Distance.kt
index 74e40d7..8cbb3ea 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/Distance.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/Distance.kt
@@ -15,7 +15,6 @@
*/
package androidx.health.data.client.records
-import androidx.annotation.RestrictTo
import androidx.health.data.client.aggregate.DoubleAggregateMetric
import androidx.health.data.client.metadata.Metadata
import java.time.Instant
@@ -25,9 +24,12 @@
* Captures distance travelled by the user since the last reading, in meters. The total distance
* over an interval can be calculated by adding together all the values during the interval. The
* start time of each record should represent the start of the interval in which the distance was
- * covered. The start time must be equal to or greater than the end time of the previous data point.
+ * covered.
+ *
+ * If break downs are preferred in scenario of a long workout, consider writing multiple distance
+ * records. The start time of each record should be equal to or greater than the end time of the
+ * previous record.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class Distance(
/** Distance in meters. Required field. Valid range: 0-1000000. */
public val distanceMeters: Double,
@@ -61,10 +63,10 @@
return result
}
- companion object {
+ internal companion object {
/** Metric identifier to retrieve total distance from [AggregateDataRow]. */
@JvmStatic
- val DISTANCE_TOTAL: DoubleAggregateMetric =
+ internal val DISTANCE_TOTAL: DoubleAggregateMetric =
DoubleAggregateMetric("Distance", "total", "distance")
}
}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/ElevationGained.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/ElevationGained.kt
index 8ae8530..f5886d14 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/ElevationGained.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/ElevationGained.kt
@@ -15,14 +15,12 @@
*/
package androidx.health.data.client.records
-import androidx.annotation.RestrictTo
import androidx.health.data.client.aggregate.DoubleAggregateMetric
import androidx.health.data.client.metadata.Metadata
import java.time.Instant
import java.time.ZoneOffset
/** Captures the elevation gained by the user since the last reading. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class ElevationGained(
/** Elevation in meters. Required field. Valid range: -1000000-1000000. */
public val elevationMeters: Double,
@@ -56,10 +54,10 @@
return result
}
- companion object {
+ internal companion object {
/** Metric identifier to retrieve total elevation gained from [AggregateDataRow]. */
@JvmStatic
- val ELEVATION_TOTAL: DoubleAggregateMetric =
+ internal val ELEVATION_TOTAL: DoubleAggregateMetric =
DoubleAggregateMetric("ElevationGained", "total", "elevation")
}
}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/Height.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/Height.kt
index 471f8b8..8d9b426 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/Height.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/Height.kt
@@ -15,14 +15,12 @@
*/
package androidx.health.data.client.records
-import androidx.annotation.RestrictTo
import androidx.health.data.client.aggregate.DoubleAggregateMetric
import androidx.health.data.client.metadata.Metadata
import java.time.Instant
import java.time.ZoneOffset
/** Captures the user's height in meters. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class Height(
/** Height in meters. Required field. Valid range: 0-3. */
public val heightMeters: Double,
@@ -51,17 +49,20 @@
return result
}
- companion object {
+ internal companion object {
/** Metric identifier to retrieve average height from [AggregateDataRow]. */
@JvmStatic
- val HEIGHT_AVG: DoubleAggregateMetric = DoubleAggregateMetric("Height", "avg", "height")
+ internal val HEIGHT_AVG: DoubleAggregateMetric =
+ DoubleAggregateMetric("Height", "avg", "height")
/** Metric identifier to retrieve minimum height from [AggregateDataRow]. */
@JvmStatic
- val HEIGHT_MIN: DoubleAggregateMetric = DoubleAggregateMetric("Height", "min", "height")
+ internal val HEIGHT_MIN: DoubleAggregateMetric =
+ DoubleAggregateMetric("Height", "min", "height")
/** Metric identifier to retrieve maximum height from [AggregateDataRow]. */
@JvmStatic
- val HEIGHT_MAX: DoubleAggregateMetric = DoubleAggregateMetric("Height", "max", "height")
+ internal val HEIGHT_MAX: DoubleAggregateMetric =
+ DoubleAggregateMetric("Height", "max", "height")
}
}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/HipCircumference.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/HipCircumference.kt
index 716a6cd..e82d523 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/HipCircumference.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/HipCircumference.kt
@@ -15,13 +15,11 @@
*/
package androidx.health.data.client.records
-import androidx.annotation.RestrictTo
import androidx.health.data.client.metadata.Metadata
import java.time.Instant
import java.time.ZoneOffset
/** Captures the user's hip circumference in meters. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class HipCircumference(
/** Circumference in meters. Required field. Valid range: 0-10. */
public val circumferenceMeters: Double,
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/Nutrition.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/Nutrition.kt
index 9e2429d..0bd018c 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/Nutrition.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/records/Nutrition.kt
@@ -107,6 +107,8 @@
public val vitaminKGrams: Double = 0.0,
/** Zinc in grams. Optional field. Valid range: 0-100. */
public val zincGrams: Double = 0.0,
+ /** Name for food or drink, provided by the user. Optional field. */
+ public val name: String? = null,
/**
* Type of meal related to the nutrients consumed. Optional, enum field. Allowed values:
* [MealType].
@@ -165,6 +167,7 @@
if (vitaminKGrams != other.vitaminKGrams) return false
if (zincGrams != other.zincGrams) return false
if (mealType != other.mealType) return false
+ if (name != other.name) return false
if (startTime != other.startTime) return false
if (startZoneOffset != other.startZoneOffset) return false
if (endTime != other.endTime) return false
@@ -219,6 +222,7 @@
result = 31 * result + vitaminKGrams.hashCode()
result = 31 * result + zincGrams.hashCode()
result = 31 * result + mealType.hashCode()
+ result = 31 * result + name.hashCode()
result = 31 * result + (startZoneOffset?.hashCode() ?: 0)
result = 31 * result + endTime.hashCode()
result = 31 * result + (endZoneOffset?.hashCode() ?: 0)
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/records/Pace.kt b/health/health-data-client/src/main/java/androidx/health/data/client/records/Pace.kt
deleted file mode 100644
index 5f36e7a..0000000
--- a/health/health-data-client/src/main/java/androidx/health/data/client/records/Pace.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.health.data.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.data.client.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures the user's pace in minutes per kilometer. The value represents the scalar magnitude of
- * the pace, so negative values should not occur.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class Pace(
- /** Pace in minutes per kilometer. Required field. Valid range: 0-1000000. */
- public val pace: Double,
- override val time: Instant,
- override val zoneOffset: ZoneOffset?,
- override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Pace) return false
-
- if (pace != other.pace) return false
- if (time != other.time) return false
- if (zoneOffset != other.zoneOffset) return false
- if (metadata != other.metadata) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = 0
- result = 31 * result + pace.hashCode()
- result = 31 * result + time.hashCode()
- result = 31 * result + (zoneOffset?.hashCode() ?: 0)
- result = 31 * result + metadata.hashCode()
- return result
- }
-}
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateGroupByDurationRequest.kt b/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateGroupByDurationRequest.kt
new file mode 100644
index 0000000..a1552c8
--- /dev/null
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateGroupByDurationRequest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.request
+
+import androidx.annotation.RestrictTo
+import androidx.health.data.client.aggregate.AggregateMetric
+import androidx.health.data.client.metadata.DataOrigin
+import androidx.health.data.client.time.TimeRangeFilter
+import java.time.Duration
+
+/**
+ * Request object to read time bucketed aggregations for given [AggregateMetric]s in Android Health
+ * Platform.
+ *
+ * @property metrics Set of [AggregateMetric]s to aggregate, such as `Steps::STEPS_COUNT_TOTAL`.
+ * @property timeRangeFilter The [TimeRangeFilter] to read from.
+ * @property timeRangeSlicer The bucket size of each returned aggregate row. [timeRangeFilter] will
+ * be sliced into several equal-sized time buckets (except for the last one).
+ * @property dataOriginFilter List of [DataOrigin]s to read from, or empty for no filter.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class AggregateGroupByDurationRequest(
+ internal val metrics: Set<AggregateMetric>,
+ internal val timeRangeFilter: TimeRangeFilter,
+ internal val timeRangeSlicer: Duration,
+ internal val dataOriginFilter: List<DataOrigin> = emptyList(),
+)
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateRequest.kt b/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateRequest.kt
index 949168d..895e1d2 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateRequest.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/request/AggregateRequest.kt
@@ -21,7 +21,7 @@
import androidx.health.data.client.time.TimeRangeFilter
/**
- * Request object to read aggregate for given [AggregateMetric]s in Android Health Platform.
+ * Request object to read aggregations for given [AggregateMetric]s in Android Health Platform.
*
* @property metrics Set of [AggregateMetric]s to aggregate, such as `Steps::STEPS_COUNT_TOTAL`.
* @property timeRangeFilter The [TimeRangeFilter] to read from.
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/time/TimeRangeFilter.kt b/health/health-data-client/src/main/java/androidx/health/data/client/time/TimeRangeFilter.kt
index b1f261c..9ec302b 100644
--- a/health/health-data-client/src/main/java/androidx/health/data/client/time/TimeRangeFilter.kt
+++ b/health/health-data-client/src/main/java/androidx/health/data/client/time/TimeRangeFilter.kt
@@ -15,79 +15,123 @@
*/
package androidx.health.data.client.time
-import androidx.annotation.RestrictTo
+import androidx.health.data.client.records.Record
import java.time.Instant
import java.time.LocalDateTime
/**
* Specification of time range for read and delete requests.
*
- * The time range can be specified either in exact times, or zoneless local times.
+ * The time range can be specified in one of the following ways:
+ * - use [between] for a closed-ended time range, inclusive-exclusive;
+ * - use [before] for a open-ended start time range, end time is exclusive;
+ * - use [after] for a open-ended end time range, start time is inclusive.
+ *
+ * Time can be specified in one of the two ways:
+ * - use [Instant] for a specific point in time such as "2021-01-03 at 10:00 UTC+1";
+ * - use [LocalDateTime] for a user experienced time concept such as "2021-01-03 at 10 o'clock",
+ * without knowing which time zone the user was at that time.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
class TimeRangeFilter
-internal constructor(
- val startTime: Instant? = null,
- val endTime: Instant? = null,
- val localStartTime: LocalDateTime? = null,
- val localEndTime: LocalDateTime? = null,
+private constructor(
+ internal val startTime: Instant? = null,
+ internal val endTime: Instant? = null,
+ internal val localStartTime: LocalDateTime? = null,
+ internal val localEndTime: LocalDateTime? = null,
) {
companion object {
- @JvmSynthetic
- @JvmOverloads
/**
- * Specifies the time range with [Instant] start and end time, inclusive - exclusive.
+ * Creates a [TimeRangeFilter] for a time range within the [Instant] time range
+ * [startTime, endTime).
*
- * E.g. User created [Record]s at 2pm(UTC+1), crossed a time zone and created new [Record]s
- * at 3pm(UTC). Filtering between 2pm(UTC) and 6pm(UTC) will include records at 3pm(UTC) but
- * not records at 2pm(UTC+1).
+ * If user created a [Record] at 2pm(UTC+1), crossed a time zone and created a new [Record]
+ * at 3pm(UTC). Filtering between 2pm(UTC) and 6pm(UTC) will include the record at 3pm(UTC)
+ * but not the record at 2pm(UTC+1), because 2pm(UTC+1) happened before 2pm(UTC).
*
- * Both interval endpoints are nullable where any null value means the interval is
- * open-ended in that direction.
+ * @param startTime start time of the filter.
+ * @param endTime end time of the filter.
+ * @return a [TimeRangeFilter] for filtering [Record]s.
+ *
+ * @see before for time range with open-ended [startTime].
+ * @see after for time range with open-ended [endTime].
*/
- fun exact(startTime: Instant? = null, endTime: Instant? = null): TimeRangeFilter =
- TimeRangeFilter(
- startTime = startTime,
- endTime = endTime,
- localStartTime = null,
- localEndTime = null
- )
+ @JvmStatic
+ fun between(startTime: Instant, endTime: Instant): TimeRangeFilter {
+ require(startTime.isBefore(endTime)) { "end time needs be after start time" }
+ return TimeRangeFilter(startTime = startTime, endTime = endTime)
+ }
- @JvmSynthetic
- @JvmOverloads
/**
- * Specifies the time range with [LocalDateTime] start and end time, inclusive - exclusive,
- * without specifying any time zone.
+ * Creates a [TimeRangeFilter] for a time range within the [LocalDateTime] range
+ * [startTime, endTime).
*
- * E.g. User created [Record]s at 2pm(UTC+1), crossed a time zone and created new [Record]s
- * at 3pm(UTC). Filtering between 2pm and 6pm will include records at both 2pm(UTC+1) and
- * 3pm(UTC).
+ * @param startTime start time of the filter.
+ * @param endTime end time of the filter.
+ * @return a [TimeRangeFilter] for filtering [Record]s.
*
- * Both interval endpoints are nullable where any null value means the interval is
- * open-ended in that direction.
+ * @see before for time range with open-ended [startTime].
+ * @see after for time range with open-ended [endTime].
*/
- fun zoneless(
- localStartTime: LocalDateTime? = null,
- localEndTime: LocalDateTime? = null
- ): TimeRangeFilter =
- TimeRangeFilter(
- startTime = null,
- endTime = null,
- localStartTime = localStartTime,
- localEndTime = localEndTime
- )
+ @JvmStatic
+ fun between(startTime: LocalDateTime, endTime: LocalDateTime): TimeRangeFilter {
+ require(startTime.isBefore(endTime)) { "end time needs be after start time" }
+ return TimeRangeFilter(localStartTime = startTime, localEndTime = endTime)
+ }
- @JvmSynthetic
- /** Default [TimeRangeFilter] where all fields are null. */
- fun empty(): TimeRangeFilter =
- TimeRangeFilter(
- startTime = null,
- endTime = null,
- localStartTime = null,
- localEndTime = null
- )
+ /**
+ * Creates a [TimeRangeFilter] for a time range until the given [endTime].
+ *
+ * @param endTime end time of the filter.
+ * @return a [TimeRangeFilter] for filtering [Record]s.
+ *
+ * @see between for closed-ended time range.
+ * @see after for time range with open-ended [endTime]
+ */
+ @JvmStatic
+ fun before(endTime: Instant) = TimeRangeFilter(endTime = endTime)
+
+ /**
+ * Creates a [TimeRangeFilter] for a time range until the given [endTime].
+ *
+ * @param endTime end time of the filter.
+ * @return a [TimeRangeFilter] for filtering [Record]s.
+ *
+ * @see between for closed-ended time range.
+ * @see after for time range with open-ended [endTime]
+ */
+ @JvmStatic
+ fun before(endTime: LocalDateTime) = TimeRangeFilter(localEndTime = endTime)
+
+ /**
+ * Creates a [TimeRangeFilter] for a time range after the given [startTime].
+ *
+ * @param startTime start time of the filter.
+ * @return a [TimeRangeFilter] for filtering [Record]s.
+ *
+ * @see between for closed-ended time range.
+ * @see after for time range with open-ended [startTime]
+ */
+ @JvmStatic
+ fun after(startTime: Instant) = TimeRangeFilter(startTime = startTime)
+
+ /**
+ * Creates a [TimeRangeFilter] for a time range after the given [startTime].
+ *
+ * @param startTime start time of the filter.
+ * @return a [TimeRangeFilter] for filtering [Record]s.
+ *
+ * @see between for closed-ended time range.
+ * @see after for time range with open-ended [startTime]
+ */
+ @JvmStatic
+ fun after(startTime: LocalDateTime) = TimeRangeFilter(localStartTime = startTime)
+
+ /** Default [TimeRangeFilter] where neither start nor end time is specified, no [Record]s
+ * will be filtered. */
+ @JvmStatic
+ internal fun none(): TimeRangeFilter = TimeRangeFilter()
}
internal fun isOpenEnded(): Boolean =
(localStartTime == null || localEndTime == null) && (startTime == null || endTime == null)
-}
+}
\ No newline at end of file
diff --git a/health/health-data-client/src/main/java/androidx/health/data/client/time/package-info.java b/health/health-data-client/src/main/java/androidx/health/data/client/time/package-info.java
deleted file mode 100644
index 91d0563..0000000
--- a/health/health-data-client/src/main/java/androidx/health/data/client/time/package-info.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- */
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-package androidx.health.data.client.time;
-
-import androidx.annotation.RestrictTo;
diff --git a/health/health-data-client/src/main/proto/request.proto b/health/health-data-client/src/main/proto/request.proto
index 0c935ec..93e783a 100644
--- a/health/health-data-client/src/main/proto/request.proto
+++ b/health/health-data-client/src/main/proto/request.proto
@@ -100,7 +100,7 @@
optional TimeSpec time_spec = 1;
repeated AggregateMetricSpec metric_spec = 2;
repeated DataOrigin data_origin = 3;
- // TODO(b/219339660): Add group by here
+ optional int64 slice_duration_millis = 4;
}
message GetChangesTokenRequest {
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/impl/HealthDataClientImplTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/impl/HealthDataClientImplTest.kt
index 134f182..382fc5f 100644
--- a/health/health-data-client/src/test/java/androidx/health/data/client/impl/HealthDataClientImplTest.kt
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/impl/HealthDataClientImplTest.kt
@@ -32,7 +32,9 @@
import androidx.health.data.client.records.ActiveEnergyBurned
import androidx.health.data.client.records.Nutrition
import androidx.health.data.client.records.Steps
+import androidx.health.data.client.records.Steps.Companion.STEPS_COUNT_TOTAL
import androidx.health.data.client.records.Weight
+import androidx.health.data.client.request.AggregateGroupByDurationRequest
import androidx.health.data.client.request.AggregateRequest
import androidx.health.data.client.request.ChangesTokenRequest
import androidx.health.data.client.request.ReadRecordsRequest
@@ -59,6 +61,7 @@
import androidx.test.espresso.intent.Intents
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
import java.time.Instant
import kotlin.test.assertFailsWith
import kotlinx.coroutines.Deferred
@@ -79,17 +82,23 @@
{ insertRecords(listOf()) },
{ updateRecords(listOf()) },
{ deleteRecords(ActiveEnergyBurned::class, listOf(), listOf()) },
- { deleteRecords(ActiveEnergyBurned::class, TimeRangeFilter.empty()) },
+ { deleteRecords(ActiveEnergyBurned::class, TimeRangeFilter.none()) },
{ readRecord(Steps::class, "uid") },
{
readRecords(
ReadRecordsRequest(
- Steps::class,
- TimeRangeFilter.exact(Instant.ofEpochMilli(1234L), Instant.ofEpochMilli(1235L))
+ recordType = Steps::class,
+ timeRangeFilter = TimeRangeFilter.none(),
+ limit = 1
)
)
},
- { aggregate(AggregateRequest(setOf(), TimeRangeFilter.empty())) },
+ { aggregate(AggregateRequest(setOf(), TimeRangeFilter.none())) },
+ {
+ aggregateGroupByDuration(
+ AggregateGroupByDurationRequest(setOf(), TimeRangeFilter.none(), Duration.ZERO)
+ )
+ },
{ getChanges("token") },
{ getChangesToken(ChangesTokenRequest(recordTypes = setOf(Steps::class))) }
)
@@ -301,10 +310,10 @@
endTime = Instant.ofEpochMilli(5678L),
endZoneOffset = null,
metadata =
- Metadata(
- uid = "testUid",
- device = Device(),
- )
+ Metadata(
+ uid = "testUid",
+ device = Device(),
+ )
)
)
}
@@ -332,7 +341,7 @@
healthDataClient.readRecords(
ReadRecordsRequest(
Steps::class,
- timeRangeFilter = TimeRangeFilter.exact(endTime = Instant.ofEpochMilli(7890L)),
+ timeRangeFilter = TimeRangeFilter.before(Instant.ofEpochMilli(7890L)),
limit = 10
)
)
@@ -361,24 +370,53 @@
endTime = Instant.ofEpochMilli(5678L),
endZoneOffset = null,
metadata =
- Metadata(
- uid = "testUid",
- device = Device(),
- )
+ Metadata(
+ uid = "testUid",
+ device = Device(),
+ )
)
)
}
@Test(timeout = 10000L)
- fun deleteRecords_steps() = runTest {
+ fun deleteRecordsById_steps() = runTest {
+ val deferred = async {
+ healthDataClient.deleteRecords(Steps::class, listOf("myUid"), listOf("myClientId"))
+ }
+
+ advanceUntilIdle()
+ waitForMainLooperIdle()
+ deferred.await()
+
+ val stepsTypeProto = DataProto.DataType.newBuilder().setName("Steps")
+ assertThat(fakeAhpServiceStub.lastDeleteDataRequest?.clientIds)
+ .containsExactly(
+ RequestProto.DataTypeIdPair.newBuilder()
+ .setDataType(stepsTypeProto)
+ .setId("myClientId")
+ .build()
+ )
+ assertThat(fakeAhpServiceStub.lastDeleteDataRequest?.uids)
+ .containsExactly(
+ RequestProto.DataTypeIdPair.newBuilder()
+ .setDataType(stepsTypeProto)
+ .setId("myUid")
+ .build()
+ )
+ }
+
+ @Test(timeout = 10000L)
+ fun deleteRecordsByRange_steps() = runTest {
val deferred = async {
healthDataClient.deleteRecords(
Steps::class,
- timeRangeFilter = TimeRangeFilter.exact(endTime = Instant.ofEpochMilli(7890L)),
+ timeRangeFilter = TimeRangeFilter.before(Instant.ofEpochMilli(7890L)),
)
}
+
advanceUntilIdle()
waitForMainLooperIdle()
+
deferred.await()
assertThat(fakeAhpServiceStub.lastDeleteDataRangeRequest?.proto)
.isEqualTo(
@@ -442,7 +480,7 @@
healthDataClient.aggregate(
AggregateRequest(
setOf(Steps.STEPS_COUNT_TOTAL),
- TimeRangeFilter.exact(startTime, endTime)
+ TimeRangeFilter.between(startTime, endTime)
)
)
}
@@ -475,6 +513,59 @@
)
}
+ @Test
+ fun aggregateGroupByDuration_totalSteps() = runTest {
+ val dataOrigin = DataProto.DataOrigin.newBuilder().setApplicationId("id").build()
+ val aggregateDataRow =
+ DataProto.AggregateDataRow.newBuilder()
+ .setStartTimeEpochMs(1234)
+ .setEndTimeEpochMs(4567)
+ .setZoneOffsetSeconds(999)
+ .addDataOrigins(dataOrigin)
+ .putLongValues("Steps_count_total", 1000)
+ .build()
+ fakeAhpServiceStub.aggregateDataResponse =
+ AggregateDataResponse(
+ ResponseProto.AggregateDataResponse.newBuilder().addRows(aggregateDataRow).build()
+ )
+ val deferred = async {
+ val startTime = Instant.ofEpochMilli(1234)
+ val endTime = Instant.ofEpochMilli(4567)
+ healthDataClient.aggregate(
+ AggregateRequest(
+ setOf(STEPS_COUNT_TOTAL),
+ TimeRangeFilter.between(startTime, endTime)
+ )
+ )
+ }
+
+ advanceUntilIdle()
+ waitForMainLooperIdle()
+
+ val response: AggregateDataRow = deferred.await()
+ assertThat(response.hasMetric(STEPS_COUNT_TOTAL)).isTrue()
+ assertThat(response.getMetric(STEPS_COUNT_TOTAL)).isEqualTo(1000)
+ assertThat(response.dataOrigins).contains(DataOrigin("id"))
+ assertThat(fakeAhpServiceStub.lastAggregateRequest?.proto)
+ .isEqualTo(
+ RequestProto.AggregateDataRequest.newBuilder()
+ .setTimeSpec(
+ TimeProto.TimeSpec.newBuilder()
+ .setStartTimeEpochMs(1234)
+ .setEndTimeEpochMs(4567)
+ .build()
+ )
+ .addMetricSpec(
+ RequestProto.AggregateMetricSpec.newBuilder()
+ .setDataTypeName("Steps")
+ .setAggregationType("total")
+ .setFieldName("count")
+ .build()
+ )
+ .build()
+ )
+ }
+
@Test(timeout = 10000L)
fun getChangesToken() = runTest {
fakeAhpServiceStub.changesTokenResponse =
@@ -544,27 +635,6 @@
)
}
- @Test(timeout = 10000L)
- fun deleteRecordsById_steps() = runTest {
- val deferred = async {
- healthDataClient.deleteRecords(Steps::class, listOf("myUid"), listOf("myClientId"))
- }
-
- advanceUntilIdle()
- waitForMainLooperIdle()
- deferred.await()
-
- val stepsTypeProto = DataProto.DataType.newBuilder().setName("Steps")
- assertThat(fakeAhpServiceStub.lastDeleteDataRequest?.clientIds).containsExactly(
- RequestProto.DataTypeIdPair.newBuilder()
- .setDataType(stepsTypeProto).setId("myClientId").build()
- )
- assertThat(fakeAhpServiceStub.lastDeleteDataRequest?.uids).containsExactly(
- RequestProto.DataTypeIdPair.newBuilder()
- .setDataType(stepsTypeProto).setId("myUid").build()
- )
- }
-
private fun waitForMainLooperIdle() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
}
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/aggregate/AggregateDataRowConverterTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/aggregate/AggregateDataRowConverterTest.kt
new file mode 100644
index 0000000..c05f0d7
--- /dev/null
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/aggregate/AggregateDataRowConverterTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.impl.converters.aggregate
+
+import androidx.health.data.client.aggregate.AggregateDataRow
+import androidx.health.data.client.aggregate.AggregateDataRowGroupByDuration
+import androidx.health.data.client.metadata.DataOrigin
+import androidx.health.platform.client.proto.DataProto
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.time.Instant
+import java.time.ZoneOffset
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AggregateDataRowConverterTest {
+ @Test
+ fun retrieveAggregateDataRow() {
+ val proto =
+ DataProto.AggregateDataRow.newBuilder()
+ .addDataOrigins(DataProto.DataOrigin.newBuilder().setApplicationId("testApp"))
+ .putDoubleValues("doubleKey", 123.4)
+ .putLongValues("longKey", 567)
+ .build()
+
+ proto
+ .retrieveAggregateDataRow()
+ .assertEquals(
+ AggregateDataRow(
+ longValues = mapOf(Pair("longKey", 567L)),
+ doubleValues = mapOf(Pair("doubleKey", 123.4)),
+ dataOrigins = listOf(DataOrigin("testApp")),
+ )
+ )
+ }
+
+ @Test
+ // ZoneOffset.ofTotalSeconds() has been banned but safe here for serialization.
+ @SuppressWarnings("GoodTime")
+ fun toAggregateDataRowGroupByDuration() {
+ val proto =
+ DataProto.AggregateDataRow.newBuilder()
+ .addDataOrigins(DataProto.DataOrigin.newBuilder().setApplicationId("testApp"))
+ .putDoubleValues("doubleKey", 123.4)
+ .putLongValues("longKey", 567)
+ .setStartTimeEpochMs(1111)
+ .setEndTimeEpochMs(9999)
+ .setZoneOffsetSeconds(123)
+ .build()
+
+ proto
+ .toAggregateDataRowGroupByDuration()
+ .assertEquals(
+ AggregateDataRowGroupByDuration(
+ data =
+ AggregateDataRow(
+ longValues = mapOf(Pair("longKey", 567L)),
+ doubleValues = mapOf(Pair("doubleKey", 123.4)),
+ dataOrigins = listOf(DataOrigin("testApp")),
+ ),
+ startTime = Instant.ofEpochMilli(1111),
+ endTime = Instant.ofEpochMilli(9999),
+ zoneOffset = ZoneOffset.ofTotalSeconds(123),
+ )
+ )
+ }
+
+ @Test
+ fun toAggregateDataRowGroupByDuration_startOrEndTimeNotSet_throws() {
+ val proto =
+ DataProto.AggregateDataRow.newBuilder()
+ .addDataOrigins(DataProto.DataOrigin.newBuilder().setApplicationId("testApp"))
+ .putDoubleValues("doubleKey", 123.4)
+ .putLongValues("longKey", 567)
+ .setStartTimeEpochMs(1111)
+ .setEndTimeEpochMs(9999)
+ .setZoneOffsetSeconds(123)
+ .build()
+
+ var thrown =
+ assertThrows(IllegalArgumentException::class.java) {
+ proto
+ .toBuilder()
+ .clearStartTimeEpochMs()
+ .build()
+ .toAggregateDataRowGroupByDuration()
+ }
+ assertThat(thrown.message).isEqualTo("start time must be set")
+ thrown =
+ assertThrows(IllegalArgumentException::class.java) {
+ proto.toBuilder().clearEndTimeEpochMs().build().toAggregateDataRowGroupByDuration()
+ }
+ assertThat(thrown.message).isEqualTo("end time must be set")
+ }
+
+ private fun AggregateDataRow.assertEquals(expected: AggregateDataRow) {
+ assertThat(longValues).isEqualTo(expected.longValues)
+ assertThat(doubleValues).isEqualTo(expected.doubleValues)
+ assertThat(dataOrigins).isEqualTo(expected.dataOrigins)
+ }
+
+ // ZoneOffset.ofTotalSeconds() has been banned but safe here for serialization.
+ @SuppressWarnings("GoodTime")
+ private fun AggregateDataRowGroupByDuration.assertEquals(
+ expected: AggregateDataRowGroupByDuration,
+ ) {
+ data.assertEquals(expected.data)
+ assertThat(startTime.toEpochMilli()).isEqualTo(1111)
+ assertThat(endTime.toEpochMilli()).isEqualTo(9999)
+ assertThat(zoneOffset.totalSeconds).isEqualTo(123)
+ }
+}
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/aggregate/AggregateMetricConverterTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/aggregate/AggregateMetricConverterTest.kt
new file mode 100644
index 0000000..0096887
--- /dev/null
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/aggregate/AggregateMetricConverterTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.impl.converters.aggregate
+
+import androidx.health.data.client.aggregate.DurationAggregateMetric
+import androidx.health.data.client.records.Distance
+import androidx.health.data.client.records.Steps
+import androidx.health.platform.client.proto.RequestProto
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AggregateMetricConverterTest {
+ @Test
+ fun aggregateMetric_toProto() {
+ assertThat(Steps.STEPS_COUNT_TOTAL.toProto())
+ .isEqualTo(
+ RequestProto.AggregateMetricSpec.newBuilder()
+ .setDataTypeName("Steps")
+ .setAggregationType("total")
+ .setFieldName("count")
+ .build()
+ )
+ assertThat(Distance.DISTANCE_TOTAL.toProto())
+ .isEqualTo(
+ RequestProto.AggregateMetricSpec.newBuilder()
+ .setDataTypeName("Distance")
+ .setAggregationType("total")
+ .setFieldName("distance")
+ .build()
+ )
+ // TODO(b/227996244): Use active time when the metric is created
+ assertThat(DurationAggregateMetric("ActiveTime", "total").toProto())
+ .isEqualTo(
+ RequestProto.AggregateMetricSpec.newBuilder()
+ .setDataTypeName("ActiveTime")
+ .setAggregationType("total")
+ .build()
+ )
+ }
+}
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/records/AllRecordsConverterTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/records/AllRecordsConverterTest.kt
index 130e5ae..451f355 100644
--- a/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -725,6 +725,7 @@
vitaminKGrams = 1.0,
zincGrams = 1.0,
mealType = null,
+ name = null,
startTime = START_TIME,
startZoneOffset = START_ZONE_OFFSET,
endTime = END_TIME,
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/request/AggregateRequestConverterTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/request/AggregateRequestConverterTest.kt
new file mode 100644
index 0000000..3e1e12d
--- /dev/null
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/impl/converters/request/AggregateRequestConverterTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.impl.converters.request
+
+import androidx.health.data.client.impl.converters.time.toProto
+import androidx.health.data.client.metadata.DataOrigin
+import androidx.health.data.client.records.Steps
+import androidx.health.data.client.request.AggregateGroupByDurationRequest
+import androidx.health.data.client.request.AggregateRequest
+import androidx.health.data.client.time.TimeRangeFilter
+import androidx.health.platform.client.proto.DataProto
+import androidx.health.platform.client.proto.RequestProto
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.time.Instant
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val METRIC = Steps.STEPS_COUNT_TOTAL
+private val METRIC_PROTO =
+ RequestProto.AggregateMetricSpec.newBuilder()
+ .setDataTypeName("Steps")
+ .setAggregationType("total")
+ .setFieldName("count")
+private val TIME_RANGE_FILTER =
+ TimeRangeFilter.between(Instant.ofEpochMilli(123), Instant.ofEpochMilli(456))
+private val DATA_ORIGIN_FILTER = listOf(DataOrigin("testAppName"))
+
+@RunWith(AndroidJUnit4::class)
+class AggregateRequestConverterTest {
+ @Test
+ fun aggregateRequestToProto() {
+ val request =
+ AggregateRequest(
+ metrics = setOf(METRIC),
+ timeRangeFilter = TIME_RANGE_FILTER,
+ dataOriginFilter = DATA_ORIGIN_FILTER
+ )
+
+ assertThat(request.toProto())
+ .isEqualTo(
+ RequestProto.AggregateDataRequest.newBuilder()
+ .addMetricSpec(METRIC_PROTO)
+ .addAllDataOrigin(DATA_ORIGIN_FILTER.toProtoList())
+ .setTimeSpec(TIME_RANGE_FILTER.toProto())
+ .build()
+ )
+ }
+
+ @Test
+ fun aggregateGroupByDurationRequestToProto() {
+ val request =
+ AggregateGroupByDurationRequest(
+ metrics = setOf(METRIC),
+ timeRangeFilter = TIME_RANGE_FILTER,
+ timeRangeSlicer = Duration.ofMillis(98765),
+ dataOriginFilter = DATA_ORIGIN_FILTER
+ )
+
+ assertThat(request.toProto())
+ .isEqualTo(
+ RequestProto.AggregateDataRequest.newBuilder()
+ .addMetricSpec(METRIC_PROTO)
+ .addAllDataOrigin(DATA_ORIGIN_FILTER.toProtoList())
+ .setTimeSpec(TIME_RANGE_FILTER.toProto())
+ .setSliceDurationMillis(98765)
+ .build()
+ )
+ }
+
+ private fun List<DataOrigin>.toProtoList() =
+ this.map { DataProto.DataOrigin.newBuilder().setApplicationId(it.packageName).build() }
+}
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/request/ReadRecordsRequestTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/request/ReadRecordsRequestTest.kt
index 62b034d3..55fa8cb 100644
--- a/health/health-data-client/src/test/java/androidx/health/data/client/request/ReadRecordsRequestTest.kt
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/request/ReadRecordsRequestTest.kt
@@ -28,7 +28,7 @@
class ReadRecordsRequestTest {
private val closedTimeRange =
- TimeRangeFilter.exact(Instant.ofEpochMilli(1234L), Instant.ofEpochMilli(1235L))
+ TimeRangeFilter.between(Instant.ofEpochMilli(1234L), Instant.ofEpochMilli(1235L))
@Test
fun limitAndSizeTogether_throws() {
@@ -36,7 +36,7 @@
assertThrows(IllegalArgumentException::class.java) {
ReadRecordsRequest(
recordType = Steps::class,
- timeRangeFilter = TimeRangeFilter.empty(),
+ timeRangeFilter = TimeRangeFilter.none(),
limit = 10,
pageSize = 10
)
@@ -50,7 +50,7 @@
assertThrows(IllegalArgumentException::class.java) {
ReadRecordsRequest(
recordType = Steps::class,
- timeRangeFilter = TimeRangeFilter.empty()
+ timeRangeFilter = TimeRangeFilter.none()
)
}
assertEquals(
@@ -63,7 +63,7 @@
fun openEndedTimeRange_withLimit_success() {
ReadRecordsRequest(
recordType = Steps::class,
- timeRangeFilter = TimeRangeFilter.empty(),
+ timeRangeFilter = TimeRangeFilter.none(),
limit = 10
)
}
@@ -72,7 +72,7 @@
fun openEndedTimeRange_withPageSize_success() {
ReadRecordsRequest(
recordType = Steps::class,
- timeRangeFilter = TimeRangeFilter.empty(),
+ timeRangeFilter = TimeRangeFilter.none(),
pageSize = 10
)
}
diff --git a/health/health-data-client/src/test/java/androidx/health/data/client/time/TimeRangeFilterTest.kt b/health/health-data-client/src/test/java/androidx/health/data/client/time/TimeRangeFilterTest.kt
new file mode 100644
index 0000000..893e039
--- /dev/null
+++ b/health/health-data-client/src/test/java/androidx/health/data/client/time/TimeRangeFilterTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.health.data.client.time
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import java.time.Instant
+import java.time.LocalDateTime
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TimeRangeFilterTest {
+
+ @Test
+ fun checksStartTimeBeforeEndTime() {
+ assertFailsWith<IllegalArgumentException> {
+ TimeRangeFilter.between(
+ endTime = Instant.ofEpochMilli(1234L),
+ startTime = Instant.ofEpochMilli(5679L),
+ )
+ }
+ assertFailsWith<IllegalArgumentException> {
+ TimeRangeFilter.between(
+ startTime = Instant.ofEpochMilli(1234L),
+ endTime = Instant.ofEpochMilli(1234L),
+ )
+ }
+ TimeRangeFilter.between(
+ startTime = Instant.ofEpochMilli(1234L),
+ endTime = Instant.ofEpochMilli(5679L),
+ )
+ }
+
+ @Test
+ fun checksLocalStartTimeBeforeEndTime() {
+ assertFailsWith<IllegalArgumentException> {
+ TimeRangeFilter.between(
+ startTime = LocalDateTime.parse("2021-02-01T02:00:00"),
+ endTime = LocalDateTime.parse("2021-02-01T01:00:00")
+ )
+ }
+ assertFailsWith<IllegalArgumentException> {
+ TimeRangeFilter.between(
+ startTime = LocalDateTime.parse("2021-02-01T02:00:00"),
+ endTime = LocalDateTime.parse("2021-02-01T02:00:00")
+ )
+ }
+ TimeRangeFilter.between(
+ startTime = LocalDateTime.parse("2021-02-01T01:00:00"),
+ endTime = LocalDateTime.parse("2021-02-01T02:00:00")
+ )
+ }
+}
diff --git a/libraryversions.toml b/libraryversions.toml
index d321fe6..536f208 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -23,8 +23,8 @@
CONTENTPAGER = "1.1.0-alpha01"
COORDINATORLAYOUT = "1.3.0-alpha01"
CORE = "1.8.0-alpha08"
-CORE_ANIMATION = "1.0.0-alpha03"
-CORE_ANIMATION_TESTING = "1.0.0-alpha03"
+CORE_ANIMATION = "1.0.0-beta01"
+CORE_ANIMATION_TESTING = "1.0.0-beta01"
CORE_APPDIGEST = "1.0.0-alpha01"
CORE_GOOGLE_SHORTCUTS = "1.1.0-alpha02"
CORE_I18N = "1.0.0-alpha01"
@@ -38,7 +38,7 @@
CUSTOMVIEW_POOLINGCONTAINER = "1.0.0-alpha02"
DATASTORE = "1.1.0-alpha01"
DOCUMENTFILE = "1.1.0-alpha02"
-DRAGANDDROP = "1.0.0-beta02"
+DRAGANDDROP = "1.0.0-rc01"
DRAWERLAYOUT = "1.2.0-alpha01"
DYNAMICANIMATION = "1.1.0-alpha04"
DYNAMICANIMATION_KTX = "1.0.0-alpha04"
@@ -109,13 +109,13 @@
TESTSCREENSHOT = "1.0.0-alpha01"
TEXT = "1.0.0-alpha01"
TEXTCLASSIFIER = "1.0.0-alpha05"
-TRACING = "1.1.0-beta03"
+TRACING = "1.1.0-rc01"
TRACING_PERFETTO = "1.0.0-alpha01"
TRANSITION = "1.5.0-alpha01"
TVPROVIDER = "1.1.0-alpha02"
-VECTORDRAWABLE = "1.2.0-alpha03"
-VECTORDRAWABLE_ANIMATED = "1.2.0-alpha01"
-VECTORDRAWABLE_SEEKABLE = "1.0.0-alpha03"
+VECTORDRAWABLE = "1.2.0-beta01"
+VECTORDRAWABLE_ANIMATED = "1.2.0-beta01"
+VECTORDRAWABLE_SEEKABLE = "1.0.0-beta01"
VERSIONED_PARCELABLE = "1.2.0-alpha01"
VIEWPAGER = "1.1.0-alpha02"
VIEWPAGER2 = "1.1.0-beta02"
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
index 65b6666..8a4ed64 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
@@ -14,6 +14,8 @@
public final class SavedStateHandleSaverKt {
method @androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi public static <T> T saveable(androidx.lifecycle.SavedStateHandle, String key, optional androidx.compose.runtime.saveable.Saver<T,?> saver, kotlin.jvm.functions.Function0<? extends T> init);
method @androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi public static <T> androidx.compose.runtime.MutableState<T> saveable(androidx.lifecycle.SavedStateHandle, String key, androidx.compose.runtime.saveable.Saver<T,?> stateSaver, kotlin.jvm.functions.Function0<? extends androidx.compose.runtime.MutableState<T>> init);
+ method @androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi public static <T> kotlin.properties.PropertyDelegateProvider<java.lang.Object,kotlin.properties.ReadOnlyProperty<java.lang.Object,T>> saveable(androidx.lifecycle.SavedStateHandle, optional androidx.compose.runtime.saveable.Saver<T,?> saver, kotlin.jvm.functions.Function0<? extends T> init);
+ method @androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi public static <T, M extends androidx.compose.runtime.MutableState<T>> kotlin.properties.PropertyDelegateProvider<java.lang.Object,kotlin.properties.ReadWriteProperty<java.lang.Object,T>> saveableMutableState(androidx.lifecycle.SavedStateHandle, optional androidx.compose.runtime.saveable.Saver<T,?> stateSaver, kotlin.jvm.functions.Function0<? extends M> init);
}
public final class ViewModelKt {
diff --git a/lifecycle/lifecycle-viewmodel-compose/samples/src/main/java/androidx/lifecycle/viewmodel/compose/samples/LifecycleViewModelSamples.kt b/lifecycle/lifecycle-viewmodel-compose/samples/src/main/java/androidx/lifecycle/viewmodel/compose/samples/LifecycleViewModelSamples.kt
index 8218847..750d2ff 100644
--- a/lifecycle/lifecycle-viewmodel-compose/samples/src/main/java/androidx/lifecycle/viewmodel/compose/samples/LifecycleViewModelSamples.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/samples/src/main/java/androidx/lifecycle/viewmodel/compose/samples/LifecycleViewModelSamples.kt
@@ -179,3 +179,89 @@
}
}
}
+
+@Sampled
+fun SnapshotStateViewModelWithDelegates() {
+
+ /**
+ * A simple item that is not inherently [Parcelable]
+ */
+ data class Item(
+ val id: UUID,
+ val value: String
+ )
+
+ @OptIn(SavedStateHandleSaveableApi::class)
+ class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {
+
+ /**
+ * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
+ * The size of this set must remain small in expectation, since the maximum size of saved
+ * instance state space is limited.
+ */
+ private val items: MutableList<Item> by handle.saveable(
+ saver = listSaver(
+ save = {
+ it.map { item ->
+ listOf(item.id.toString(), item.value)
+ }
+ },
+ restore = {
+ it.map { saved ->
+ Item(
+ id = UUID.fromString(saved[0]),
+ value = saved[1]
+ )
+ }.toMutableStateList()
+ }
+ )
+ ) {
+ mutableStateListOf()
+ }
+
+ /**
+ * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
+ * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys.
+ * The size of this set must remain small in expectation, since the maximum size of saved
+ * instance state space is limited.
+ */
+ private val selectedItemIds: MutableMap<UUID, Unit> by handle.saveable(
+ saver = listSaver(
+ save = { it.keys.map(UUID::toString) },
+ restore = { it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap() }
+ )
+ ) {
+ mutableStateMapOf()
+ }
+
+ /**
+ * A snapshot-backed flag representing where selections are enabled, persisted by the
+ * [SavedStateHandle].
+ */
+ var areSelectionsEnabled by handle.saveable { mutableStateOf(true) }
+
+ /**
+ * A list of items paired with a selection state.
+ */
+ val selectedItems: List<Pair<Item, Boolean>> get() =
+ items.map { it to (it.id in selectedItemIds) }
+
+ /**
+ * Updates the selection state for the item with [id] to [selected].
+ */
+ fun selectItem(id: UUID, selected: Boolean) {
+ if (selected) {
+ selectedItemIds[id] = Unit
+ } else {
+ selectedItemIds.remove(id)
+ }
+ }
+
+ /**
+ * Adds an item with the given [value].
+ */
+ fun addItem(value: String) {
+ items.add(Item(UUID.randomUUID(), value))
+ }
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt
index fe1f757..0dc453e 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt
@@ -166,6 +166,96 @@
assertThat((state as SnapshotMutableState).policy)
.isEqualTo(referentialEqualityPolicy<CustomState>())
}
+
+ @OptIn(SavedStateHandleSaveableApi::class)
+ @Test
+ fun delegate_simpleRestore() {
+ var savedStateHandle: SavedStateHandle? = null
+ var array: IntArray? = null
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val viewModel = viewModel<SavingTestViewModel>(activity)
+ savedStateHandle = viewModel.savedStateHandle
+ val arrayProperty: IntArray by viewModel.savedStateHandle.saveable<IntArray> {
+ intArrayOf(0)
+ }
+ array = arrayProperty
+ }
+ }
+
+ assertThat(array).isEqualTo(intArrayOf(0))
+ assertThat(savedStateHandle?.keys()).isEqualTo(setOf("arrayProperty"))
+
+ activityTestRuleScenario.scenario.onActivity {
+ array!![0] = 1
+ // we null both to ensure recomposition happened
+ array = null
+ savedStateHandle = null
+ }
+
+ activityTestRuleScenario.scenario.recreate()
+
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val viewModel = viewModel<SavingTestViewModel>(activity)
+ savedStateHandle = viewModel.savedStateHandle
+ val arrayProperty: IntArray by viewModel.savedStateHandle.saveable<IntArray> {
+ intArrayOf(0)
+ }
+ array = arrayProperty
+ }
+ }
+
+ assertThat(array).isEqualTo(intArrayOf(1))
+ assertThat(savedStateHandle?.keys()).isEqualTo(setOf("arrayProperty"))
+ }
+
+ @OptIn(SavedStateHandleSaveableApi::class)
+ @Test
+ fun mutableState_delegate_simpleRestore() {
+ var savedStateHandle: SavedStateHandle? = null
+ var getCount: (() -> Int)? = null
+ var setCount: ((Int) -> Unit)? = null
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val viewModel = viewModel<SavingTestViewModel>(activity)
+ savedStateHandle = viewModel.savedStateHandle
+ var count by viewModel.savedStateHandle.saveable {
+ mutableStateOf(0)
+ }
+ getCount = { count }
+ setCount = { count = it }
+ }
+ }
+
+ assertThat(getCount!!()).isEqualTo(0)
+ assertThat(savedStateHandle?.keys()).isEqualTo(setOf("count"))
+
+ activityTestRuleScenario.scenario.onActivity {
+ setCount!!(1)
+ // we null all to ensure recomposition happened
+ getCount = null
+ setCount = null
+ savedStateHandle = null
+ }
+
+ activityTestRuleScenario.scenario.recreate()
+
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val viewModel = viewModel<SavingTestViewModel>(activity)
+ savedStateHandle = viewModel.savedStateHandle
+ var count by viewModel.savedStateHandle.saveable {
+ mutableStateOf(0)
+ }
+ getCount = { count }
+ setCount = { count = it }
+ }
+ }
+
+ assertThat(getCount!!()).isEqualTo(1)
+ assertThat(savedStateHandle?.keys()).isEqualTo(setOf("count"))
+ }
}
class SavingTestViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
index fbd860e..0df553a 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
@@ -33,7 +33,7 @@
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -68,7 +68,7 @@
dialog.window?.decorView?.run {
// Specifically only set the LifecycleOwner and SavedStateRegistryOwner
ViewTreeLifecycleOwner.set(this, lifecycleOwner)
- ViewTreeSavedStateRegistryOwner.set(this, savedStateRegistryOwner)
+ setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
}
onDispose {
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt b/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt
index a545608..51e909e 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt
@@ -19,14 +19,20 @@
import android.os.Bundle
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SnapshotMutationPolicy
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotMutableState
import androidx.core.os.bundleOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.SavedStateHandle.Companion.validateValue
+import kotlin.properties.PropertyDelegateProvider
+import kotlin.properties.ReadOnlyProperty
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
/**
* Inter-opt between [SavedStateHandle] and [Saver] so that any state holder that is
@@ -90,6 +96,82 @@
)
/**
+ * Inter-opt between [SavedStateHandle] and [Saver] so that any state holder that is
+ * being saved via [rememberSaveable] with a custom [Saver] can also be saved with
+ * [SavedStateHandle].
+ *
+ * The key is automatically retrieved as the name of the property this delegate is being used
+ * to create.
+ *
+ * The returned state [T] should be the only way that a value is saved or restored from the
+ * [SavedStateHandle] with the automatic key.
+ *
+ * Using the same key again with another [SavedStateHandle] method is not supported, as values
+ * won't cross-set or communicate updates.
+ *
+ * @sample androidx.lifecycle.viewmodel.compose.samples.SnapshotStateViewModelWithDelegates
+ */
+@SavedStateHandleSaveableApi
+fun <T : Any> SavedStateHandle.saveable(
+ saver: Saver<T, out Any> = autoSaver(),
+ init: () -> T,
+): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
+ PropertyDelegateProvider { _, property ->
+ val value = saveable(
+ key = property.name,
+ saver = saver,
+ init = init
+ )
+
+ ReadOnlyProperty { _, _ -> value }
+ }
+
+/**
+ * Inter-opt between [SavedStateHandle] and [Saver] so that any state holder that is
+ * being saved via [rememberSaveable] with a custom [Saver] can also be saved with
+ * [SavedStateHandle].
+ *
+ * The key is automatically retrieved as the name of the property this delegate is being used
+ * to create.
+ *
+ * The delegated [MutableState] should be the only way that a value is saved or restored from the
+ * [SavedStateHandle] with the automatic key.
+ *
+ * Using the same key again with another [SavedStateHandle] method is not supported, as values
+ * won't cross-set or communicate updates.
+ *
+ * Use this overload to allow delegating to a mutable state just like you can with
+ * `rememberSaveable`:
+ * ```
+ * var value by savedStateHandle.saveable { mutableStateOf("initialValue") }
+ * ```
+ *
+ * @sample androidx.lifecycle.viewmodel.compose.samples.SnapshotStateViewModelWithDelegates
+ */
+@SavedStateHandleSaveableApi
+@JvmName("saveableMutableState")
+fun <T : Any, M : MutableState<T>> SavedStateHandle.saveable(
+ stateSaver: Saver<T, out Any> = autoSaver(),
+ init: () -> M,
+): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> =
+ PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> { _, property ->
+ val mutableState = saveable(
+ key = property.name,
+ stateSaver = stateSaver,
+ init = init
+ )
+
+ // Create a property that delegates to the mutableState
+ object : ReadWriteProperty<Any?, T> {
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T =
+ mutableState.getValue(thisRef, property)
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =
+ mutableState.setValue(thisRef, property, value)
+ }
+ }
+
+/**
* Copied from RememberSaveable.kt
*/
@Suppress("UNCHECKED_CAST")
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerViewModel.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerViewModel.java
index 5443931..1fe8c8a 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerViewModel.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerViewModel.java
@@ -48,8 +48,10 @@
@SuppressLint("RestrictedApi")
private void createDb() {
- mDatabase = Room.databaseBuilder(this.getApplication(),
- SampleDatabase.class, "customerDatabase").build();
+ mDatabase = Room.databaseBuilder(this.getApplication(), SampleDatabase.class,
+ "customerDatabase")
+ .fallbackToDestructiveMigration()
+ .build();
ArchTaskExecutor.getInstance().executeOnDiskIO(() -> {
// fill with some simple data
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt
similarity index 61%
copy from compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
copy to paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt
index 8f78d81..e593fab 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,9 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package androidx.paging.integration.testapp.room
-package androidx.compose.foundation.lazy
+import androidx.room.Entity
+import androidx.room.PrimaryKey
-internal actual fun getDefaultLazyKeyFor(index: Int): Any = DefaultLazyKey(index)
-
-private data class DefaultLazyKey(private val index: Int)
+/**
+ * Sample entity to persist remote key for use in RemoteMediator.
+ */
+@Entity(tableName = "remote_key")
+class RemoteKey(val prevKey: Int, val nextKey: Int) {
+ @PrimaryKey
+ var id = 0
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt
new file mode 100644
index 0000000..1b46925
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.paging.integration.testapp.room
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy.Companion.REPLACE
+import androidx.room.Query
+
+/**
+ * Simple Customer DAO for Room Customer list sample.
+ */
+@Dao
+interface RemoteKeyDao {
+ /**
+ * Insert a RemoteKey
+ *
+ * @param remoteKey
+ */
+ @Insert(onConflict = REPLACE)
+ suspend fun insert(remoteKey: RemoteKey)
+
+ /**
+ * Clears the RemoteKey
+ */
+ @Query("DELETE FROM remote_key")
+ fun delete()
+
+ /**
+ * @return Latest persisted RemoteKey
+ */
+ @Query("SELECT * FROM remote_key LIMIT 1")
+ suspend fun queryRemoteKey(): RemoteKey?
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/SampleDatabase.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/SampleDatabase.java
index 7dac100..1686443 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/SampleDatabase.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/SampleDatabase.java
@@ -16,16 +16,24 @@
package androidx.paging.integration.testapp.room;
+import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.RoomDatabase;
/**
* Sample database of customers.
*/
-@Database(entities = {Customer.class}, version = 1, exportSchema = false)
+@Database(entities = {Customer.class, RemoteKey.class}, version = 1, exportSchema = false)
public abstract class SampleDatabase extends RoomDatabase {
/**
* @return customer dao.
*/
+ @NonNull
public abstract CustomerDao getCustomerDao();
+
+ /**
+ * @return RemoteKeyDao
+ */
+ @NonNull
+ public abstract RemoteKeyDao getRemoteKeyDao();
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
index fa3204e..490e234 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
@@ -22,6 +22,7 @@
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.paging.integration.testapp.room.Customer
+import androidx.paging.integration.testapp.room.RemoteKey
import androidx.paging.integration.testapp.room.SampleDatabase
import androidx.room.withTransaction
import kotlinx.coroutines.Dispatchers
@@ -49,6 +50,13 @@
return MediatorResult.Success(endOfPaginationReached = true)
}
+ // Fetch latest remote key from db. We cannot rely on PagingState because the
+ // invalidate + load loop in paging may race with the actual load + insert happening in
+ // RemoteMediator.
+ val remoteKey = withContext(Dispatchers.IO) {
+ database.remoteKeyDao.queryRemoteKey() ?: RemoteKey(-1, 0)
+ }
+
// TODO: Move this to be a more fully featured sample which demonstrated key translation
// between two types of PagingSources where the keys do not map 1:1.
val loadParams = when (loadType) {
@@ -59,7 +67,7 @@
)
LoadType.PREPEND -> throw IllegalStateException()
LoadType.APPEND -> PagingSource.LoadParams.Append(
- key = state.pages.lastOrNull()?.nextKey ?: 0,
+ key = remoteKey.nextKey,
loadSize = 10,
placeholdersEnabled = false
)
@@ -70,9 +78,13 @@
withContext(Dispatchers.IO) {
database.withTransaction {
if (loadType == LoadType.REFRESH) {
+ database.remoteKeyDao.delete()
database.customerDao.removeAll()
}
+ database.remoteKeyDao.insert(
+ RemoteKey(remoteKey.prevKey, remoteKey.nextKey + result.data.size)
+ )
database.customerDao.insertAll(result.data.toTypedArray())
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
index 9a3718d..5c202ee 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
@@ -31,8 +31,8 @@
import androidx.paging.integration.testapp.room.Customer
import androidx.paging.integration.testapp.room.SampleDatabase
import androidx.room.Room
-import kotlinx.coroutines.flow.map
import java.util.UUID
+import kotlinx.coroutines.flow.map
class V3RoomViewModel(application: Application) : AndroidViewModel(application) {
val database = Room.databaseBuilder(
@@ -56,8 +56,12 @@
@SuppressLint("RestrictedApi")
internal fun clearAllCustomers() {
- ArchTaskExecutor.getInstance()
- .executeOnDiskIO { database.customerDao.removeAll() }
+ ArchTaskExecutor.getInstance().executeOnDiskIO {
+ database.runInTransaction {
+ database.remoteKeyDao.delete()
+ database.customerDao.removeAll()
+ }
+ }
}
@OptIn(ExperimentalPagingApi::class)
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index dcb27a1..2fa2839 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,7 +25,7 @@
kotlin.code.style=official
# Disable docs
androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=8382462
+androidx.playground.snapshotBuildId=8413179
androidx.playground.metalavaBuildId=8385786
androidx.playground.dokkaBuildId=7472101
androidx.studio.type=playground
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
index aed0202..943644b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
@@ -18,7 +18,15 @@
import androidx.room.compiler.processing.XNullability
import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.util.Types
+import kotlin.coroutines.Continuation
private val NONNULL_ANNOTATIONS = arrayOf(
"androidx.annotation.NonNull",
@@ -59,4 +67,85 @@
} else {
null
}
+}
+
+/**
+ * Tests whether one suspend function, as a member of a given types, overrides another suspend
+ * function.
+ *
+ * This method assumes function one and two are suspend methods, i.e. they both return Object,
+ * have at least one parameter and the last parameter is of type Continuation. This method is
+ * similar to MoreElements.overrides() but doesn't check isSubsignature() due to Continuation's
+ * type arg being covariant, instead the equivalent is done by checking each parameter explicitly.
+ */
+internal fun suspendOverrides(
+ overrider: ExecutableElement,
+ overridden: ExecutableElement,
+ owner: TypeElement,
+ typeUtils: Types
+): Boolean {
+ if (overrider.simpleName != overridden.simpleName) {
+ return false
+ }
+ if (overrider.enclosingElement == overridden.enclosingElement) {
+ return false
+ }
+ if (overridden.modifiers.contains(Modifier.STATIC)) {
+ return false
+ }
+ if (overridden.modifiers.contains(Modifier.PRIVATE)) {
+ return false
+ }
+ val overriddenType = overridden.enclosingElement as? TypeElement ?: return false
+ if (!typeUtils.isSubtype(
+ typeUtils.erasure(owner.asType()),
+ typeUtils.erasure(overriddenType.asType()))
+ ) {
+ return false
+ }
+ val ownerType = MoreTypes.asDeclared(owner.asType())
+ val overriderExecutable = MoreTypes.asExecutable(typeUtils.asMemberOf(ownerType, overrider))
+ val overriddenExecutable = MoreTypes.asExecutable(typeUtils.asMemberOf(ownerType, overrider))
+ if (overriderExecutable.parameterTypes.size != overriddenExecutable.parameterTypes.size) {
+ return false
+ }
+ val continuationTypeName = TypeName.get(Continuation::class.java)
+ val overriderLastParamTypeName =
+ (TypeName.get(overriderExecutable.parameterTypes.last()) as? ParameterizedTypeName)
+ ?.rawType
+ check(overriderLastParamTypeName == continuationTypeName) {
+ "Expected $overriderLastParamTypeName to be $continuationTypeName"
+ }
+ val overriddenLastParamTypeName =
+ (TypeName.get(overriddenExecutable.parameterTypes.last()) as? ParameterizedTypeName)
+ ?.rawType
+ check(overriddenLastParamTypeName == continuationTypeName) {
+ "Expected $overriddenLastParamTypeName to be $continuationTypeName"
+ }
+ val overriderContinuationTypeArg =
+ MoreTypes.asDeclared(overriderExecutable.parameterTypes.last())
+ .typeArguments.single().extendsBound()
+ val overriddenContinuationTypeArg =
+ MoreTypes.asDeclared(overriderExecutable.parameterTypes.last())
+ .typeArguments.single().extendsBound()
+ if (!typeUtils.isSameType(
+ typeUtils.erasure(overriderContinuationTypeArg),
+ typeUtils.erasure(overriddenContinuationTypeArg))
+ ) {
+ return false
+ }
+ if (overriddenExecutable.parameterTypes.size >= 2) {
+ overriderExecutable.parameterTypes.zip(overriddenExecutable.parameterTypes)
+ .dropLast(1)
+ .forEach { (overriderParam, overriddenParam) ->
+ if (!typeUtils.isSameType(
+ typeUtils.erasure(overriderParam),
+ typeUtils.erasure(overriddenParam)
+ )
+ ) {
+ return false
+ }
+ }
+ }
+ return true
}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt
index baa51d1..167315a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt
@@ -18,6 +18,7 @@
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XMethodType
+import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.javac.kotlin.KmFunction
@@ -121,6 +122,14 @@
override fun overrides(other: XMethodElement, owner: XTypeElement): Boolean {
check(other is JavacMethodElement)
check(owner is JavacTypeElement)
+ if (
+ env.backend == XProcessingEnv.Backend.JAVAC &&
+ this.isSuspendFunction() &&
+ other.isSuspendFunction()
+ ) {
+ // b/222240938 - Special case suspend functions in KAPT
+ return suspendOverrides(element, other.element, owner.element, env.typeUtils)
+ }
// Use auto-common's overrides, which provides consistency across javac and ejc (Eclipse).
return MoreElements.overrides(element, other.element, owner.element, env.typeUtils)
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 5970140..cf909f8 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -804,6 +804,64 @@
}
@Test
+ fun suspendOverride() {
+ val src = Source.kotlin(
+ "Foo.kt",
+ """
+ interface Base<T> {
+ suspend fun get(): T
+ suspend fun getAll(): List<T>
+ suspend fun putAll(input: List<T>)
+ suspend fun getAllWithDefault(): List<T>
+ }
+
+ interface DerivedInterface : Base<String> {
+ override suspend fun get(): String
+ override suspend fun getAll(): List<String>
+ override suspend fun putAll(input: List<String>)
+ override suspend fun getAllWithDefault(): List<String> {
+ return emptyList()
+ }
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(sources = listOf(src)) { invocation ->
+ val base = invocation.processingEnv.requireTypeElement("DerivedInterface")
+ val methodNames = base.getAllMethods().toList().jvmNames()
+ assertThat(methodNames).containsExactly("get", "getAll", "putAll", "getAllWithDefault")
+ }
+ }
+
+ @Test
+ fun suspendOverride_abstractClass() {
+ val src = Source.kotlin(
+ "Foo.kt",
+ """
+ abstract class Base<T> {
+ abstract suspend fun get(): T
+ abstract suspend fun getAll(): List<T>
+ abstract suspend fun putAll(input: List<T>)
+ }
+
+ abstract class DerivedClass : Base<Int>() {
+ abstract override suspend fun get(): Int
+ abstract override suspend fun getAll(): List<Int>
+ override suspend fun putAll(input: List<Int>) {
+ }
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(sources = listOf(src)) { invocation ->
+ val base = invocation.processingEnv.requireTypeElement("DerivedClass")
+ val methodNamesCount =
+ base.getAllMethods().toList().jvmNames().groupingBy { it }.eachCount()
+ assertThat(methodNamesCount["get"]).isEqualTo(1)
+ assertThat(methodNamesCount["getAll"]).isEqualTo(1)
+ assertThat(methodNamesCount["putAll"]).isEqualTo(1)
+ }
+ }
+
+ @Test
fun overrideMethodWithCovariantReturnType() {
val src = Source.kotlin(
"ParentWithExplicitOverride.kt",
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index d5d89bd..8e4ae16 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -289,6 +289,10 @@
tasks.withType(Test).configureEach {
it.systemProperty("androidx.room.compiler.processing.strict", "true")
it.maxParallelForks(3)
+ if (project.providers.environmentVariable("GITHUB_ACTIONS").present) {
+ // limit memory usage to avoid running out of memory in the docker container.
+ it.maxHeapSize("512m")
+ }
}
androidx {
diff --git a/room/room-paging/src/main/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt b/room/room-paging/src/main/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
index 30dab5b..023e5b6 100644
--- a/room/room-paging/src/main/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
+++ b/room/room-paging/src/main/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
@@ -21,14 +21,16 @@
import androidx.annotation.RestrictTo
import androidx.paging.PagingSource
import androidx.paging.PagingState
-import androidx.room.InvalidationTracker
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.room.getQueryDispatcher
+import androidx.room.paging.util.ThreadSafeInvalidationObserver
+import androidx.room.paging.util.getClippedRefreshKey
+import androidx.room.paging.util.queryDatabase
+import androidx.room.paging.util.queryItemCount
import androidx.room.withTransaction
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.withContext
-import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
/**
@@ -60,28 +62,20 @@
internal val itemCount: AtomicInteger = AtomicInteger(-1)
- private val observer = object : InvalidationTracker.Observer(tables) {
- override fun onInvalidated(tables: MutableSet<String>) {
- invalidate()
- }
- }
- private val registeredObserver: AtomicBoolean = AtomicBoolean(false)
+ private val observer = ThreadSafeInvalidationObserver(
+ tables = tables,
+ onInvalidated = ::invalidate
+ )
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Value> {
return withContext(db.getQueryDispatcher()) {
- registerObserverIfNecessary()
+ observer.registerIfNecessary(db)
val tempCount = itemCount.get()
// if itemCount is < 0, then it is initial load
if (tempCount < 0) {
initialLoad(params)
} else {
- // otherwise, it is a subsequent load
- val loadResult = loadFromDb(params, tempCount)
- // manually check if database has been updated. If so, the observers's
- // invalidation callback will invalidate this paging source
- db.invalidationTracker.refreshVersionsSync()
- @Suppress("UNCHECKED_CAST")
- if (invalid) INVALID as LoadResult.Invalid<Int, Value> else loadResult
+ nonInitialLoad(params, tempCount)
}
}
}
@@ -98,170 +92,29 @@
*/
private suspend fun initialLoad(params: LoadParams<Int>): LoadResult<Int, Value> {
return db.withTransaction {
- val tempCount = queryItemCount()
+ val tempCount = queryItemCount(sourceQuery, db)
itemCount.set(tempCount)
- loadFromDb(params, tempCount)
+ queryDatabase(params, sourceQuery, db, tempCount, ::convertRows)
}
}
- private suspend fun loadFromDb(
+ private suspend fun nonInitialLoad(
params: LoadParams<Int>,
- itemCount: Int,
+ tempCount: Int,
): LoadResult<Int, Value> {
- val key = params.key ?: 0
- val limit: Int = getLimit(params, key)
- val offset: Int = getOffset(params, key, itemCount)
- return queryDatabase(offset, limit, itemCount)
- }
-
- /**
- * Calculates query limit based on LoadType.
- *
- * Prepend: If requested loadSize is larger than available number of items to prepend, it will
- * query with OFFSET = 0, LIMIT = prevKey
- */
- private fun getLimit(params: LoadParams<Int>, key: Int): Int {
- return when (params) {
- is LoadParams.Prepend ->
- if (key < params.loadSize) key else params.loadSize
- else -> params.loadSize
- }
- }
-
- /**
- * calculates query offset amount based on loadtype
- *
- * Prepend: OFFSET is calculated by counting backwards the number of items that needs to be
- * loaded before [key]. For example, if key = 30 and loadSize = 5, then offset = 25 and items
- * in db position 26-30 are loaded.
- * If requested loadSize is larger than the number of available items to
- * prepend, OFFSET clips to 0 to prevent negative OFFSET.
- *
- * Refresh:
- * If initialKey is supplied through Pager, Paging 3 will now start loading from
- * initialKey with initialKey being the first item.
- * If key is supplied by [getRefreshKey],OFFSET will attempt to load around the anchorPosition
- * with anchorPosition being the middle item. See comments on [getRefreshKey] for more details.
- * If key (regardless if from initialKey or [getRefreshKey]) is larger than available items,
- * the last page will be loaded by counting backwards the loadSize before last item in
- * database. For example, this can happen if invalidation came from a large number of items
- * dropped. i.e. in items 0 - 100, items 41-80 are dropped. Depending on last
- * viewed item, hypothetically [getRefreshKey] may return key = 60. If loadSize = 10, then items
- * 31-40 will be loaded.
- */
- private fun getOffset(params: LoadParams<Int>, key: Int, itemCount: Int): Int {
- return when (params) {
- is LoadParams.Prepend ->
- if (key < params.loadSize) 0 else (key - params.loadSize)
- is LoadParams.Append -> key
- is LoadParams.Refresh ->
- if (key >= itemCount) {
- maxOf(0, itemCount - params.loadSize)
- } else {
- key
- }
- }
- }
-
- /**
- * calls RoomDatabase.query() to return a cursor and then calls convertRows() to extract and
- * return list of data
- *
- * throws [IllegalArgumentException] from [CursorUtil] if column does not exist
- *
- * @param offset offset parameter for LIMIT/OFFSET query. Bounded within user-supplied offset
- * if it is supplied
- *
- * @param limit limit parameter for LIMIT/OFFSET query. Bounded within user-supplied limit
- * if it is supplied
- */
- private suspend fun queryDatabase(
- offset: Int,
- limit: Int,
- itemCount: Int,
- ): LoadResult<Int, Value> {
- val limitOffsetQuery =
- "SELECT * FROM ( ${sourceQuery.sql} ) LIMIT $limit OFFSET $offset"
- val sqLiteQuery: RoomSQLiteQuery = RoomSQLiteQuery.acquire(
- limitOffsetQuery,
- sourceQuery.argCount
- )
- sqLiteQuery.copyArgumentsFrom(sourceQuery)
- val cursor = db.query(sqLiteQuery)
- val data: List<Value>
- try {
- data = convertRows(cursor)
- } finally {
- cursor.close()
- sqLiteQuery.release()
- }
- val nextPosToLoad = offset + data.size
- val nextKey =
- if (data.isEmpty() || data.size < limit || nextPosToLoad >= itemCount) {
- null
- } else {
- nextPosToLoad
- }
- val prevKey = if (offset <= 0 || data.isEmpty()) null else offset
- return LoadResult.Page(
- data = data,
- prevKey = prevKey,
- nextKey = nextKey,
- itemsBefore = offset,
- itemsAfter = maxOf(0, itemCount - nextPosToLoad)
- )
- }
-
- /**
- * returns count of requested items to calculate itemsAfter and itemsBefore for use in creating
- * LoadResult.Page<>
- *
- * throws error when the column value is null, the column type is not an integral type,
- * or the integer value is outside the range [Integer.MIN_VALUE, Integer.MAX_VALUE]
- */
- private fun queryItemCount(): Int {
- val countQuery = "SELECT COUNT(*) FROM ( ${sourceQuery.sql} )"
- val sqLiteQuery: RoomSQLiteQuery = RoomSQLiteQuery.acquire(
- countQuery,
- sourceQuery.argCount
- )
- sqLiteQuery.copyArgumentsFrom(sourceQuery)
- val cursor: Cursor = db.query(sqLiteQuery)
- try {
- if (cursor.moveToFirst()) {
- return cursor.getInt(0)
- }
- return 0
- } finally {
- cursor.close()
- sqLiteQuery.release()
- }
+ val loadResult = queryDatabase(params, sourceQuery, db, tempCount, ::convertRows)
+ // manually check if database has been updated. If so, the observers's
+ // invalidation callback will invalidate this paging source
+ db.invalidationTracker.refreshVersionsSync()
+ @Suppress("UNCHECKED_CAST")
+ return if (invalid) INVALID as LoadResult.Invalid<Int, Value> else loadResult
}
@NonNull
protected abstract fun convertRows(cursor: Cursor): List<Value>
- private fun registerObserverIfNecessary() {
- if (registeredObserver.compareAndSet(false, true)) {
- db.invalidationTracker.addWeakObserver(observer)
- }
- }
-
- /**
- * It is unknown whether anchorPosition represents the item at the top of the screen or item at
- * the bottom of the screen. To ensure the number of items loaded is enough to fill up the
- * screen, half of loadSize is loaded before the anchorPosition and the other half is
- * loaded after the anchorPosition -- anchorPosition becomes the middle item.
- *
- * To prevent a negative key, key = 0 when the number of items available before anchorPosition
- * is less than the requested amount of initialLoadSize / 2.
- */
override fun getRefreshKey(state: PagingState<Int, Value>): Int? {
- val initialLoadSize = state.config.initialLoadSize
- return when {
- state.anchorPosition == null -> null
- else -> maxOf(0, state.anchorPosition!! - (initialLoadSize / 2))
- }
+ return state.getClippedRefreshKey()
}
override val jumpingSupported: Boolean
diff --git a/room/room-paging/src/main/kotlin/androidx/room/paging/util/RoomPagingUtil.kt b/room/room-paging/src/main/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
new file mode 100644
index 0000000..2511fc6
--- /dev/null
+++ b/room/room-paging/src/main/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+
+package androidx.room.paging.util
+
+import android.database.Cursor
+import androidx.annotation.RestrictTo
+import androidx.paging.PagingSource
+import androidx.paging.PagingSource.LoadParams
+import androidx.paging.PagingSource.LoadParams.Prepend
+import androidx.paging.PagingSource.LoadParams.Append
+import androidx.paging.PagingSource.LoadParams.Refresh
+import androidx.paging.PagingSource.LoadResult
+import androidx.paging.PagingState
+import androidx.room.RoomDatabase
+import androidx.room.RoomSQLiteQuery
+
+/**
+ * Calculates query limit based on LoadType.
+ *
+ * Prepend: If requested loadSize is larger than available number of items to prepend, it will
+ * query with OFFSET = 0, LIMIT = prevKey
+ */
+fun getLimit(params: LoadParams<Int>, key: Int): Int {
+ return when (params) {
+ is Prepend ->
+ if (key < params.loadSize) {
+ key
+ } else {
+ params.loadSize
+ }
+ else -> params.loadSize
+ }
+}
+
+/**
+ * calculates query offset amount based on loadtype
+ *
+ * Prepend: OFFSET is calculated by counting backwards the number of items that needs to be
+ * loaded before [key]. For example, if key = 30 and loadSize = 5, then offset = 25 and items
+ * in db position 26-30 are loaded.
+ * If requested loadSize is larger than the number of available items to
+ * prepend, OFFSET clips to 0 to prevent negative OFFSET.
+ *
+ * Refresh:
+ * If initialKey is supplied through Pager, Paging 3 will now start loading from
+ * initialKey with initialKey being the first item.
+ * If key is supplied by [getClippedRefreshKey], the key has already been adjusted to load half
+ * of the requested items before anchorPosition and the other half after anchorPosition. See
+ * comments on [getClippedRefreshKey] for more details.
+ * If key (regardless if from initialKey or [getClippedRefreshKey]) is larger than available items,
+ * the last page will be loaded by counting backwards the loadSize before last item in
+ * database. For example, this can happen if invalidation came from a large number of items
+ * dropped. i.e. in items 0 - 100, items 41-80 are dropped. Depending on last
+ * viewed item, hypothetically [getClippedRefreshKey] may return key = 60. If loadSize = 10, then items
+ * 31-40 will be loaded.
+ */
+fun getOffset(params: LoadParams<Int>, key: Int, itemCount: Int): Int {
+ return when (params) {
+ is Prepend ->
+ if (key < params.loadSize) {
+ 0
+ } else {
+ key - params.loadSize
+ }
+ is Append -> key
+ is Refresh ->
+ if (key >= itemCount) {
+ maxOf(0, itemCount - params.loadSize)
+ } else {
+ key
+ }
+ }
+}
+
+/**
+ * calls RoomDatabase.query() to return a cursor and then calls convertRows() to extract and
+ * return list of data
+ *
+ * throws [IllegalArgumentException] from CursorUtil if column does not exist
+ *
+ * @param params load params to calculate query limit and offset
+ *
+ * @param sourceQuery user provided [RoomSQLiteQuery] for database query
+ *
+ * @param db the [RoomDatabase] to query from
+ *
+ * @param itemCount the db row count, triggers a new PagingSource generation if itemCount changes,
+ * i.e. items are added / removed
+ *
+ * @param convertRows the function to iterate data with provided [Cursor] to return List<Value>
+ */
+fun <Value : Any> queryDatabase(
+ params: LoadParams<Int>,
+ sourceQuery: RoomSQLiteQuery,
+ db: RoomDatabase,
+ itemCount: Int,
+ convertRows: (Cursor) -> List<Value>,
+): LoadResult<Int, Value> {
+ val key = params.key ?: 0
+ val limit: Int = getLimit(params, key)
+ val offset: Int = getOffset(params, key, itemCount)
+ val limitOffsetQuery =
+ "SELECT * FROM ( ${sourceQuery.sql} ) LIMIT $limit OFFSET $offset"
+ val sqLiteQuery: RoomSQLiteQuery = RoomSQLiteQuery.acquire(
+ limitOffsetQuery,
+ sourceQuery.argCount
+ )
+ sqLiteQuery.copyArgumentsFrom(sourceQuery)
+ val cursor = db.query(sqLiteQuery)
+ val data: List<Value>
+ try {
+ data = convertRows(cursor)
+ } finally {
+ cursor.close()
+ sqLiteQuery.release()
+ }
+ val nextPosToLoad = offset + data.size
+ val nextKey =
+ if (data.isEmpty() || data.size < limit || nextPosToLoad >= itemCount) {
+ null
+ } else {
+ nextPosToLoad
+ }
+ val prevKey = if (offset <= 0 || data.isEmpty()) null else offset
+ return LoadResult.Page(
+ data = data,
+ prevKey = prevKey,
+ nextKey = nextKey,
+ itemsBefore = offset,
+ itemsAfter = maxOf(0, itemCount - nextPosToLoad)
+ )
+}
+
+/**
+ * returns count of requested items to calculate itemsAfter and itemsBefore for use in creating
+ * LoadResult.Page<>
+ *
+ * throws error when the column value is null, the column type is not an integral type,
+ * or the integer value is outside the range [Integer.MIN_VALUE, Integer.MAX_VALUE]
+ */
+fun queryItemCount(
+ sourceQuery: RoomSQLiteQuery,
+ db: RoomDatabase
+): Int {
+ val countQuery = "SELECT COUNT(*) FROM ( ${sourceQuery.sql} )"
+ val sqLiteQuery: RoomSQLiteQuery = RoomSQLiteQuery.acquire(
+ countQuery,
+ sourceQuery.argCount
+ )
+ sqLiteQuery.copyArgumentsFrom(sourceQuery)
+ val cursor: Cursor = db.query(sqLiteQuery)
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getInt(0)
+ }
+ return 0
+ } finally {
+ cursor.close()
+ sqLiteQuery.release()
+ }
+}
+
+/**
+ * Returns the key for [PagingSource] for a non-initial REFRESH load.
+ *
+ * To prevent a negative key, key is clipped to 0 when the number of items available before
+ * anchorPosition is less than the requested amount of initialLoadSize / 2.
+ */
+fun <Value : Any> PagingState<Int, Value>.getClippedRefreshKey(): Int? {
+ return when (val anchorPosition = anchorPosition) {
+ null -> null
+ /**
+ * It is unknown whether anchorPosition represents the item at the top of the screen or item at
+ * the bottom of the screen. To ensure the number of items loaded is enough to fill up the
+ * screen, half of loadSize is loaded before the anchorPosition and the other half is
+ * loaded after the anchorPosition -- anchorPosition becomes the middle item.
+ */
+ else -> maxOf(0, anchorPosition - (config.initialLoadSize / 2))
+ }
+}
diff --git a/room/room-paging/src/main/kotlin/androidx/room/paging/util/ThreadSafeInvalidationObserver.kt b/room/room-paging/src/main/kotlin/androidx/room/paging/util/ThreadSafeInvalidationObserver.kt
new file mode 100644
index 0000000..90b9fb7
--- /dev/null
+++ b/room/room-paging/src/main/kotlin/androidx/room/paging/util/ThreadSafeInvalidationObserver.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.paging.util
+
+import androidx.annotation.RestrictTo
+import androidx.room.InvalidationTracker
+import androidx.room.RoomDatabase
+import java.util.concurrent.atomic.AtomicBoolean
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ThreadSafeInvalidationObserver(
+ tables: Array<out String>,
+ val onInvalidated: () -> Unit,
+) : InvalidationTracker.Observer(tables) {
+ private val registered: AtomicBoolean = AtomicBoolean(false)
+
+ override fun onInvalidated(tables: MutableSet<String>) {
+ onInvalidated()
+ }
+
+ fun registerIfNecessary(db: RoomDatabase) {
+ if (registered.compareAndSet(false, true)) {
+ db.invalidationTracker.addWeakObserver(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/savedstate/savedstate-ktx/api/current.ignore b/savedstate/savedstate-ktx/api/current.ignore
new file mode 100644
index 0000000..b576716
--- /dev/null
+++ b/savedstate/savedstate-ktx/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+InvalidNullConversion: androidx.savedstate.ViewKt#findViewTreeSavedStateRegistryOwner(android.view.View):
+ Attempted to remove @Nullable annotation from method androidx.savedstate.ViewKt.findViewTreeSavedStateRegistryOwner(android.view.View)
diff --git a/savedstate/savedstate-ktx/api/current.txt b/savedstate/savedstate-ktx/api/current.txt
index 13e3138..b01f511 100644
--- a/savedstate/savedstate-ktx/api/current.txt
+++ b/savedstate/savedstate-ktx/api/current.txt
@@ -2,7 +2,7 @@
package androidx.savedstate {
public final class ViewKt {
- method public static androidx.savedstate.SavedStateRegistryOwner? findViewTreeSavedStateRegistryOwner(android.view.View);
+ method @Deprecated public static androidx.savedstate.SavedStateRegistryOwner! findViewTreeSavedStateRegistryOwner(android.view.View);
}
}
diff --git a/savedstate/savedstate-ktx/api/public_plus_experimental_current.txt b/savedstate/savedstate-ktx/api/public_plus_experimental_current.txt
index 13e3138..b01f511 100644
--- a/savedstate/savedstate-ktx/api/public_plus_experimental_current.txt
+++ b/savedstate/savedstate-ktx/api/public_plus_experimental_current.txt
@@ -2,7 +2,7 @@
package androidx.savedstate {
public final class ViewKt {
- method public static androidx.savedstate.SavedStateRegistryOwner? findViewTreeSavedStateRegistryOwner(android.view.View);
+ method @Deprecated public static androidx.savedstate.SavedStateRegistryOwner! findViewTreeSavedStateRegistryOwner(android.view.View);
}
}
diff --git a/savedstate/savedstate-ktx/api/restricted_current.ignore b/savedstate/savedstate-ktx/api/restricted_current.ignore
new file mode 100644
index 0000000..b576716
--- /dev/null
+++ b/savedstate/savedstate-ktx/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+InvalidNullConversion: androidx.savedstate.ViewKt#findViewTreeSavedStateRegistryOwner(android.view.View):
+ Attempted to remove @Nullable annotation from method androidx.savedstate.ViewKt.findViewTreeSavedStateRegistryOwner(android.view.View)
diff --git a/savedstate/savedstate-ktx/api/restricted_current.txt b/savedstate/savedstate-ktx/api/restricted_current.txt
index 13e3138..b01f511 100644
--- a/savedstate/savedstate-ktx/api/restricted_current.txt
+++ b/savedstate/savedstate-ktx/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.savedstate {
public final class ViewKt {
- method public static androidx.savedstate.SavedStateRegistryOwner? findViewTreeSavedStateRegistryOwner(android.view.View);
+ method @Deprecated public static androidx.savedstate.SavedStateRegistryOwner! findViewTreeSavedStateRegistryOwner(android.view.View);
}
}
diff --git a/savedstate/savedstate-ktx/src/main/java/androidx/savedstate/View.kt b/savedstate/savedstate-ktx/src/main/java/androidx/savedstate/View.kt
index be8e1b7..aebee41 100644
--- a/savedstate/savedstate-ktx/src/main/java/androidx/savedstate/View.kt
+++ b/savedstate/savedstate-ktx/src/main/java/androidx/savedstate/View.kt
@@ -21,6 +21,15 @@
/**
* Locates the [SavedStateRegistryOwner] associated with this [View], if present.
* This may be used to save and restore the state associated with this view.
+ *
*/
+@Deprecated(
+ message = "Replaced by View.findViewTreeSavedStateRegistryOwner() from savedstate module",
+ replaceWith = ReplaceWith(
+ "findViewTreeSavedStateRegistryOwner()",
+ "androidx.savedstate.findViewTreeSavedStateRegistryOwner"
+ ),
+ level = DeprecationLevel.HIDDEN
+)
public fun View.findViewTreeSavedStateRegistryOwner(): SavedStateRegistryOwner? =
- ViewTreeSavedStateRegistryOwner.get(this)
+ findViewTreeSavedStateRegistryOwner()
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index 1d2cca2..e1371e1 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -2,19 +2,22 @@
package androidx.savedstate {
public final class SavedStateRegistry {
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
- method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String);
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String key);
+ method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String key);
method @MainThread public boolean isRestored();
- method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
- method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
- method @MainThread public void unregisterSavedStateProvider(String);
+ method @MainThread public void performAttach(androidx.lifecycle.Lifecycle lifecycle);
+ method @MainThread public void performRestore(android.os.Bundle? savedState);
+ method @MainThread public void registerSavedStateProvider(String key, androidx.savedstate.SavedStateRegistry.SavedStateProvider provider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated> clazz);
+ method @MainThread public void unregisterSavedStateProvider(String key);
+ property @MainThread public final boolean isRestored;
}
public static interface SavedStateRegistry.AutoRecreated {
- method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner owner);
}
- public static interface SavedStateRegistry.SavedStateProvider {
+ public static fun interface SavedStateRegistry.SavedStateProvider {
method public android.os.Bundle saveState();
}
@@ -39,7 +42,7 @@
public final class ViewTreeSavedStateRegistryOwner {
method public static androidx.savedstate.SavedStateRegistryOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner?);
+ method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner? owner);
}
}
diff --git a/savedstate/savedstate/api/public_plus_experimental_current.txt b/savedstate/savedstate/api/public_plus_experimental_current.txt
index 1d2cca2..e1371e1 100644
--- a/savedstate/savedstate/api/public_plus_experimental_current.txt
+++ b/savedstate/savedstate/api/public_plus_experimental_current.txt
@@ -2,19 +2,22 @@
package androidx.savedstate {
public final class SavedStateRegistry {
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
- method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String);
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String key);
+ method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String key);
method @MainThread public boolean isRestored();
- method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
- method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
- method @MainThread public void unregisterSavedStateProvider(String);
+ method @MainThread public void performAttach(androidx.lifecycle.Lifecycle lifecycle);
+ method @MainThread public void performRestore(android.os.Bundle? savedState);
+ method @MainThread public void registerSavedStateProvider(String key, androidx.savedstate.SavedStateRegistry.SavedStateProvider provider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated> clazz);
+ method @MainThread public void unregisterSavedStateProvider(String key);
+ property @MainThread public final boolean isRestored;
}
public static interface SavedStateRegistry.AutoRecreated {
- method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner owner);
}
- public static interface SavedStateRegistry.SavedStateProvider {
+ public static fun interface SavedStateRegistry.SavedStateProvider {
method public android.os.Bundle saveState();
}
@@ -39,7 +42,7 @@
public final class ViewTreeSavedStateRegistryOwner {
method public static androidx.savedstate.SavedStateRegistryOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner?);
+ method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner? owner);
}
}
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index 1d2cca2..e1371e1 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -2,19 +2,22 @@
package androidx.savedstate {
public final class SavedStateRegistry {
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
- method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String);
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String key);
+ method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String key);
method @MainThread public boolean isRestored();
- method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
- method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
- method @MainThread public void unregisterSavedStateProvider(String);
+ method @MainThread public void performAttach(androidx.lifecycle.Lifecycle lifecycle);
+ method @MainThread public void performRestore(android.os.Bundle? savedState);
+ method @MainThread public void registerSavedStateProvider(String key, androidx.savedstate.SavedStateRegistry.SavedStateProvider provider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated> clazz);
+ method @MainThread public void unregisterSavedStateProvider(String key);
+ property @MainThread public final boolean isRestored;
}
public static interface SavedStateRegistry.AutoRecreated {
- method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner owner);
}
- public static interface SavedStateRegistry.SavedStateProvider {
+ public static fun interface SavedStateRegistry.SavedStateProvider {
method public android.os.Bundle saveState();
}
@@ -39,7 +42,7 @@
public final class ViewTreeSavedStateRegistryOwner {
method public static androidx.savedstate.SavedStateRegistryOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner?);
+ method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner? owner);
}
}
diff --git a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.java b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.java
deleted file mode 100644
index 97999cf..0000000
--- a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.savedstate;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.Lifecycle;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ViewTreeSavedStateRegistryOwnerTest {
-
- /**
- * Tests that a direct set/get on a single view survives a round trip
- */
- @Test
- public void setGetSameView() {
- final View v = new View(InstrumentationRegistry.getInstrumentation().getContext());
- final SavedStateRegistryOwner fakeOwner = new FakeOwner();
-
- assertNull("initial SavedStateRegistryOwner expects null",
- ViewTreeSavedStateRegistryOwner.get(v));
-
- ViewTreeSavedStateRegistryOwner.set(v, fakeOwner);
-
- assertEquals("get the SavedStateRegistryOwner set directly", fakeOwner,
- ViewTreeSavedStateRegistryOwner.get(v));
- }
-
- /**
- * Tests that the owner set on a root of a subhierarchy is seen by both direct children
- * and other descendants
- */
- @Test
- public void getAncestorOwner() {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final ViewGroup root = new FrameLayout(context);
- final ViewGroup parent = new FrameLayout(context);
- final View child = new View(context);
- root.addView(parent);
- parent.addView(child);
-
- assertNull("initial SavedStateRegistryOwner expects null",
- ViewTreeSavedStateRegistryOwner.get(child));
-
- final SavedStateRegistryOwner fakeOwner = new FakeOwner();
- ViewTreeSavedStateRegistryOwner.set(root, fakeOwner);
-
- assertEquals("root sees owner", fakeOwner, ViewTreeSavedStateRegistryOwner.get(root));
- assertEquals("direct child sees owner", fakeOwner,
- ViewTreeSavedStateRegistryOwner.get(parent));
- assertEquals("grandchild sees owner", fakeOwner,
- ViewTreeSavedStateRegistryOwner.get(child));
- }
-
- /**
- * Tests that a new owner set between a root and a descendant is seen by the descendant
- * instead of the root value
- */
- @Test
- public void shadowedOwner() {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final ViewGroup root = new FrameLayout(context);
- final ViewGroup parent = new FrameLayout(context);
- final View child = new View(context);
- root.addView(parent);
- parent.addView(child);
-
- assertNull("initial SavedStateRegistryOwner expects null",
- ViewTreeSavedStateRegistryOwner.get(child));
-
- final SavedStateRegistryOwner rootFakeOwner = new FakeOwner();
- ViewTreeSavedStateRegistryOwner.set(root, rootFakeOwner);
-
- final SavedStateRegistryOwner parentFakeOwner = new FakeOwner();
- ViewTreeSavedStateRegistryOwner.set(parent, parentFakeOwner);
-
- assertEquals("root sees owner", rootFakeOwner, ViewTreeSavedStateRegistryOwner.get(root));
- assertEquals("direct child sees owner", parentFakeOwner,
- ViewTreeSavedStateRegistryOwner.get(parent));
- assertEquals("grandchild sees owner", parentFakeOwner,
- ViewTreeSavedStateRegistryOwner.get(child));
- }
-
- static class FakeOwner implements SavedStateRegistryOwner {
- @NonNull
- @Override
- public Lifecycle getLifecycle() {
- throw new UnsupportedOperationException("not a real SavedStateRegistryOwner");
- }
-
- @NonNull
- @Override
- public SavedStateRegistry getSavedStateRegistry() {
- throw new UnsupportedOperationException("not a real SavedStateRegistryOwner");
- }
- }
-}
diff --git a/savedstate/savedstate-ktx/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
similarity index 94%
rename from savedstate/savedstate-ktx/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
rename to savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
index f0b4657..d176efc 100644
--- a/savedstate/savedstate-ktx/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
+++ b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package androidx.savedstate
import android.view.View
@@ -41,7 +42,7 @@
.isNull()
val fakeOwner: SavedStateRegistryOwner = FakeSavedStateRegistryOwner()
- ViewTreeSavedStateRegistryOwner.set(v, fakeOwner)
+ v.setViewTreeSavedStateRegistryOwner(fakeOwner)
assertWithMessage("get the SavedStateRegistryOwner set directly")
.that(v.findViewTreeSavedStateRegistryOwner())
@@ -66,7 +67,7 @@
.isNull()
val fakeOwner: SavedStateRegistryOwner = FakeSavedStateRegistryOwner()
- ViewTreeSavedStateRegistryOwner.set(root, fakeOwner)
+ root.setViewTreeSavedStateRegistryOwner(fakeOwner)
assertWithMessage("root sees owner")
.that(root.findViewTreeSavedStateRegistryOwner())
@@ -98,10 +99,10 @@
.isNull()
val rootFakeOwner: SavedStateRegistryOwner = FakeSavedStateRegistryOwner()
- ViewTreeSavedStateRegistryOwner.set(root, rootFakeOwner)
+ root.setViewTreeSavedStateRegistryOwner(rootFakeOwner)
val parentFakeOwner: SavedStateRegistryOwner = FakeSavedStateRegistryOwner()
- ViewTreeSavedStateRegistryOwner.set(parent, parentFakeOwner)
+ parent.setViewTreeSavedStateRegistryOwner(parentFakeOwner)
assertWithMessage("root sees owner")
.that(root.findViewTreeSavedStateRegistryOwner())
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java
deleted file mode 100644
index 3bca4f1..0000000
--- a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.savedstate;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.internal.SafeIterableMap;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleEventObserver;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * An interface for plugging components that consumes and contributes to the saved state.
- *
- * <p>This objects lifetime is bound to the lifecycle of owning component: when activity or
- * fragment is recreated, new instance of the object is created as well.
- */
-@SuppressLint("RestrictedApi")
-public final class SavedStateRegistry {
- private static final String SAVED_COMPONENTS_KEY =
- "androidx.lifecycle.BundlableSavedStateRegistry.key";
-
- private final SafeIterableMap<String, SavedStateProvider> mComponents =
- new SafeIterableMap<>();
- private boolean mAttached;
- @Nullable
- private Bundle mRestoredState;
- private boolean mRestored;
- private Recreator.SavedStateProvider mRecreatorProvider;
- boolean mAllowingSavingState = true;
-
- SavedStateRegistry() {
- }
-
- /**
- * Consumes saved state previously supplied by {@link SavedStateProvider} registered
- * via {@link #registerSavedStateProvider(String, SavedStateProvider)}
- * with the given {@code key}.
- * <p>
- * This call clears an internal reference to returned saved state, so if you call it second time
- * in the row it will return {@code null}.
- * <p>
- * All unconsumed values will be saved during {@code onSaveInstanceState(Bundle savedState)}
- * <p>
- * This method can be called after {@code super.onCreate(savedStateBundle)} of the corresponding
- * component. Calling it before that will result in {@code IllegalArgumentException}.
- * {@link Lifecycle.Event#ON_CREATE} can be used as a signal
- * that a saved state can be safely consumed.
- *
- * @param key a key with which {@link SavedStateProvider} was previously registered.
- * @return {@code S} with the previously saved state or {@code null}
- */
- @MainThread
- @Nullable
- public Bundle consumeRestoredStateForKey(@NonNull String key) {
- if (!mRestored) {
- throw new IllegalStateException("You can consumeRestoredStateForKey "
- + "only after super.onCreate of corresponding component");
- }
- if (mRestoredState != null) {
- Bundle result = mRestoredState.getBundle(key);
- mRestoredState.remove(key);
- if (mRestoredState.isEmpty()) {
- mRestoredState = null;
- }
- return result;
- }
- return null;
- }
-
- /**
- * Registers a {@link SavedStateProvider} by the given {@code key}. This
- * {@code savedStateProvider} will be called
- * during state saving phase, returned object will be associated with the given {@code key}
- * and can be used after the restoration via {@link #consumeRestoredStateForKey(String)}.
- * <p>
- * If there is unconsumed value with the same {@code key},
- * the value supplied by {@code savedStateProvider} will be override and
- * will be written to resulting saved state.
- * <p> if a provider was already registered with the given {@code key}, an implementation should
- * throw an {@link IllegalArgumentException}
- *
- * @param key a key with which returned saved state will be associated
- * @param provider savedStateProvider to get saved state.
- */
- @MainThread
- public void registerSavedStateProvider(@NonNull String key,
- @NonNull SavedStateProvider provider) {
- SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
- if (previous != null) {
- throw new IllegalArgumentException("SavedStateProvider with the given key is"
- + " already registered");
- }
- }
-
- /**
- * Get a previously registered {@link SavedStateProvider}.
- *
- * @param key The key used to register the {@link SavedStateProvider} when it was registered
- * with {@link #registerSavedStateProvider(String, SavedStateProvider)}.
- * @return The {@link SavedStateProvider} previously registered with
- * {@link #registerSavedStateProvider(String, SavedStateProvider)} or null if no provider
- * has been registered with the given key.
- * @see #registerSavedStateProvider(String, SavedStateProvider)
- */
- @Nullable
- public SavedStateProvider getSavedStateProvider(@NonNull String key) {
- SavedStateProvider provider = null;
- for (Map.Entry<String, SavedStateProvider> entry : mComponents) {
- if (entry.getKey().equals(key)) {
- provider = entry.getValue();
- break;
- }
- }
- return provider;
- }
-
- /**
- * Unregisters a component previously registered by the given {@code key}
- *
- * @param key a key with which a component was previously registered.
- */
- @MainThread
- public void unregisterSavedStateProvider(@NonNull String key) {
- mComponents.remove(key);
- }
-
- /**
- * Returns if state was restored after creation and can be safely consumed
- * with {@link #consumeRestoredStateForKey(String)}
- *
- * @return true if state was restored.
- */
- @MainThread
- public boolean isRestored() {
- return mRestored;
- }
-
- /**
- * Subclasses of this interface will be automatically recreated if they were previously
- * registered via {@link #runOnNextRecreation(Class)}.
- * <p>
- * Subclasses must have a default constructor
- */
- public interface AutoRecreated {
- /**
- * This method will be called during
- * dispatching of {@link androidx.lifecycle.Lifecycle.Event#ON_CREATE} of owning component.
- *
- * @param owner a component that was restarted
- */
- void onRecreated(@NonNull SavedStateRegistryOwner owner);
- }
-
- /**
- * Executes the given class when the owning component restarted.
- * <p>
- * The given class will be automatically instantiated via default constructor and method
- * {@link AutoRecreated#onRecreated(SavedStateRegistryOwner)} will be called.
- * It is called as part of dispatching of {@link androidx.lifecycle.Lifecycle.Event#ON_CREATE}
- * event.
- *
- * @param clazz that will need to be instantiated on the next component recreation
- * @throws IllegalStateException if you try to call if after {@link Lifecycle.Event#ON_STOP}
- * was dispatched
- */
- @MainThread
- public void runOnNextRecreation(@NonNull Class<? extends AutoRecreated> clazz) {
- if (!mAllowingSavingState) {
- throw new IllegalStateException(
- "Can not perform this action after onSaveInstanceState");
- }
- if (mRecreatorProvider == null) {
- mRecreatorProvider = new Recreator.SavedStateProvider(this);
- }
- try {
- clazz.getDeclaredConstructor();
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException("Class" + clazz.getSimpleName() + " must have "
- + "default constructor in order to be automatically recreated", e);
- }
- mRecreatorProvider.add(clazz.getName());
- }
-
- /**
- * An interface for an owner of this @{code {@link SavedStateRegistry} to attach this
- * to a {@link Lifecycle}.
- */
- @MainThread
- void performAttach(@NonNull Lifecycle lifecycle) {
- if (mAttached) {
- throw new IllegalStateException("SavedStateRegistry was already attached.");
- }
-
- lifecycle.addObserver((LifecycleEventObserver) (source, event) -> {
- if (event == Lifecycle.Event.ON_START) {
- mAllowingSavingState = true;
- } else if (event == Lifecycle.Event.ON_STOP) {
- mAllowingSavingState = false;
- }
- });
-
- mAttached = true;
- }
-
- /**
- * An interface for an owner of this @{code {@link SavedStateRegistry} to restore saved state.
- *
- */
- @MainThread
- void performRestore(@Nullable Bundle savedState) {
- if (!mAttached) {
- throw new IllegalStateException("You must call performAttach() before calling "
- + "performRestore(Bundle).");
- }
- if (mRestored) {
- throw new IllegalStateException("SavedStateRegistry was already restored.");
- }
- if (savedState != null) {
- mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
- }
-
- mRestored = true;
- }
-
- /**
- * An interface for an owner of this @{code {@link SavedStateRegistry}
- * to perform state saving, it will call all registered providers and
- * merge with unconsumed state.
- *
- * @param outBundle Bundle in which to place a saved state
- */
- @MainThread
- void performSave(@NonNull Bundle outBundle) {
- Bundle components = new Bundle();
- if (mRestoredState != null) {
- components.putAll(mRestoredState);
- }
- for (Iterator<Map.Entry<String, SavedStateProvider>> it =
- mComponents.iteratorWithAdditions(); it.hasNext(); ) {
- Map.Entry<String, SavedStateProvider> entry1 = it.next();
- components.putBundle(entry1.getKey(), entry1.getValue().saveState());
- }
- if (!components.isEmpty()) {
- outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
- }
- }
-
- /**
- * This interface marks a component that contributes to saved state.
- */
- public interface SavedStateProvider {
- /**
- * Called to retrieve a state from a component before being killed
- * so later the state can be received from {@link #consumeRestoredStateForKey(String)}
- *
- * @return S with your saved state.
- */
- @NonNull
- Bundle saveState();
- }
-}
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.kt b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.kt
new file mode 100644
index 0000000..a8bd28d
--- /dev/null
+++ b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.kt
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.savedstate
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import androidx.annotation.MainThread
+import androidx.arch.core.internal.SafeIterableMap
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+
+/**
+ * An interface for plugging components that consumes and contributes to the saved state.
+ *
+ *
+ * This objects lifetime is bound to the lifecycle of owning component: when activity or
+ * fragment is recreated, new instance of the object is created as well.
+ */
+@SuppressLint("RestrictedApi")
+class SavedStateRegistry internal constructor() {
+ private val components = SafeIterableMap<String, SavedStateProvider>()
+ private var attached = false
+ private var restoredState: Bundle? = null
+
+ /**
+ * Whether the state was restored after creation and can be safely consumed
+ * with [consumeRestoredStateForKey].
+ *
+ * [isRestored] == true if state was restored
+ */
+ @get: MainThread
+ var isRestored = false
+ private set
+ private var recreatorProvider: Recreator.SavedStateProvider? = null
+ internal var isAllowingSavingState = true
+
+ /**
+ * Consumes saved state previously supplied by [SavedStateProvider] registered
+ * via [registerSavedStateProvider] with the given `key`.
+ *
+ *
+ * This call clears an internal reference to returned saved state, so if you call it second time
+ * in the row it will return `null`.
+ *
+ *
+ * All unconsumed values will be saved during `onSaveInstanceState(Bundle savedState)`
+ *
+ *
+ * This method can be called after `super.onCreate(savedStateBundle)` of the corresponding
+ * component. Calling it before that will result in `IllegalArgumentException`.
+ * [Lifecycle.Event.ON_CREATE] can be used as a signal
+ * that a saved state can be safely consumed.
+ *
+ * @param key a key with which [SavedStateProvider] was previously registered.
+ * @return `S` with the previously saved state or {@code null}
+ */
+ @MainThread
+ fun consumeRestoredStateForKey(key: String): Bundle? {
+ check(isRestored) {
+ ("You can consumeRestoredStateForKey " +
+ "only after super.onCreate of corresponding component")
+ }
+ if (restoredState != null) {
+ val result = restoredState?.getBundle(key)
+ restoredState?.remove(key)
+ if (restoredState?.isEmpty != false) {
+ restoredState = null
+ }
+ return result
+ }
+ return null
+ }
+
+ /**
+ * Registers a [SavedStateProvider] by the given `key`. This
+ * `savedStateProvider` will be called
+ * during state saving phase, returned object will be associated with the given `key`
+ * and can be used after the restoration via [.consumeRestoredStateForKey].
+ *
+ *
+ * If there is unconsumed value with the same `key`,
+ * the value supplied by `savedStateProvider` will be overridden and
+ * will be written to resulting saved state.
+ *
+ * If a provider was already registered with the given `key`, an implementation should
+ * throw an [IllegalArgumentException]
+ *
+ * @param key a key with which returned saved state will be associated
+ * @param provider savedStateProvider to get saved state.
+ */
+ @MainThread
+ fun registerSavedStateProvider(
+ key: String,
+ provider: SavedStateProvider
+ ) {
+ val previous = components.putIfAbsent(key, provider)
+ require(previous == null) {
+ ("SavedStateProvider with the given key is" +
+ " already registered")
+ }
+ }
+
+ /**
+ * Get a previously registered [SavedStateProvider].
+ *
+ * @param key The key used to register the [SavedStateProvider] when it was registered
+ * with registerSavedStateProvider(String, SavedStateProvider).
+ *
+ * Returns the [SavedStateProvider] previously registered with
+ * [registerSavedStateProvider] or null if no provider
+ * has been registered with the given key.
+ */
+ fun getSavedStateProvider(key: String): SavedStateProvider? {
+ var provider: SavedStateProvider? = null
+ for ((k, value) in components) {
+ if (k == key) {
+ provider = value
+ break
+ }
+ }
+ return provider
+ }
+
+ /**
+ * Unregisters a component previously registered by the given `key`
+ *
+ * @param key a key with which a component was previously registered.
+ */
+ @MainThread
+ fun unregisterSavedStateProvider(key: String) {
+ components.remove(key)
+ }
+
+ /**
+ * Subclasses of this interface will be automatically recreated if they were previously
+ * registered via [runOnNextRecreation].
+ *
+ *
+ * Subclasses must have a default constructor
+ */
+ interface AutoRecreated {
+ /**
+ * This method will be called during
+ * dispatching of [androidx.lifecycle.Lifecycle.Event.ON_CREATE] of owning component
+ * which was restarted
+ *
+ * @param owner a component that was restarted
+ */
+ fun onRecreated(owner: SavedStateRegistryOwner)
+ }
+
+ /**
+ * Executes the given class when the owning component restarted.
+ *
+ *
+ * The given class will be automatically instantiated via default constructor and method
+ * [AutoRecreated.onRecreated] will be called.
+ * It is called as part of dispatching of [androidx.lifecycle.Lifecycle.Event.ON_CREATE]
+ * event.
+ *
+ * @param clazz that will need to be instantiated on the next component recreation
+ * @throws IllegalArgumentException if you try to call if after [Lifecycle.Event.ON_STOP]
+ * was dispatched
+ */
+ @MainThread
+ fun runOnNextRecreation(clazz: Class<out AutoRecreated>) {
+ check(isAllowingSavingState) { "Can not perform this action after onSaveInstanceState" }
+ recreatorProvider = recreatorProvider ?: Recreator.SavedStateProvider(this)
+ try {
+ clazz.getDeclaredConstructor()
+ } catch (e: NoSuchMethodException) {
+ throw IllegalArgumentException(
+ "Class ${clazz.simpleName} must have " +
+ "default constructor in order to be automatically recreated", e
+ )
+ }
+ recreatorProvider?.add(clazz.name)
+ }
+
+ /**
+ * An interface for an owner of this [SavedStateRegistry] to attach this
+ * to a [Lifecycle].
+ */
+ @MainThread
+ fun performAttach(lifecycle: Lifecycle) {
+ check(!attached) { "SavedStateRegistry was already attached." }
+
+ lifecycle.addObserver(LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ isAllowingSavingState = true
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ isAllowingSavingState = false
+ }
+ })
+ attached = true
+ }
+
+ /**
+ * An interface for an owner of this [SavedStateRegistry] to restore saved state.
+ *
+ */
+ @MainThread
+ fun performRestore(savedState: Bundle?) {
+ check(attached) {
+ ("You must call performAttach() before calling " +
+ "performRestore(Bundle).")
+ }
+ check(!isRestored) { "SavedStateRegistry was already restored." }
+ restoredState = savedState?.getBundle(SAVED_COMPONENTS_KEY)
+
+ isRestored = true
+ }
+
+ /**
+ * An interface for an owner of this [SavedStateRegistry]
+ * to perform state saving, it will call all registered providers and
+ * merge with unconsumed state.
+ *
+ * @param outBundle Bundle in which to place a saved state
+ * @suppress INACCESSIBLE_TYPE iterator is used strictly as Iterator, does not access
+ * inaccessible type IteratorWithAdditions
+ */
+ @MainThread
+ @Suppress("INACCESSIBLE_TYPE")
+ fun performSave(outBundle: Bundle) {
+ val components = Bundle()
+ if (restoredState != null) {
+ components.putAll(restoredState)
+ }
+ val it: Iterator<Map.Entry<String, SavedStateProvider>> =
+ this.components.iteratorWithAdditions()
+ while (it.hasNext()) {
+ val (key, value) = it.next()
+ components.putBundle(key, value.saveState())
+ }
+ if (!components.isEmpty) {
+ outBundle.putBundle(SAVED_COMPONENTS_KEY, components)
+ }
+ }
+
+ /**
+ * This interface marks a component that contributes to saved state.
+ */
+ fun interface SavedStateProvider {
+ /**
+ * Called to retrieve a state from a component before being killed
+ * so later the state can be received from [consumeRestoredStateForKey]
+ *
+ * Returns `S` with your saved state.
+ */
+ fun saveState(): Bundle
+ }
+
+ private companion object {
+ private const val SAVED_COMPONENTS_KEY =
+ "androidx.lifecycle.BundlableSavedStateRegistry.key"
+ }
+}
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/ViewTreeSavedStateRegistryOwner.java b/savedstate/savedstate/src/main/java/androidx/savedstate/ViewTreeSavedStateRegistryOwner.java
deleted file mode 100644
index e82e908..0000000
--- a/savedstate/savedstate/src/main/java/androidx/savedstate/ViewTreeSavedStateRegistryOwner.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.savedstate;
-
-import android.view.View;
-import android.view.ViewParent;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Accessors for finding a view tree-local {@link SavedStateRegistryOwner} that allows managing the
- * saving state using {@link SavedStateRegistry} for the given view.
- */
-public final class ViewTreeSavedStateRegistryOwner {
- private ViewTreeSavedStateRegistryOwner() {
- // No instances
- }
-
- /**
- * Set the {@link SavedStateRegistryOwner} responsible for managing the saved state for the
- * given {@link View}.
- * Calls to {@link #get(View)} from this view or descendants will return {@code owner}.
- *
- * This is is automatically set for you in the common cases of using fragments or
- * ComponentActivity.
- *
- * <p>This should only be called by constructs such as activities or fragments that manage
- * a view tree and their saved state through a {@link SavedStateRegistryOwner}. Callers
- * should only set a {@link SavedStateRegistryOwner} that will be <em>stable.</em> The
- * associated {@link SavedStateRegistry} should be cleared if the view tree is removed and is
- * not guaranteed to later become reattached to a window.</p>
- *
- * @param view Root view managed by {@link SavedStateRegistryOwner}
- * @param owner The {@link SavedStateRegistryOwner} responsible for managing the
- * saved state for the given view
- */
- public static void set(@NonNull View view, @Nullable SavedStateRegistryOwner owner) {
- view.setTag(R.id.view_tree_saved_state_registry_owner, owner);
- }
-
- /**
- * Retrieve the {@link SavedStateRegistryOwner} responsible for managing the saved state
- * for the given {@link View}.
- * This may be used to save or restore the state associated with the view.
- *
- * The returned {@link SavedStateRegistryOwner} is managing all the Views within the Fragment
- * or Activity the given {@link View} is added to.
- *
- * @param view View to fetch a {@link SavedStateRegistryOwner} for
- * @return The {@link SavedStateRegistryOwner} responsible for managing the saved state for
- * the given view and/or some subset of its ancestors
- */
- @Nullable
- public static SavedStateRegistryOwner get(@NonNull View view) {
- SavedStateRegistryOwner found = (SavedStateRegistryOwner) view.getTag(
- R.id.view_tree_saved_state_registry_owner);
- if (found != null) return found;
- ViewParent parent = view.getParent();
- while (found == null && parent instanceof View) {
- final View parentView = (View) parent;
- found = (SavedStateRegistryOwner) parentView.getTag(
- R.id.view_tree_saved_state_registry_owner);
- parent = parentView.getParent();
- }
- return found;
- }
-}
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/ViewTreeSavedStateRegistryOwner.kt b/savedstate/savedstate/src/main/java/androidx/savedstate/ViewTreeSavedStateRegistryOwner.kt
new file mode 100644
index 0000000..89e347e
--- /dev/null
+++ b/savedstate/savedstate/src/main/java/androidx/savedstate/ViewTreeSavedStateRegistryOwner.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("ViewTreeSavedStateRegistryOwner")
+
+package androidx.savedstate
+
+import android.view.View
+
+/**
+ * Set the [SavedStateRegistryOwner] responsible for managing the saved state for this [View]
+ * Calls to [get] from this view or descendants will return `owner`.
+ *
+ * This is is automatically set for you in the common cases of using fragments or
+ * ComponentActivity.
+ *
+ *
+ * This should only be called by constructs such as activities or fragments that manage
+ * a view tree and their saved state through a [SavedStateRegistryOwner]. Callers
+ * should only set a [SavedStateRegistryOwner] that will be *stable.* The
+ * associated [SavedStateRegistry] should be cleared if the view tree is removed and is
+ * not guaranteed to later become reattached to a window.
+ *
+ * @param owner The [SavedStateRegistryOwner] responsible for managing the
+ * saved state for the given view
+ */
+@JvmName("set")
+fun View.setViewTreeSavedStateRegistryOwner(owner: SavedStateRegistryOwner?) {
+ setTag(R.id.view_tree_saved_state_registry_owner, owner)
+}
+
+/**
+ * Retrieve the [SavedStateRegistryOwner] responsible for managing the saved state
+ * for this [View].
+ * This may be used to save or restore the state associated with the view.
+ *
+ * The returned [SavedStateRegistryOwner] is managing all the Views within the Fragment
+ * or Activity this [View] is added to.
+
+ * @return The [SavedStateRegistryOwner] responsible for managing the saved state for
+ * this view and/or some subset of its ancestors
+ */
+@JvmName("get")
+fun View.findViewTreeSavedStateRegistryOwner(): SavedStateRegistryOwner? {
+ return generateSequence(this) { view ->
+ view.parent as? View
+ }.mapNotNull { view ->
+ view.getTag(R.id.view_tree_saved_state_registry_owner) as? SavedStateRegistryOwner
+ }.firstOrNull()
+}
diff --git a/settings.gradle b/settings.gradle
index 58d4a8c..4bd16f4a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -16,15 +16,9 @@
}
buildscript {
- repositories {
- maven {
- url = new File(buildscript.sourceFile.parent + "/../../prebuilts/androidx/external").getCanonicalFile()
- }
- if (System.getenv("ALLOW_PUBLIC_REPOS") != null) {
- mavenCentral()
- gradlePluginPortal()
- }
- }
+ ext.supportRootFolder = buildscript.sourceFile.getParentFile()
+ apply(from: "buildSrc/repos.gradle")
+ repos.addMavenRepositories(repositories)
dependencies {
classpath("com.gradle:gradle-enterprise-gradle-plugin:3.9")
@@ -32,6 +26,28 @@
}
}
+// Makes strong assumptions about the project structure.
+def prebuiltsRoot = new File(
+ buildscript.sourceFile.parentFile.parentFile.parentFile,
+ "prebuilts"
+).absolutePath
+def rootProjectRepositories
+
+getGradle().beforeProject {
+ // Migrate to dependencyResolutionManagement.repositories when
+ // https://github.com/gradle/gradle/issues/17295 is fixed
+ if (it.path == ":") {
+ repos.addMavenRepositories(it.repositories)
+ rootProjectRepositories = it.repositories
+ } else {
+ // Performance optimization because it is more efficient to reuse
+ // repositories from the root project than recreate identical ones
+ // on each project
+ it.repositories.addAll(rootProjectRepositories)
+ }
+ it.ext.prebuiltsRoot = prebuiltsRoot
+}
+
apply(plugin: "com.gradle.enterprise")
apply(plugin: "com.gradle.common-custom-user-data-gradle-plugin")
@@ -412,6 +428,7 @@
includeProject(":compose:material:material-icons-extended-twotone", [BuildType.COMPOSE])
includeProject(":compose:material:material-ripple", [BuildType.COMPOSE])
includeProject(":compose:material:material-window", [BuildType.COMPOSE])
+includeProject(":compose:material:material-window:material-window-samples", "compose/material/material-window/samples", [BuildType.COMPOSE])
includeProject(":compose:material:material:icons:generator", [BuildType.COMPOSE])
includeProject(":compose:material:material:integration-tests:material-demos", [BuildType.COMPOSE])
includeProject(":compose:material:material:integration-tests:material-catalog", [BuildType.COMPOSE])
@@ -795,6 +812,7 @@
includeProject(":webkit:webkit", [BuildType.MAIN])
includeProject(":window:window", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
includeProject(":window:extensions:extensions", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
+includeProject(":window:integration-tests:configuration-change-tests", [BuildType.MAIN])
includeProject(":window:sidecar:sidecar", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
includeProject(":window:window-java", [BuildType.MAIN])
includeProject(":window:window-rxjava2", [BuildType.MAIN])
diff --git a/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt b/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt
index 95a660f..09f8e0c 100644
--- a/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt
+++ b/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt
@@ -50,7 +50,11 @@
listOf(props.tipOfTreeMavenRepoPath) + props.repositoryUrls
}
- private val repositories: String
+ /**
+ * A `repositories {}` gradle block that contains all default repositories, for inclusion
+ * in gradle configurations.
+ */
+ val repositories: String
get() = buildString {
appendLine("repositories {")
append(defaultRepoLines)
@@ -179,6 +183,9 @@
}
fun load(): ProjectProps {
val stream = ProjectSetupRule::class.java.classLoader.getResourceAsStream("sdk.prop")
+ ?: throw IllegalStateException("No sdk.prop file found. " +
+ "(you probably need to call SdkResourceGenerator.generateForHostTest " +
+ "in build.gradle)")
val properties = Properties()
properties.load(stream)
return ProjectProps(
diff --git a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
index c1f6cd0..9d1d8b7 100644
--- a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
+++ b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
@@ -51,10 +51,10 @@
* Default compilation modes to test for all AndroidX macrobenchmarks.
*
* Baseline profiles are only supported from Nougat (API 24),
- * currently through Android 11 (API 30)
+ * currently through Android 12 (API 31)
*/
@Suppress("ConvertTwoComparisonsToRangeCheck") // lint doesn't understand range checks
-val COMPILATION_MODES = if (Build.VERSION.SDK_INT >= 24 && Build.VERSION.SDK_INT <= 30) {
+val COMPILATION_MODES = if (Build.VERSION.SDK_INT >= 24 && Build.VERSION.SDK_INT <= 31) {
listOf(CompilationMode.Partial())
} else {
emptyList()
diff --git a/vectordrawable/vectordrawable-animated/api/1.2.0-beta01.txt b/vectordrawable/vectordrawable-animated/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..4c7b6cc
--- /dev/null
+++ b/vectordrawable/vectordrawable-animated/api/1.2.0-beta01.txt
@@ -0,0 +1,36 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public interface Animatable2Compat extends android.graphics.drawable.Animatable {
+ method public void clearAnimationCallbacks();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ }
+
+ public abstract static class Animatable2Compat.AnimationCallback {
+ ctor public Animatable2Compat.AnimationCallback();
+ method public void onAnimationEnd(android.graphics.drawable.Drawable!);
+ method public void onAnimationStart(android.graphics.drawable.Drawable!);
+ }
+
+ public class AnimatedVectorDrawableCompat extends android.graphics.drawable.Drawable implements androidx.vectordrawable.graphics.drawable.Animatable2Compat {
+ method public void clearAnimationCallbacks();
+ method public static void clearAnimationCallbacks(android.graphics.drawable.Drawable!);
+ method public static androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat? create(android.content.Context, @DrawableRes int);
+ method public static androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat! createFromXmlInner(android.content.Context!, android.content.res.Resources!, org.xmlpull.v1.XmlPullParser!, android.util.AttributeSet!, android.content.res.Resources.Theme!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas!);
+ method public int getOpacity();
+ method public boolean isRunning();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public static void registerAnimationCallback(android.graphics.drawable.Drawable!, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback!);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setColorFilter(int, android.graphics.PorterDuff.Mode!);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public static boolean unregisterAnimationCallback(android.graphics.drawable.Drawable!, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback!);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable-animated/api/public_plus_experimental_1.2.0-beta01.txt b/vectordrawable/vectordrawable-animated/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..4c7b6cc
--- /dev/null
+++ b/vectordrawable/vectordrawable-animated/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,36 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public interface Animatable2Compat extends android.graphics.drawable.Animatable {
+ method public void clearAnimationCallbacks();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ }
+
+ public abstract static class Animatable2Compat.AnimationCallback {
+ ctor public Animatable2Compat.AnimationCallback();
+ method public void onAnimationEnd(android.graphics.drawable.Drawable!);
+ method public void onAnimationStart(android.graphics.drawable.Drawable!);
+ }
+
+ public class AnimatedVectorDrawableCompat extends android.graphics.drawable.Drawable implements androidx.vectordrawable.graphics.drawable.Animatable2Compat {
+ method public void clearAnimationCallbacks();
+ method public static void clearAnimationCallbacks(android.graphics.drawable.Drawable!);
+ method public static androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat? create(android.content.Context, @DrawableRes int);
+ method public static androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat! createFromXmlInner(android.content.Context!, android.content.res.Resources!, org.xmlpull.v1.XmlPullParser!, android.util.AttributeSet!, android.content.res.Resources.Theme!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas!);
+ method public int getOpacity();
+ method public boolean isRunning();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public static void registerAnimationCallback(android.graphics.drawable.Drawable!, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback!);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setColorFilter(int, android.graphics.PorterDuff.Mode!);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public static boolean unregisterAnimationCallback(android.graphics.drawable.Drawable!, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback!);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable-animated/api/res-1.2.0-beta01.txt b/vectordrawable/vectordrawable-animated/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vectordrawable/vectordrawable-animated/api/res-1.2.0-beta01.txt
diff --git a/vectordrawable/vectordrawable-animated/api/restricted_1.2.0-beta01.txt b/vectordrawable/vectordrawable-animated/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..c094267
--- /dev/null
+++ b/vectordrawable/vectordrawable-animated/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public interface Animatable2Compat extends android.graphics.drawable.Animatable {
+ method public void clearAnimationCallbacks();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ }
+
+ public abstract static class Animatable2Compat.AnimationCallback {
+ ctor public Animatable2Compat.AnimationCallback();
+ method public void onAnimationEnd(android.graphics.drawable.Drawable!);
+ method public void onAnimationStart(android.graphics.drawable.Drawable!);
+ }
+
+ public class AnimatedVectorDrawableCompat extends android.graphics.drawable.Drawable implements androidx.vectordrawable.graphics.drawable.Animatable2Compat androidx.core.graphics.drawable.TintAwareDrawable {
+ method public void clearAnimationCallbacks();
+ method public static void clearAnimationCallbacks(android.graphics.drawable.Drawable!);
+ method public static androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat? create(android.content.Context, @DrawableRes int);
+ method public static androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat! createFromXmlInner(android.content.Context!, android.content.res.Resources!, org.xmlpull.v1.XmlPullParser!, android.util.AttributeSet!, android.content.res.Resources.Theme!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas!);
+ method public int getOpacity();
+ method public boolean isRunning();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public static void registerAnimationCallback(android.graphics.drawable.Drawable!, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback!);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setColorFilter(int, android.graphics.PorterDuff.Mode!);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback);
+ method public static boolean unregisterAnimationCallback(android.graphics.drawable.Drawable!, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class AnimationUtilsCompat {
+ method public static android.view.animation.Interpolator! loadInterpolator(android.content.Context!, int) throws android.content.res.Resources.NotFoundException;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class AnimatorInflaterCompat {
+ method public static android.animation.Animator! loadAnimator(android.content.Context!, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static android.animation.Animator! loadAnimator(android.content.Context!, android.content.res.Resources!, android.content.res.Resources.Theme!, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
+ method public static android.animation.Animator! loadAnimator(android.content.Context!, android.content.res.Resources!, android.content.res.Resources.Theme!, @AnimatorRes int, float) throws android.content.res.Resources.NotFoundException;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ArgbEvaluator implements android.animation.TypeEvaluator {
+ ctor public ArgbEvaluator();
+ method public Object! evaluate(float, Object!, Object!);
+ method public static androidx.vectordrawable.graphics.drawable.ArgbEvaluator! getInstance();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PathInterpolatorCompat implements android.view.animation.Interpolator {
+ ctor public PathInterpolatorCompat(android.content.Context!, android.util.AttributeSet!, org.xmlpull.v1.XmlPullParser!);
+ ctor public PathInterpolatorCompat(android.content.res.Resources!, android.content.res.Resources.Theme!, android.util.AttributeSet!, org.xmlpull.v1.XmlPullParser!);
+ method public float getInterpolation(float);
+ field public static final double EPSILON = 1.0E-5;
+ field public static final int MAX_NUM_POINTS = 3000; // 0xbb8
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable-seekable/api/1.0.0-beta01.txt b/vectordrawable/vectordrawable-seekable/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..dab885b
--- /dev/null
+++ b/vectordrawable/vectordrawable-seekable/api/1.0.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public class SeekableAnimatedVectorDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable {
+ method public void clearAnimationCallbacks();
+ method public static androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable? create(android.content.Context, @DrawableRes int);
+ method public static androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas);
+ method @IntRange(from=0) public long getCurrentPlayTime();
+ method @Deprecated public int getOpacity();
+ method public long getTotalDuration();
+ method public boolean isPaused();
+ method public boolean isRunning();
+ method public void pause();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable.AnimationCallback);
+ method public void resume();
+ method public void setAlpha(@IntRange(from=0, to=255) int);
+ method public void setColorFilter(android.graphics.ColorFilter?);
+ method public void setCurrentPlayTime(@IntRange(from=0) long);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable.AnimationCallback);
+ }
+
+ public abstract static class SeekableAnimatedVectorDrawable.AnimationCallback {
+ ctor public SeekableAnimatedVectorDrawable.AnimationCallback();
+ method public void onAnimationEnd(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationPause(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationResume(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationStart(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationUpdate(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable-seekable/api/public_plus_experimental_1.0.0-beta01.txt b/vectordrawable/vectordrawable-seekable/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..dab885b
--- /dev/null
+++ b/vectordrawable/vectordrawable-seekable/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public class SeekableAnimatedVectorDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable {
+ method public void clearAnimationCallbacks();
+ method public static androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable? create(android.content.Context, @DrawableRes int);
+ method public static androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas);
+ method @IntRange(from=0) public long getCurrentPlayTime();
+ method @Deprecated public int getOpacity();
+ method public long getTotalDuration();
+ method public boolean isPaused();
+ method public boolean isRunning();
+ method public void pause();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable.AnimationCallback);
+ method public void resume();
+ method public void setAlpha(@IntRange(from=0, to=255) int);
+ method public void setColorFilter(android.graphics.ColorFilter?);
+ method public void setCurrentPlayTime(@IntRange(from=0) long);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable.AnimationCallback);
+ }
+
+ public abstract static class SeekableAnimatedVectorDrawable.AnimationCallback {
+ ctor public SeekableAnimatedVectorDrawable.AnimationCallback();
+ method public void onAnimationEnd(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationPause(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationResume(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationStart(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationUpdate(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable-seekable/api/res-1.0.0-beta01.txt b/vectordrawable/vectordrawable-seekable/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vectordrawable/vectordrawable-seekable/api/res-1.0.0-beta01.txt
diff --git a/vectordrawable/vectordrawable-seekable/api/restricted_1.0.0-beta01.txt b/vectordrawable/vectordrawable-seekable/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..dab885b
--- /dev/null
+++ b/vectordrawable/vectordrawable-seekable/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public class SeekableAnimatedVectorDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable {
+ method public void clearAnimationCallbacks();
+ method public static androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable? create(android.content.Context, @DrawableRes int);
+ method public static androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas);
+ method @IntRange(from=0) public long getCurrentPlayTime();
+ method @Deprecated public int getOpacity();
+ method public long getTotalDuration();
+ method public boolean isPaused();
+ method public boolean isRunning();
+ method public void pause();
+ method public void registerAnimationCallback(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable.AnimationCallback);
+ method public void resume();
+ method public void setAlpha(@IntRange(from=0, to=255) int);
+ method public void setColorFilter(android.graphics.ColorFilter?);
+ method public void setCurrentPlayTime(@IntRange(from=0) long);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable.AnimationCallback);
+ }
+
+ public abstract static class SeekableAnimatedVectorDrawable.AnimationCallback {
+ ctor public SeekableAnimatedVectorDrawable.AnimationCallback();
+ method public void onAnimationEnd(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationPause(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationResume(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationStart(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ method public void onAnimationUpdate(androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable/api/1.2.0-beta01.txt b/vectordrawable/vectordrawable/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..5a6c00b
--- /dev/null
+++ b/vectordrawable/vectordrawable/api/1.2.0-beta01.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public class VectorDrawableCompat extends android.graphics.drawable.Drawable {
+ method public static androidx.vectordrawable.graphics.drawable.VectorDrawableCompat? create(android.content.res.Resources, @DrawableRes int, android.content.res.Resources.Theme?);
+ method public static androidx.vectordrawable.graphics.drawable.VectorDrawableCompat! createFromXmlInner(android.content.res.Resources!, org.xmlpull.v1.XmlPullParser!, android.util.AttributeSet!, android.content.res.Resources.Theme!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas!);
+ method public int getOpacity();
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setColorFilter(int, android.graphics.PorterDuff.Mode!);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable/api/public_plus_experimental_1.2.0-beta01.txt b/vectordrawable/vectordrawable/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..5a6c00b
--- /dev/null
+++ b/vectordrawable/vectordrawable/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public class VectorDrawableCompat extends android.graphics.drawable.Drawable {
+ method public static androidx.vectordrawable.graphics.drawable.VectorDrawableCompat? create(android.content.res.Resources, @DrawableRes int, android.content.res.Resources.Theme?);
+ method public static androidx.vectordrawable.graphics.drawable.VectorDrawableCompat! createFromXmlInner(android.content.res.Resources!, org.xmlpull.v1.XmlPullParser!, android.util.AttributeSet!, android.content.res.Resources.Theme!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas!);
+ method public int getOpacity();
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setColorFilter(int, android.graphics.PorterDuff.Mode!);
+ }
+
+}
+
diff --git a/vectordrawable/vectordrawable/api/res-1.2.0-beta01.txt b/vectordrawable/vectordrawable/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vectordrawable/vectordrawable/api/res-1.2.0-beta01.txt
diff --git a/vectordrawable/vectordrawable/api/restricted_1.2.0-beta01.txt b/vectordrawable/vectordrawable/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..30d66c1
--- /dev/null
+++ b/vectordrawable/vectordrawable/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.vectordrawable.graphics.drawable {
+
+ public class VectorDrawableCompat extends android.graphics.drawable.Drawable implements androidx.core.graphics.drawable.TintAwareDrawable {
+ method public static androidx.vectordrawable.graphics.drawable.VectorDrawableCompat? create(android.content.res.Resources, @DrawableRes int, android.content.res.Resources.Theme?);
+ method public static androidx.vectordrawable.graphics.drawable.VectorDrawableCompat! createFromXmlInner(android.content.res.Resources!, org.xmlpull.v1.XmlPullParser!, android.util.AttributeSet!, android.content.res.Resources.Theme!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas!);
+ method public int getOpacity();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public float getPixelSize();
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setColorFilter(int, android.graphics.PorterDuff.Mode!);
+ }
+
+}
+
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index b067fae..ff4502f 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -92,6 +92,13 @@
property public final boolean reverseLayout;
}
+ public final class CurvedSizeKt {
+ method public static androidx.wear.compose.foundation.CurvedModifier angularSize(androidx.wear.compose.foundation.CurvedModifier, float angularSizeDegrees);
+ method public static androidx.wear.compose.foundation.CurvedModifier radialSize(androidx.wear.compose.foundation.CurvedModifier, float radialSize);
+ method public static androidx.wear.compose.foundation.CurvedModifier size(androidx.wear.compose.foundation.CurvedModifier, float angularSizeDegrees, float radialSize);
+ method public static androidx.wear.compose.foundation.CurvedModifier sizeIn(androidx.wear.compose.foundation.CurvedModifier, optional float angularMinDegrees, optional float angularMaxDegrees, optional float radialSizeMin, optional float radialSizeMax);
+ }
+
public final class CurvedTextStyle {
ctor public CurvedTextStyle(optional long color, optional long fontSize, optional long background);
ctor public CurvedTextStyle(androidx.compose.ui.text.TextStyle style);
diff --git a/wear/compose/compose-foundation/api/public_plus_experimental_current.txt b/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
index b067fae..ff4502f 100644
--- a/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
@@ -92,6 +92,13 @@
property public final boolean reverseLayout;
}
+ public final class CurvedSizeKt {
+ method public static androidx.wear.compose.foundation.CurvedModifier angularSize(androidx.wear.compose.foundation.CurvedModifier, float angularSizeDegrees);
+ method public static androidx.wear.compose.foundation.CurvedModifier radialSize(androidx.wear.compose.foundation.CurvedModifier, float radialSize);
+ method public static androidx.wear.compose.foundation.CurvedModifier size(androidx.wear.compose.foundation.CurvedModifier, float angularSizeDegrees, float radialSize);
+ method public static androidx.wear.compose.foundation.CurvedModifier sizeIn(androidx.wear.compose.foundation.CurvedModifier, optional float angularMinDegrees, optional float angularMaxDegrees, optional float radialSizeMin, optional float radialSizeMax);
+ }
+
public final class CurvedTextStyle {
ctor public CurvedTextStyle(optional long color, optional long fontSize, optional long background);
ctor public CurvedTextStyle(androidx.compose.ui.text.TextStyle style);
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index b067fae..ff4502f 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -92,6 +92,13 @@
property public final boolean reverseLayout;
}
+ public final class CurvedSizeKt {
+ method public static androidx.wear.compose.foundation.CurvedModifier angularSize(androidx.wear.compose.foundation.CurvedModifier, float angularSizeDegrees);
+ method public static androidx.wear.compose.foundation.CurvedModifier radialSize(androidx.wear.compose.foundation.CurvedModifier, float radialSize);
+ method public static androidx.wear.compose.foundation.CurvedModifier size(androidx.wear.compose.foundation.CurvedModifier, float angularSizeDegrees, float radialSize);
+ method public static androidx.wear.compose.foundation.CurvedModifier sizeIn(androidx.wear.compose.foundation.CurvedModifier, optional float angularMinDegrees, optional float angularMaxDegrees, optional float radialSizeMin, optional float radialSizeMax);
+ }
+
public final class CurvedTextStyle {
ctor public CurvedTextStyle(optional long color, optional long fontSize, optional long background);
ctor public CurvedTextStyle(androidx.compose.ui.text.TextStyle style);
diff --git a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/CurvedWorldSample.kt b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/CurvedWorldSample.kt
index cdcb32b..d201d69 100644
--- a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/CurvedWorldSample.kt
+++ b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/CurvedWorldSample.kt
@@ -32,11 +32,13 @@
import androidx.wear.compose.foundation.ArcPaddingValues
import androidx.wear.compose.foundation.CurvedAlignment
import androidx.wear.compose.foundation.CurvedLayout
+import androidx.wear.compose.foundation.CurvedModifier
import androidx.wear.compose.foundation.CurvedTextStyle
import androidx.wear.compose.foundation.basicCurvedText
import androidx.wear.compose.foundation.curvedColumn
import androidx.wear.compose.foundation.curvedComposable
import androidx.wear.compose.foundation.curvedRow
+import androidx.wear.compose.foundation.size
@Sampled
@Composable
@@ -140,4 +142,35 @@
)
}
}
+}
+
+@Sampled
+@Composable
+fun CurvedFixedSize() {
+ // Note the "gap" in the middle of the two curved texts has a specified size.
+ CurvedLayout(modifier = Modifier.fillMaxSize()) {
+ basicCurvedText(
+ "Fixed",
+ style = {
+ CurvedTextStyle(
+ fontSize = 16.sp,
+ color = Color.Black,
+ background = Color.White
+ )
+ }
+ )
+ curvedRow(
+ modifier = CurvedModifier.size(angularSizeDegrees = 5f, radialSize = 30.dp),
+ ) { }
+ basicCurvedText(
+ "Gap",
+ style = {
+ CurvedTextStyle(
+ fontSize = 16.sp,
+ color = Color.Black,
+ background = Color.White
+ )
+ }
+ )
+ }
}
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt
index 3a92f7c..633bda7 100644
--- a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt
@@ -17,10 +17,6 @@
package androidx.wear.compose.foundation
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.sp
import androidx.test.filters.FlakyTest
@@ -59,45 +55,3 @@
}
}
}
-
-internal data class CapturedInfo(
- var measuresCount: Int = 0,
- var layoutsCount: Int = 0,
- var drawCount: Int = 0
-) {
- // Used in CurvedLayoutTest
- var lastLayoutInfo: CurvedLayoutInfo? = null
- fun reset() {
- measuresCount = 0
- layoutsCount = 0
- drawCount = 0
- lastLayoutInfo = null
- }
-}
-
-internal fun CurvedModifier.spy(capturedInfo: CapturedInfo) =
- this.then { wrapped -> SpyCurvedChildWrapper(capturedInfo, wrapped) }
-
-internal class SpyCurvedChildWrapper(private val capturedInfo: CapturedInfo, wrapped: CurvedChild) :
- BaseCurvedChildWrapper(wrapped) {
-
- override fun MeasureScope.initializeMeasure(
- measurables: List<Measurable>,
- index: Int
- ): Int = with(wrapped) {
- capturedInfo.measuresCount++
- initializeMeasure(measurables, index)
- }
-
- override fun (Placeable.PlacementScope).placeIfNeeded() = with(wrapped) {
- capturedInfo.lastLayoutInfo = layoutInfo
- capturedInfo.layoutsCount++
- placeIfNeeded()
- }
-
- override fun DrawScope.draw() = with(wrapped) {
- capturedInfo.lastLayoutInfo = layoutInfo
- capturedInfo.drawCount++
- draw()
- }
-}
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedSizeTest.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedSizeTest.kt
new file mode 100644
index 0000000..bed1afc
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedSizeTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation
+
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import org.junit.Rule
+import org.junit.Test
+
+class CurvedSizeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun proper_nested_sizes_work() = nested_size_test(60f, 30.dp, 30f, 20.dp)
+
+ @Test
+ fun inverted_nested_sizes_work() = nested_size_test(30f, 20.dp, 60f, 30.dp)
+
+ @Test
+ fun equal_nested_sizes_work() = nested_size_test(30f, 20.dp, 30f, 20.dp)
+
+ private fun nested_size_test(
+ angle: Float,
+ thickness: Dp,
+ innerAngle: Float,
+ innerThickness: Dp
+ ) {
+ val capturedInfo = CapturedInfo()
+ val innerCapturedInfo = CapturedInfo()
+ var thicknessPx = 0f
+ var innerThicknessPx = 0f
+ rule.setContent {
+ with(LocalDensity.current) {
+ thicknessPx = thickness.toPx()
+ innerThicknessPx = innerThickness.toPx()
+ }
+ CurvedLayout {
+ curvedRow(
+ modifier = CurvedModifier
+ .spy(capturedInfo)
+ .size(angle, thickness)
+ .spy(innerCapturedInfo)
+ .size(innerAngle, innerThickness)
+ ) { }
+ }
+ }
+
+ rule.runOnIdle {
+ capturedInfo.checkDimensions(angle, thicknessPx)
+ innerCapturedInfo.checkParentDimensions(angle, thicknessPx)
+ innerCapturedInfo.checkDimensions(innerAngle, innerThicknessPx)
+ innerCapturedInfo.checkPositionOnParent(0f, 0f)
+ }
+ }
+}
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/SpyModifier.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/SpyModifier.kt
new file mode 100644
index 0000000..1c112aa
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/SpyModifier.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import org.junit.Assert
+
+/**
+ * Class used to capture information regarding measure/layout.
+ * This is used through CurvedModifier.spy, and captures information on that point in the modifier
+ * chain.
+ * This is also the single point of access to the internals of CurvedLayout, so tests are easier to
+ * refactor if we change something there.
+ */
+internal data class CapturedInfo(
+ // Counters
+ var measuresCount: Int = 0,
+ var layoutsCount: Int = 0,
+ var drawCount: Int = 0
+) {
+ // Captured information
+ var lastLayoutInfo: CurvedLayoutInfo? = null
+ var parentOuterRadius: Float = 0f
+ var parentThickness: Float = 0f
+ var parentStartAngleRadians: Float = 0f
+ var parentSweepRadians: Float = 0f
+
+ fun reset() {
+ measuresCount = 0
+ layoutsCount = 0
+ drawCount = 0
+ lastLayoutInfo = null
+ parentOuterRadius = 0f
+ parentThickness = 0f
+ parentStartAngleRadians = 0f
+ parentSweepRadians = 0f
+ }
+}
+
+internal const val FINE_FLOAT_TOLERANCE = 0.001f
+
+internal fun CapturedInfo.checkDimensions(
+ expectedAngleDegrees: Float,
+ expectedThicknessPx: Float
+) {
+ Assert.assertEquals(
+ expectedAngleDegrees,
+ lastLayoutInfo!!.sweepRadians.toDegrees(),
+ FINE_FLOAT_TOLERANCE
+ )
+ Assert.assertEquals(
+ expectedThicknessPx,
+ lastLayoutInfo!!.thickness,
+ FINE_FLOAT_TOLERANCE
+ )
+}
+
+internal fun CapturedInfo.checkParentDimensions(
+ expectedAngleDegrees: Float,
+ expectedThicknessPx: Float,
+) {
+ Assert.assertEquals(
+ expectedAngleDegrees,
+ parentSweepRadians.toDegrees(),
+ FINE_FLOAT_TOLERANCE
+ )
+ Assert.assertEquals(
+ expectedThicknessPx,
+ parentThickness,
+ FINE_FLOAT_TOLERANCE
+ )
+}
+
+internal fun CapturedInfo.checkPositionOnParent(
+ expectedAngularPositionDegrees: Float,
+ expectedRadialPositionPx: Float
+) {
+ Assert.assertEquals(
+ expectedAngularPositionDegrees,
+ (lastLayoutInfo!!.startAngleRadians - parentStartAngleRadians).toDegrees(),
+ FINE_FLOAT_TOLERANCE
+ )
+ Assert.assertEquals(
+ expectedRadialPositionPx,
+ parentOuterRadius - lastLayoutInfo!!.outerRadius,
+ FINE_FLOAT_TOLERANCE
+ )
+}
+
+internal fun CurvedModifier.spy(capturedInfo: CapturedInfo) =
+ this.then { wrapped -> SpyCurvedChildWrapper(capturedInfo, wrapped) }
+
+internal class SpyCurvedChildWrapper(private val capturedInfo: CapturedInfo, wrapped: CurvedChild) :
+ BaseCurvedChildWrapper(wrapped) {
+
+ override fun MeasureScope.initializeMeasure(
+ measurables: List<Measurable>,
+ index: Int
+ ): Int = with(wrapped) {
+ capturedInfo.measuresCount++
+ initializeMeasure(measurables, index)
+ }
+
+ override fun doRadialPosition(
+ parentOuterRadius: Float,
+ parentThickness: Float,
+ ): PartialLayoutInfo {
+ capturedInfo.parentOuterRadius = parentOuterRadius
+ capturedInfo.parentThickness = parentThickness
+ return wrapped.radialPosition(
+ parentOuterRadius,
+ parentThickness,
+ )
+ }
+
+ override fun doAngularPosition(
+ parentStartAngleRadians: Float,
+ parentSweepRadians: Float,
+ centerOffset: Offset
+ ): Float {
+ capturedInfo.parentStartAngleRadians = parentStartAngleRadians
+ capturedInfo.parentSweepRadians = parentSweepRadians
+ return wrapped.angularPosition(
+ parentStartAngleRadians,
+ parentSweepRadians,
+ centerOffset
+ )
+ }
+
+ override fun (Placeable.PlacementScope).placeIfNeeded() = with(wrapped) {
+ capturedInfo.lastLayoutInfo = layoutInfo
+ capturedInfo.layoutsCount++
+ placeIfNeeded()
+ }
+
+ override fun DrawScope.draw() = with(wrapped) {
+ capturedInfo.lastLayoutInfo = layoutInfo
+ capturedInfo.drawCount++
+ draw()
+ }
+}
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedLayout.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedLayout.kt
index c9c7ace..aa01aa8 100644
--- a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedLayout.kt
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedLayout.kt
@@ -232,8 +232,8 @@
/**
* Initialize the Child to do a measure pass.
*
- * @param measurables: The measurables on the CurvedLayout, used to map to our nodes as we walk
- * the tree.
+ * @param measurables: The measurables on the CurvedLayout, used to map to the compose-ui nodes
+ * we generated in [SubComposition] as we walk the tree.
* @param index: The current index in the measurables array
* @return The new index in the measurables array, taking into account how many items we
* mapped.
@@ -254,10 +254,10 @@
/**
* Compute our radial positioning relative to the parent.
*
- * Note that parentInnerRadius & parentOuterRadius are similar to min & max Constraints in
- * compose, but for curved components is important to know absolute values of the possible,
- * radius. Curved things draw very differently on radius 50 to 100 than on radius 300 to 350.
- *
+ * Note that parentInnerRadius (which is parentOuterRadius - parentThickness) &
+ * parentOuterRadius are similar to min & max Constraints in compose, but for curved components
+ * is important to know absolute values of the possible radius. Curved things draw very
+ * differently on radius 50 to 100 than on radius 300 to 350.
*
* @param parentOuterRadius The outer radius of the space we have in the parent container
* @param parentThickness The thickness of the space we have in the parent container
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt
new file mode 100644
index 0000000..3e480aa
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * Specify the dimensions of the content to be restricted between the given bounds.
+ *
+ * @param angularMinDegrees the minimum angle (in degrees) for the content.
+ * @param angularMaxDegrees the maximum angle (in degrees) for the content.
+ * @param radialSizeMin the minimum radialSize (thickness) for the content.
+ * @param radialSizeMax the maximum radialSize (thickness) for the content.
+ */
+public fun CurvedModifier.sizeIn(
+ /* @FloatRange(from = 0f, to = 360f) */
+ angularMinDegrees: Float = 0f,
+ /* @FloatRange(from = 0f, to = 360f) */
+ angularMaxDegrees: Float = 360f,
+ radialSizeMin: Dp = 0.dp,
+ radialSizeMax: Dp = Dp.Infinity,
+) = this.then { child -> SizeWrapper(
+ child,
+ angularMinDegrees = angularMinDegrees,
+ angularMaxDegrees = angularMaxDegrees,
+ thicknessMin = radialSizeMin,
+ thicknessMax = radialSizeMax
+) }
+
+/**
+ * Specify the angular size and thickness for the content.
+ *
+ * @sample androidx.wear.compose.foundation.samples.CurvedFixedSize
+ *
+ * @param angularSizeDegrees Indicates the angular size (sweep) of the content.
+ * @param radialSize Indicates the radialSize (thickness) of the content.
+ */
+public fun CurvedModifier.size(angularSizeDegrees: Float, radialSize: Dp) = sizeIn(
+ /* @FloatRange(from = 0f, to = 360f) */
+ angularMinDegrees = angularSizeDegrees,
+ /* @FloatRange(from = 0f, to = 360f) */
+ angularMaxDegrees = angularSizeDegrees,
+ radialSizeMin = radialSize,
+ radialSizeMax = radialSize
+)
+
+/**
+ * Specify the angular size (sweep) for the content.
+ *
+ * @param angularSizeDegrees Indicates the angular size of the content.
+ */
+public fun CurvedModifier.angularSize(angularSizeDegrees: Float) = sizeIn(
+ angularMinDegrees = angularSizeDegrees,
+ angularMaxDegrees = angularSizeDegrees
+)
+
+/**
+ * Specify the radialSize (thickness) for the content.
+ *
+ * @param radialSize Indicates the thickness of the content.
+ */
+public fun CurvedModifier.radialSize(radialSize: Dp) = sizeIn(
+ radialSizeMin = radialSize,
+ radialSizeMax = radialSize
+)
+
+internal class SizeWrapper(
+ child: CurvedChild,
+ val angularMinDegrees: Float,
+ val angularMaxDegrees: Float,
+ val thicknessMin: Dp,
+ val thicknessMax: Dp,
+) : BaseCurvedChildWrapper(child) {
+ private var thicknessMinPx = 0f
+ private var thicknessMaxPx = 0f
+
+ override fun MeasureScope.initializeMeasure(
+ measurables: List<Measurable>,
+ index: Int
+ ): Int {
+ thicknessMinPx = thicknessMin.toPx()
+ thicknessMaxPx = thicknessMax.toPx()
+ return with(wrapped) {
+ // Call initializeMeasure on wrapper (while still having the MeasureScope scope)
+ initializeMeasure(measurables, index)
+ }
+ }
+
+ override fun doEstimateThickness(maxRadius: Float) =
+ wrapped.estimateThickness(maxRadius).coerceIn(thicknessMinPx, thicknessMaxPx)
+
+ override fun doRadialPosition(
+ parentOuterRadius: Float,
+ parentThickness: Float
+ ): PartialLayoutInfo {
+ val partialLayoutInfo = wrapped.radialPosition(
+ parentOuterRadius,
+ estimatedThickness
+ )
+ return PartialLayoutInfo(
+ partialLayoutInfo.sweepRadians.coerceIn(
+ angularMinDegrees.toRadians(),
+ angularMaxDegrees.toRadians()
+ ),
+ parentOuterRadius,
+ thickness = estimatedThickness,
+ measureRadius = partialLayoutInfo.measureRadius +
+ partialLayoutInfo.outerRadius - parentOuterRadius
+ )
+ }
+
+ override fun doAngularPosition(
+ parentStartAngleRadians: Float,
+ parentSweepRadians: Float,
+ centerOffset: Offset
+ ): Float {
+ wrapped.angularPosition(
+ parentStartAngleRadians,
+ parentSweepRadians = sweepRadians,
+ centerOffset
+ )
+ return parentStartAngleRadians
+ }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index f419366..46fb9d3 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -572,15 +572,16 @@
}
public final class ToggleChipDefaults {
- method @androidx.compose.runtime.Composable public void CheckboxIcon(boolean checked);
- method @androidx.compose.runtime.Composable public void RadioIcon(boolean checked);
- method @androidx.compose.runtime.Composable public void SwitchIcon(boolean checked);
+ method public androidx.compose.ui.graphics.vector.ImageVector checkboxIcon(boolean checked);
method public androidx.compose.ui.graphics.vector.ImageVector getCheckboxOn();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method public float getIconSize();
method public androidx.compose.ui.graphics.vector.ImageVector getRadioOff();
method public androidx.compose.ui.graphics.vector.ImageVector getRadioOn();
+ method public androidx.compose.ui.graphics.vector.ImageVector radioIcon(boolean checked);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SplitToggleChipColors splitToggleChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long checkedToggleControlTintColor, optional long uncheckedToggleControlTintColor, optional long splitBackgroundOverlayColor);
+ method public androidx.compose.ui.graphics.vector.ImageVector switchIcon(boolean checked);
+ method @androidx.compose.runtime.Composable public long switchUncheckedIconColor();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ToggleChipColors toggleChipColors(optional long checkedStartBackgroundColor, optional long checkedEndBackgroundColor, optional long checkedContentColor, optional long checkedSecondaryContentColor, optional long checkedToggleControlTintColor, optional long uncheckedStartBackgroundColor, optional long uncheckedEndBackgroundColor, optional long uncheckedContentColor, optional long uncheckedSecondaryContentColor, optional long uncheckedToggleControlTintColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
property public final androidx.compose.ui.graphics.vector.ImageVector CheckboxOn;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
@@ -591,8 +592,8 @@
}
public final class ToggleChipKt {
- method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
- method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
}
@androidx.compose.runtime.Immutable public final class Typography {
diff --git a/wear/compose/compose-material/api/public_plus_experimental_current.txt b/wear/compose/compose-material/api/public_plus_experimental_current.txt
index b06cca9..9bb204a 100644
--- a/wear/compose/compose-material/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material/api/public_plus_experimental_current.txt
@@ -651,15 +651,16 @@
}
public final class ToggleChipDefaults {
- method @androidx.compose.runtime.Composable public void CheckboxIcon(boolean checked);
- method @androidx.compose.runtime.Composable public void RadioIcon(boolean checked);
- method @androidx.compose.runtime.Composable public void SwitchIcon(boolean checked);
+ method public androidx.compose.ui.graphics.vector.ImageVector checkboxIcon(boolean checked);
method public androidx.compose.ui.graphics.vector.ImageVector getCheckboxOn();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method public float getIconSize();
method public androidx.compose.ui.graphics.vector.ImageVector getRadioOff();
method public androidx.compose.ui.graphics.vector.ImageVector getRadioOn();
+ method public androidx.compose.ui.graphics.vector.ImageVector radioIcon(boolean checked);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SplitToggleChipColors splitToggleChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long checkedToggleControlTintColor, optional long uncheckedToggleControlTintColor, optional long splitBackgroundOverlayColor);
+ method public androidx.compose.ui.graphics.vector.ImageVector switchIcon(boolean checked);
+ method @androidx.compose.runtime.Composable public long switchUncheckedIconColor();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ToggleChipColors toggleChipColors(optional long checkedStartBackgroundColor, optional long checkedEndBackgroundColor, optional long checkedContentColor, optional long checkedSecondaryContentColor, optional long checkedToggleControlTintColor, optional long uncheckedStartBackgroundColor, optional long uncheckedEndBackgroundColor, optional long uncheckedContentColor, optional long uncheckedSecondaryContentColor, optional long uncheckedToggleControlTintColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
property public final androidx.compose.ui.graphics.vector.ImageVector CheckboxOn;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
@@ -670,8 +671,8 @@
}
public final class ToggleChipKt {
- method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
- method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
}
@androidx.compose.runtime.Immutable public final class Typography {
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index f419366..46fb9d3 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -572,15 +572,16 @@
}
public final class ToggleChipDefaults {
- method @androidx.compose.runtime.Composable public void CheckboxIcon(boolean checked);
- method @androidx.compose.runtime.Composable public void RadioIcon(boolean checked);
- method @androidx.compose.runtime.Composable public void SwitchIcon(boolean checked);
+ method public androidx.compose.ui.graphics.vector.ImageVector checkboxIcon(boolean checked);
method public androidx.compose.ui.graphics.vector.ImageVector getCheckboxOn();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method public float getIconSize();
method public androidx.compose.ui.graphics.vector.ImageVector getRadioOff();
method public androidx.compose.ui.graphics.vector.ImageVector getRadioOn();
+ method public androidx.compose.ui.graphics.vector.ImageVector radioIcon(boolean checked);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SplitToggleChipColors splitToggleChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long checkedToggleControlTintColor, optional long uncheckedToggleControlTintColor, optional long splitBackgroundOverlayColor);
+ method public androidx.compose.ui.graphics.vector.ImageVector switchIcon(boolean checked);
+ method @androidx.compose.runtime.Composable public long switchUncheckedIconColor();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ToggleChipColors toggleChipColors(optional long checkedStartBackgroundColor, optional long checkedEndBackgroundColor, optional long checkedContentColor, optional long checkedSecondaryContentColor, optional long checkedToggleControlTintColor, optional long uncheckedStartBackgroundColor, optional long uncheckedEndBackgroundColor, optional long uncheckedContentColor, optional long uncheckedSecondaryContentColor, optional long uncheckedToggleControlTintColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
property public final androidx.compose.ui.graphics.vector.ImageVector CheckboxOn;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
@@ -591,8 +592,8 @@
}
public final class ToggleChipKt {
- method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
- method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
}
@androidx.compose.runtime.Immutable public final class Typography {
diff --git a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt
index ba004f2..dbb84ac 100644
--- a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt
+++ b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt
@@ -36,7 +36,6 @@
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.wear.compose.material.ExperimentalWearMaterialApi
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.ScalingLazyColumn
import androidx.wear.compose.material.Text
@@ -93,7 +92,6 @@
private var itemSizeDp: Dp = 10.dp
private var defaultItemSpacingDp: Dp = 4.dp
- @OptIn(ExperimentalWearMaterialApi::class)
@Composable
override fun MeasuredContent() {
ScalingLazyColumn(
diff --git a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ToggleChipBenchmark.kt b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ToggleChipBenchmark.kt
index fea555e..0800506 100644
--- a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ToggleChipBenchmark.kt
+++ b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ToggleChipBenchmark.kt
@@ -27,6 +27,7 @@
import androidx.compose.testutils.benchmark.benchmarkLayoutPerf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.SplitToggleChip
import androidx.wear.compose.material.Text
@@ -87,7 +88,12 @@
onCheckedChange = {},
enabled = false,
label = { Text("Label") },
- toggleControl = { ToggleChipDefaults.CheckboxIcon(checked = true) },
+ toggleControl = {
+ Icon(
+ imageVector = ToggleChipDefaults.checkboxIcon(checked = true),
+ contentDescription = null
+ )
+ },
onClick = {},
)
}
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToDismissBoxSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToDismissBoxSample.kt
index db0d84d..65e66eb 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToDismissBoxSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToDismissBoxSample.kt
@@ -37,11 +37,13 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.SplitToggleChip
import androidx.wear.compose.material.SwipeToDismissBox
import androidx.wear.compose.material.SwipeToDismissValue
import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.ToggleChipDefaults
import androidx.wear.compose.material.edgeSwipeToDismiss
import androidx.wear.compose.material.rememberSwipeToDismissBoxState
@@ -118,7 +120,15 @@
label = { Text("Item details") },
modifier = Modifier.height(40.dp),
onCheckedChange = { v -> checked.value = v },
- onClick = { showMainScreen = false }
+ onClick = { showMainScreen = false },
+ toggleControl = {
+ Icon(
+ imageVector = ToggleChipDefaults.checkboxIcon(
+ checked = checked.value
+ ),
+ contentDescription = null,
+ )
+ }
)
}
}
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt
index 524d453..cd48f3d 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt
@@ -47,8 +47,17 @@
Text("With secondary label", maxLines = 1, overflow = TextOverflow.Ellipsis)
},
checked = checked,
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to ToggleChipDefaults.switchUncheckedIconColor()
+ // rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults.switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = checked)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(checked = checked),
+ contentDescription = if (checked) "On" else "Off",
+ )
},
onCheckedChange = { checked = it },
appIcon = {
@@ -70,7 +79,10 @@
label = { Text("Split with CheckboxIcon") },
checked = checked,
toggleControl = {
- ToggleChipDefaults.CheckboxIcon(checked = checked)
+ Icon(
+ imageVector = ToggleChipDefaults.checkboxIcon(checked = checked),
+ contentDescription = if (checked) "Checked" else "Unchecked"
+ )
},
onCheckedChange = { checked = it },
onClick = {
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/InlineSliderTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/InlineSliderTest.kt
index 2e9bc51..41c1ec5 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/InlineSliderTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/InlineSliderTest.kt
@@ -41,7 +41,6 @@
import org.junit.Rule
import org.junit.Test
-@ExperimentalWearMaterialApi
public class InlineSliderTest {
@get:Rule
public val rule = createComposeRule()
@@ -410,7 +409,6 @@
private val DefaultIconWidth = 24.dp
}
-@ExperimentalWearMaterialApi
public class IntegerInlineSliderTest {
@get:Rule
public val rule = createComposeRule()
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/StepperTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/StepperTest.kt
index 078440c..e617d6b 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/StepperTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/StepperTest.kt
@@ -44,7 +44,6 @@
import org.junit.Rule
import org.junit.Test
-@ExperimentalWearMaterialApi
public class StepperTest {
@get:Rule
public val rule = createComposeRule()
@@ -363,7 +362,6 @@
private val DefaultIconHeight = 24.dp
}
-@ExperimentalWearMaterialApi
public class IntegerStepperTest {
@get:Rule
public val rule = createComposeRule()
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt
index 3acb401..3733148 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt
@@ -92,13 +92,13 @@
* @param onCheckedChange Callback to be invoked when this buttons checked/selected status is
* @param label A slot for providing the chip's main label. The contents are expected to be text
* which is "start" aligned.
- * @param modifier Modifier to be applied to the chip
* @param toggleControl A slot for providing the chip's toggle controls(s). The contents are
* expected to be a horizontally and vertically centre aligned icon of size
- * [ToggleChipDefaults.IconSize]. Three built-in types of toggle control are supported and can be
- * obtained from [ToggleChipDefaults.SwitchIcon], [ToggleChipDefaults.RadioIcon] and
- * [ToggleChipDefaults.CheckboxIcon]. In order to correctly render when the Chip is not enabled the
+ * [ToggleChipDefaults.IconSize]. Three built-in types of toggle control are supported and
+ * [ImageVector]s can be obtained from [ToggleChipDefaults.switchIcon], [ToggleChipDefaults.radioIcon] and
+ * [ToggleChipDefaults.checkboxIcon]. In order to correctly render when the Chip is not enabled the
* icon must set its alpha value to [LocalContentAlpha].
+ * @param modifier Modifier to be applied to the chip
* @param appIcon An optional slot for providing an icon to indicate the purpose of the chip. The
* contents are expected to be a horizontally and vertically centre aligned icon of size
* [ToggleChipDefaults.IconSize]. In order to correctly render when the Chip is not enabled the
@@ -125,8 +125,8 @@
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
label: @Composable () -> Unit,
+ toggleControl: @Composable () -> Unit,
modifier: Modifier = Modifier,
- toggleControl: @Composable () -> Unit = { ToggleChipDefaults.CheckboxIcon(checked = checked) },
appIcon: @Composable (() -> Unit)? = null,
secondaryLabel: @Composable (() -> Unit)? = null,
colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
@@ -268,13 +268,13 @@
* which is "start" aligned.
* @param onClick Click listener called when the user clicks the main body of the chip, the area
* behind the labels.
- * @param modifier Modifier to be applied to the chip
* @param toggleControl A slot for providing the chip's toggle controls(s). The contents are
* expected to be a horizontally and vertically centre aligned icon of size
- * [ToggleChipDefaults.IconSize]. Three built-in types of toggle control are supported and can be
- * obtained from [ToggleChipDefaults.SwitchIcon], [ToggleChipDefaults.RadioIcon] and
- * [ToggleChipDefaults.CheckboxIcon]. In order to correctly render when the Chip is not enabled the
+ * [ToggleChipDefaults.IconSize]. Three built-in types of toggle control are supported and
+ * [ImageVector]s can be obtained from [ToggleChipDefaults.switchIcon], [ToggleChipDefaults.radioIcon]
+ * and [ToggleChipDefaults.checkboxIcon]. In order to correctly render when the Chip is not enabled the
* icon must set its alpha value to [LocalContentAlpha].
+ * @param modifier Modifier to be applied to the chip
* @param secondaryLabel A slot for providing the chip's secondary label. The contents are expected
* to be "start" or "center" aligned. label and secondaryLabel contents should be consistently
* aligned.
@@ -302,8 +302,8 @@
onCheckedChange: (Boolean) -> Unit,
label: @Composable () -> Unit,
onClick: () -> Unit,
+ toggleControl: @Composable () -> Unit,
modifier: Modifier = Modifier,
- toggleControl: @Composable () -> Unit = { ToggleChipDefaults.CheckboxIcon(checked = checked) },
secondaryLabel: @Composable (() -> Unit)? = null,
colors: SplitToggleChipColors = ToggleChipDefaults.splitToggleChipColors(),
enabled: Boolean = true,
@@ -710,6 +710,12 @@
)
}
+ /**
+ * The Wear Material UX recommended tint color to use for an unselected switch icon.
+ */
+ @Composable
+ public fun switchUncheckedIconColor() = MaterialTheme.colors.onSurface.copy(0.6f)
+
private val ChipHorizontalPadding = 14.dp
private val ChipVerticalPadding = 6.dp
@@ -724,35 +730,19 @@
)
/**
- * Creates switch style toggle [Icon]s for use in the toggleControl slot of a [ToggleChip] or
- * [SplitToggleChip].
+ * Creates switch style toggle [ImageVector]s for use in the toggleControl slot of a
+ * [ToggleChip] or [SplitToggleChip].
* Depending on [checked] will return either an 'on' (checked) or 'off' (unchecked) switch icon.
*
* @param checked whether the [ToggleChip] or [SplitToggleChip] is currently 'on' (checked/true)
* or 'off' (unchecked/false)
*/
- @Composable
- public fun SwitchIcon(
+ public fun switchIcon(
checked: Boolean,
- ) {
- if (checked) {
- Icon(
- imageVector = SwitchOn,
- contentDescription = "Switch selector",
- modifier = Modifier.size(24.dp),
- )
- } else {
- Icon(
- imageVector = SwitchOff,
- contentDescription = "Switch selector",
- modifier = Modifier.size(24.dp),
- tint = MaterialTheme.colors.onSurface.copy(0.6f)
- )
- }
- }
+ ): ImageVector = if (checked) SwitchOn else SwitchOff
/**
- * Creates a radio button style toggle [Icon]s for use in the toggleControl slot of a
+ * Creates a radio button style toggle [ImageVector]s for use in the toggleControl slot of a
* [ToggleChip] or [SplitToggleChip].
* Depending on [checked] will return either an 'on' (checked) or 'off' (unchecked) radio button
* icon.
@@ -760,32 +750,22 @@
* @param checked whether the [ToggleChip] or [SplitToggleChip] is currently 'on' (checked/true)
* or 'off' (unchecked/false)
*/
- @Composable
- public fun RadioIcon(checked: Boolean) {
- Icon(
- imageVector = if (checked) RadioOn else RadioOff,
- contentDescription = "Radio selector",
- modifier = Modifier.size(24.dp)
- )
- }
+ public fun radioIcon(
+ checked: Boolean,
+ ): ImageVector = if (checked) RadioOn else RadioOff
/**
- * Creates a checkbox style toggle [Icon]s for use in the toggleControl slot of a [ToggleChip]
- * or [SplitToggleChip].
+ * Creates checkbox style toggle [ImageVector]s for use in the toggleControl slot of a
+ * [ToggleChip] or [SplitToggleChip].
* Depending on [checked] will return either an 'on' (ticked/checked) or 'off'
- * (unticked/unchecked) checkbox icon.
+ * (unticked/unchecked) checkbox image.
*
* @param checked whether the [ToggleChip] or [SplitToggleChip] is currently 'on' (checked/true)
* or 'off' (unchecked/false)
*/
- @Composable
- public fun CheckboxIcon(checked: Boolean) {
- Icon(
- imageVector = if (checked) CheckboxOn else CheckboxOff,
- contentDescription = "Checkbox selector",
- modifier = Modifier.size(24.dp)
- )
- }
+ public fun checkboxIcon(
+ checked: Boolean,
+ ): ImageVector = if (checked) CheckboxOn else CheckboxOff
/**
* The default height applied for the [ToggleChip] or [SplitToggleChip].
diff --git a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
index e496106..3716bae 100644
--- a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
+++ b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
@@ -260,6 +260,27 @@
}
@Test
+ fun updates_lifecycle_after_popping_back_stack() {
+ lateinit var navController: NavHostController
+ rule.setContentWithTheme {
+ navController = rememberSwipeDismissableNavController()
+ SwipeDismissWithNavigation(navController)
+ }
+
+ rule.waitForIdle()
+ rule.onNodeWithText(START).performClick()
+
+ rule.runOnIdle {
+ navController.popBackStack()
+ }
+
+ rule.runOnIdle {
+ assertThat(navController.currentBackStackEntry?.lifecycle?.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ }
+ }
+
+ @Test
fun provides_access_to_current_backstack_entry_state() {
lateinit var navController: NavHostController
lateinit var backStackEntry: State<NavBackStackEntry?>
diff --git a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
index 9e84192..bf8658e 100644
--- a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
+++ b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
@@ -21,7 +21,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -155,6 +154,7 @@
val wearNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
WearNavigator.NAME
) as? WearNavigator ?: return
+
val backStack by wearNavigator.backStack.collectAsState()
val transitionsInProgress by wearNavigator.transitionsInProgress.collectAsState()
var initialContent by remember { mutableStateOf(true) }
@@ -182,9 +182,9 @@
// This effect marks the transitions completed when swipe animations finish,
// so that the navigation backstack entries can go to Lifecycle.State.RESUMED.
if (state.isAnimationRunning == false) {
- transitionsInProgress.forEach { entry ->
- wearNavigator.onTransitionComplete(entry)
- }
+ transitionsInProgress.forEach { entry ->
+ wearNavigator.onTransitionComplete(entry)
+ }
}
}
@@ -199,7 +199,7 @@
}
)
- SideEffect {
+ DisposableEffect(previous, current) {
if (initialContent) {
// There are no animations for showing the initial content, so mark transitions complete,
// allowing the navigation backstack entry to go to Lifecycle.State.RESUMED.
@@ -208,6 +208,11 @@
}
initialContent = false
}
+ onDispose {
+ transitionsInProgress.forEach { entry ->
+ wearNavigator.onTransitionComplete(entry)
+ }
+ }
}
}
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt
index 312e03c..7efc45b 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt
@@ -48,6 +48,7 @@
import androidx.wear.compose.material.ChipColors
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.CompactChip
+import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.ListHeader
import androidx.wear.compose.material.LocalContentColor
import androidx.wear.compose.material.MaterialTheme
@@ -329,8 +330,17 @@
label = {
Text("Chips enabled")
},
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to ToggleChipDefaults.switchUncheckedIconColor()
+ // rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults.switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = enabled)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(checked = enabled),
+ contentDescription = if (enabled) "On" else "Off"
+ )
}
)
}
@@ -500,8 +510,17 @@
label = {
Text("Chips enabled")
},
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to ToggleChipDefaults.switchUncheckedIconColor()
+ // rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults.switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = enabled)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(checked = enabled),
+ contentDescription = if (enabled) "On" else "Off"
+ )
}
)
}
@@ -548,8 +567,17 @@
label = {
Text("Chips enabled")
},
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to ToggleChipDefaults.switchUncheckedIconColor()
+ // rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults.switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = enabled)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(checked = enabled),
+ contentDescription = if (enabled) "On" else "Off"
+ )
}
)
}
@@ -597,8 +625,17 @@
label = {
Text("Chips enabled")
},
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to ToggleChipDefaults.switchUncheckedIconColor()
+ // rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults.switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = enabled)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(checked = enabled),
+ contentDescription = if (enabled) "On" else "Off"
+ )
}
)
}
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
index 1ae7f56..0b463d5 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
@@ -17,18 +17,22 @@
package androidx.wear.compose.integration.demos
import androidx.wear.compose.foundation.samples.CurvedAndNormalText
+import androidx.wear.compose.foundation.samples.CurvedFixedSize
import androidx.wear.compose.foundation.samples.CurvedRowAndColumn
import androidx.wear.compose.foundation.samples.SimpleCurvedWorld
val WearFoundationDemos = DemoCategory(
"Foundation",
listOf(
- ComposableDemo("Curved Row") { CurvedWorldDemo() },
- ComposableDemo("Curved Row and Column") { CurvedRowAndColumn() },
- ComposableDemo("Simple") { SimpleCurvedWorld() },
- ComposableDemo("Alignment") { CurvedRowAlignmentDemo() },
- ComposableDemo("Curved Text") { BasicCurvedTextDemo() },
- ComposableDemo("Curved and Normal Text") { CurvedAndNormalText() },
+ DemoCategory("CurvedLayout", listOf(
+ ComposableDemo("Curved Row") { CurvedWorldDemo() },
+ ComposableDemo("Curved Row and Column") { CurvedRowAndColumn() },
+ ComposableDemo("Simple") { SimpleCurvedWorld() },
+ ComposableDemo("Alignment") { CurvedRowAlignmentDemo() },
+ ComposableDemo("Curved Text") { BasicCurvedTextDemo() },
+ ComposableDemo("Curved and Normal Text") { CurvedAndNormalText() },
+ ComposableDemo("Fixed size") { CurvedFixedSize() },
+ )),
ComposableDemo("Scrollable Column") { ScrollableColumnDemo() },
ComposableDemo("Scrollable Row") { ScrollableRowDemo() },
DemoCategory("Rotary Input", RotaryInputDemos),
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt
index d43dea6..5ee6a64 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt
@@ -79,7 +79,18 @@
checked = enabled,
onCheckedChange = { enabled = it },
label = { Text("Sliders enabled") },
- toggleControl = { ToggleChipDefaults.SwitchIcon(checked = enabled) }
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to ToggleChipDefaults.switchUncheckedIconColor()
+ // rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults.switchUncheckedIconColor()
+ ),
+ toggleControl = {
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(checked = enabled),
+ contentDescription = if (enabled) "On" else "Off"
+ )
+ }
)
}
}
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
index 6932fc8..35147d2 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.ListHeader
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.ScalingLazyListState
@@ -82,7 +83,12 @@
label = { Text("CheckboxIcon") },
checked = checkBoxIconChecked,
toggleControl = {
- ToggleChipDefaults.CheckboxIcon(checked = checkBoxIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.checkboxIcon(
+ checked = checkBoxIconChecked
+ ),
+ contentDescription = if (checkBoxIconChecked) "Checked" else "Unchecked"
+ )
},
onCheckedChange = { checkBoxIconChecked = it },
enabled = enabled,
@@ -94,8 +100,20 @@
ToggleChip(
label = { Text("SwitchIcon") },
checked = switchIconChecked,
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to
+ // ToggleChipDefaults.switchUncheckedIconColor() rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults
+ .switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = switchIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(
+ checked = switchIconChecked
+ ),
+ contentDescription = if (switchIconChecked) "On" else "Off"
+ )
},
onCheckedChange = { switchIconChecked = it },
enabled = enabled,
@@ -110,7 +128,10 @@
},
checked = radioIconChecked,
toggleControl = {
- ToggleChipDefaults.RadioIcon(checked = radioIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.radioIcon(checked = radioIconChecked),
+ contentDescription = if (radioIconChecked) "Selected" else "Unselected"
+ )
},
onCheckedChange = { radioIconChecked = it },
enabled = enabled,
@@ -132,7 +153,13 @@
},
checked = radioIconWithSecondaryChecked,
toggleControl = {
- ToggleChipDefaults.RadioIcon(checked = radioIconWithSecondaryChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.radioIcon(
+ checked = radioIconWithSecondaryChecked
+ ),
+ contentDescription = if (radioIconWithSecondaryChecked) "Selected"
+ else "Unselected"
+ )
},
onCheckedChange = { radioIconWithSecondaryChecked = it },
enabled = enabled,
@@ -153,9 +180,21 @@
Text("With secondary label", maxLines = 1, overflow = TextOverflow.Ellipsis)
},
checked = switchIconWithSecondaryChecked,
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to
+ // ToggleChipDefaults.switchUncheckedIconColor() rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults
+ .switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = switchIconWithSecondaryChecked)
- },
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(
+ checked = switchIconWithSecondaryChecked
+ ),
+ contentDescription = if (switchIconWithSecondaryChecked) "On" else "Off"
+ )
+ },
onCheckedChange = { switchIconWithSecondaryChecked = it },
appIcon = { DemoIcon(R.drawable.ic_airplanemode_active_24px) },
enabled = enabled,
@@ -170,8 +209,20 @@
Text("With switchable icon", maxLines = 1, overflow = TextOverflow.Ellipsis)
},
checked = switchIconWithIconChecked,
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to
+ // ToggleChipDefaults.switchUncheckedIconColor() rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults
+ .switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = switchIconWithIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(
+ checked = switchIconWithIconChecked
+ ),
+ contentDescription = if (switchIconWithIconChecked) "On" else "Off"
+ )
},
onCheckedChange = { switchIconWithIconChecked = it },
appIcon = {
@@ -198,7 +249,13 @@
label = { Text("Split with CheckboxIcon") },
checked = splitWithCheckboxIconChecked,
toggleControl = {
- ToggleChipDefaults.CheckboxIcon(checked = splitWithCheckboxIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.checkboxIcon(
+ checked = splitWithCheckboxIconChecked
+ ),
+ contentDescription = if (splitWithCheckboxIconChecked) "Checked"
+ else "Unchecked"
+ )
},
onCheckedChange = { splitWithCheckboxIconChecked = it },
onClick = {
@@ -216,8 +273,20 @@
SplitToggleChip(
label = { Text("Split with SwitchIcon") },
checked = splitWithSwitchIconChecked,
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to
+ // ToggleChipDefaults.switchUncheckedIconColor() rather than the default.
+ colors = ToggleChipDefaults.splitToggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults
+ .switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = splitWithSwitchIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(
+ checked = splitWithSwitchIconChecked
+ ),
+ contentDescription = if (splitWithSwitchIconChecked) "On" else "Off"
+ )
},
onCheckedChange = { splitWithSwitchIconChecked = it },
onClick = {
@@ -236,7 +305,13 @@
label = { Text("Split with RadioIcon") },
checked = splitWithRadioIconChecked,
toggleControl = {
- ToggleChipDefaults.RadioIcon(checked = splitWithRadioIconChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.radioIcon(
+ checked = splitWithRadioIconChecked
+ ),
+ contentDescription = if (splitWithRadioIconChecked) "Selected"
+ else "Unselected"
+ )
},
onCheckedChange = { splitWithRadioIconChecked = it },
onClick = {
@@ -266,7 +341,12 @@
},
checked = splitWithCustomColorChecked,
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = splitWithCustomColorChecked)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(
+ checked = splitWithCustomColorChecked
+ ),
+ contentDescription = if (splitWithCustomColorChecked) "On" else "Off"
+ )
},
onCheckedChange = { splitWithCustomColorChecked = it },
onClick = {
@@ -275,8 +355,13 @@
"Text was clicked", Toast.LENGTH_SHORT
).show()
},
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to
+ // ToggleChipDefaults.switchUncheckedIconColor() rather than the default.
colors = ToggleChipDefaults.splitToggleChipColors(
- checkedToggleControlTintColor = AlternatePrimaryColor1
+ checkedToggleControlTintColor = AlternatePrimaryColor1,
+ uncheckedToggleControlTintColor = ToggleChipDefaults
+ .switchUncheckedIconColor()
),
enabled = enabled,
)
@@ -290,8 +375,20 @@
label = {
Text("Chips enabled")
},
+ // For Switch toggle controls the Wear Material UX guidance is to set the
+ // unselected toggle control color to
+ // ToggleChipDefaults.switchUncheckedIconColor() rather than the default.
+ colors = ToggleChipDefaults.toggleChipColors(
+ uncheckedToggleControlTintColor = ToggleChipDefaults
+ .switchUncheckedIconColor()
+ ),
toggleControl = {
- ToggleChipDefaults.SwitchIcon(checked = enabled)
+ Icon(
+ imageVector = ToggleChipDefaults.switchIcon(
+ checked = enabled
+ ),
+ contentDescription = if (enabled) "On" else "Off"
+ )
},
)
}
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
index d391583..c96a155 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
@@ -69,6 +69,7 @@
import androidx.wear.compose.material.TitleCard
import androidx.wear.compose.material.ToggleButton
import androidx.wear.compose.material.ToggleChip
+import androidx.wear.compose.material.ToggleChipDefaults
import androidx.wear.compose.material.Vignette
import androidx.wear.compose.material.VignettePosition
import androidx.wear.compose.material.curvedText
@@ -201,12 +202,29 @@
ListHeader { Text("Chips") }
Chip(onClick = {}, colors = ChipDefaults.primaryChipColors()) { Text("Chip") }
CompactChip(onClick = {}, label = { Text("CompactChip") })
- ToggleChip(true, onCheckedChange = {}, label = { Text("ToggleChip") })
+ ToggleChip(
+ checked = true,
+ onCheckedChange = {},
+ label = { Text("ToggleChip") },
+ toggleControl = {
+ Icon(
+ imageVector = ToggleChipDefaults.radioIcon(checked = false),
+ contentDescription = null
+ )
+ }
+ )
SplitToggleChip(
checked = true,
onCheckedChange = {},
label = { Text("SplitToggleChip") },
- onClick = {})
+ onClick = {},
+ toggleControl = {
+ Icon(
+ imageVector = ToggleChipDefaults.radioIcon(checked = true),
+ contentDescription = null
+ )
+ }
+ )
}
@Composable
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java
index 287db2c..192ad83 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java
@@ -108,8 +108,8 @@
public static final DpProp ICON_SIZE = dp(24);
/** The recommended colors for a primary {@link Chip}. */
- @NonNull public static final ChipColors PRIMARY_COLORS =
- ChipColors.primaryChipColors(Colors.DEFAULT);
+ @NonNull
+ public static final ChipColors PRIMARY_COLORS = ChipColors.primaryChipColors(Colors.DEFAULT);
/** The recommended colors for a secondary {@link Chip}. */
@NonNull
diff --git a/wear/watchface/watchface-client/api/current.txt b/wear/watchface/watchface-client/api/current.txt
index 556cb5a..6e77b4d 100644
--- a/wear/watchface/watchface-client/api/current.txt
+++ b/wear/watchface/watchface-client/api/current.txt
@@ -203,6 +203,22 @@
ctor public WatchFaceControlClient.ServiceStartFailureException(optional String message);
}
+ public final class WatchFaceException extends java.lang.Exception {
+ ctor public WatchFaceException(Exception e, int reason);
+ method public int getReason();
+ property public final int reason;
+ field public static final androidx.wear.watchface.client.WatchFaceException.Companion Companion;
+ field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
+ field public static final int UNKNOWN = 3; // 0x3
+ field public static final int WATCHFACE_DIED = 1; // 0x1
+ }
+
+ public static final class WatchFaceException.Companion {
+ }
+
+ public final class WatchFaceExceptionKt {
+ }
+
public final class WatchFaceId {
ctor public WatchFaceId(String id);
method public String getId();
@@ -210,8 +226,8 @@
}
public interface WatchFaceMetadataClient extends java.lang.AutoCloseable {
- method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotMetadata> getComplicationSlotMetadataMap() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
- method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
+ method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotMetadata> getComplicationSlotMetadataMap() throws androidx.wear.watchface.client.WatchFaceException;
+ method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws androidx.wear.watchface.client.WatchFaceException;
method public boolean isUserStyleSchemaStatic();
property public abstract boolean isUserStyleSchemaStatic;
field public static final androidx.wear.watchface.client.WatchFaceMetadataClient.Companion Companion;
@@ -229,19 +245,6 @@
ctor public WatchFaceMetadataClient.ServiceStartFailureException(optional String message);
}
- public static final class WatchFaceMetadataClient.WatchFaceException extends java.lang.Exception {
- ctor public WatchFaceMetadataClient.WatchFaceException(Exception e, int reason);
- method public int getReason();
- property public final int reason;
- field public static final androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException.Companion Companion;
- field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
- field public static final int UNKNOWN = 3; // 0x3
- field public static final int WATCHFACE_DIED = 1; // 0x1
- }
-
- public static final class WatchFaceMetadataClient.WatchFaceException.Companion {
- }
-
public final class WatchUiState {
ctor public WatchUiState(boolean inAmbientMode, int interruptionFilter);
method public boolean getInAmbientMode();
diff --git a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
index 334d432..95ff68c 100644
--- a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
@@ -102,7 +102,7 @@
method public default static androidx.wear.watchface.client.HeadlessWatchFaceClient createFromBundle(android.os.Bundle bundle);
method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
- method @androidx.wear.watchface.WatchFaceFlavorsExperimental @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public default androidx.wear.watchface.UserStyleFlavors getUserStyleFlavors() throws androidx.wear.watchface.client.HeadlessWatchFaceClient.WatchFaceException;
+ method @androidx.wear.watchface.WatchFaceFlavorsExperimental @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public default androidx.wear.watchface.UserStyleFlavors getUserStyleFlavors() throws androidx.wear.watchface.client.WatchFaceException;
method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default byte[] getUserStyleSchemaDigestHash() throws android.os.RemoteException;
method @AnyThread public boolean isConnectionAlive();
@@ -125,19 +125,6 @@
method public androidx.wear.watchface.client.HeadlessWatchFaceClient createFromBundle(android.os.Bundle bundle);
}
- @androidx.wear.watchface.WatchFaceFlavorsExperimental public static final class HeadlessWatchFaceClient.WatchFaceException extends java.lang.Exception {
- ctor public HeadlessWatchFaceClient.WatchFaceException(Exception e, int reason);
- method public int getReason();
- property public final int reason;
- field public static final androidx.wear.watchface.client.HeadlessWatchFaceClient.WatchFaceException.Companion Companion;
- field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
- field public static final int UNKNOWN = 3; // 0x3
- field public static final int WATCHFACE_DIED = 1; // 0x1
- }
-
- public static final class HeadlessWatchFaceClient.WatchFaceException.Companion {
- }
-
public interface InteractiveWatchFaceClient extends java.lang.AutoCloseable {
method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
method public void addOnWatchFaceReadyListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
@@ -220,6 +207,22 @@
ctor public WatchFaceControlClient.ServiceStartFailureException(optional String message);
}
+ public final class WatchFaceException extends java.lang.Exception {
+ ctor public WatchFaceException(Exception e, int reason);
+ method public int getReason();
+ property public final int reason;
+ field public static final androidx.wear.watchface.client.WatchFaceException.Companion Companion;
+ field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
+ field public static final int UNKNOWN = 3; // 0x3
+ field public static final int WATCHFACE_DIED = 1; // 0x1
+ }
+
+ public static final class WatchFaceException.Companion {
+ }
+
+ public final class WatchFaceExceptionKt {
+ }
+
public final class WatchFaceId {
ctor public WatchFaceId(String id);
method public String getId();
@@ -227,9 +230,9 @@
}
public interface WatchFaceMetadataClient extends java.lang.AutoCloseable {
- method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotMetadata> getComplicationSlotMetadataMap() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
- method @androidx.wear.watchface.WatchFaceFlavorsExperimental @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.UserStyleFlavors getUserStyleFlavors() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
- method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
+ method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotMetadata> getComplicationSlotMetadataMap() throws androidx.wear.watchface.client.WatchFaceException;
+ method @androidx.wear.watchface.WatchFaceFlavorsExperimental @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.UserStyleFlavors getUserStyleFlavors() throws androidx.wear.watchface.client.WatchFaceException;
+ method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws androidx.wear.watchface.client.WatchFaceException;
method public boolean isUserStyleSchemaStatic();
property public abstract boolean isUserStyleSchemaStatic;
field public static final androidx.wear.watchface.client.WatchFaceMetadataClient.Companion Companion;
@@ -247,19 +250,6 @@
ctor public WatchFaceMetadataClient.ServiceStartFailureException(optional String message);
}
- public static final class WatchFaceMetadataClient.WatchFaceException extends java.lang.Exception {
- ctor public WatchFaceMetadataClient.WatchFaceException(Exception e, int reason);
- method public int getReason();
- property public final int reason;
- field public static final androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException.Companion Companion;
- field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
- field public static final int UNKNOWN = 3; // 0x3
- field public static final int WATCHFACE_DIED = 1; // 0x1
- }
-
- public static final class WatchFaceMetadataClient.WatchFaceException.Companion {
- }
-
public final class WatchUiState {
ctor public WatchUiState(boolean inAmbientMode, int interruptionFilter);
method public boolean getInAmbientMode();
diff --git a/wear/watchface/watchface-client/api/restricted_current.txt b/wear/watchface/watchface-client/api/restricted_current.txt
index 556cb5a..6e77b4d 100644
--- a/wear/watchface/watchface-client/api/restricted_current.txt
+++ b/wear/watchface/watchface-client/api/restricted_current.txt
@@ -203,6 +203,22 @@
ctor public WatchFaceControlClient.ServiceStartFailureException(optional String message);
}
+ public final class WatchFaceException extends java.lang.Exception {
+ ctor public WatchFaceException(Exception e, int reason);
+ method public int getReason();
+ property public final int reason;
+ field public static final androidx.wear.watchface.client.WatchFaceException.Companion Companion;
+ field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
+ field public static final int UNKNOWN = 3; // 0x3
+ field public static final int WATCHFACE_DIED = 1; // 0x1
+ }
+
+ public static final class WatchFaceException.Companion {
+ }
+
+ public final class WatchFaceExceptionKt {
+ }
+
public final class WatchFaceId {
ctor public WatchFaceId(String id);
method public String getId();
@@ -210,8 +226,8 @@
}
public interface WatchFaceMetadataClient extends java.lang.AutoCloseable {
- method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotMetadata> getComplicationSlotMetadataMap() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
- method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException;
+ method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotMetadata> getComplicationSlotMetadataMap() throws androidx.wear.watchface.client.WatchFaceException;
+ method @kotlin.jvm.Throws(exceptionClasses=WatchFaceException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws androidx.wear.watchface.client.WatchFaceException;
method public boolean isUserStyleSchemaStatic();
property public abstract boolean isUserStyleSchemaStatic;
field public static final androidx.wear.watchface.client.WatchFaceMetadataClient.Companion Companion;
@@ -229,19 +245,6 @@
ctor public WatchFaceMetadataClient.ServiceStartFailureException(optional String message);
}
- public static final class WatchFaceMetadataClient.WatchFaceException extends java.lang.Exception {
- ctor public WatchFaceMetadataClient.WatchFaceException(Exception e, int reason);
- method public int getReason();
- property public final int reason;
- field public static final androidx.wear.watchface.client.WatchFaceMetadataClient.WatchFaceException.Companion Companion;
- field public static final int TRANSACTION_TOO_LARGE = 2; // 0x2
- field public static final int UNKNOWN = 3; // 0x3
- field public static final int WATCHFACE_DIED = 1; // 0x1
- }
-
- public static final class WatchFaceMetadataClient.WatchFaceException.Companion {
- }
-
public final class WatchUiState {
ctor public WatchUiState(boolean inAmbientMode, int interruptionFilter);
method public boolean getInAmbientMode();
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt
index c4fc2e8..5b88032 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt
@@ -19,12 +19,9 @@
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.os.Bundle
-import android.os.DeadObjectException
import android.os.RemoteException
-import android.os.TransactionTooLargeException
import android.support.wearable.watchface.SharedMemoryImage
import androidx.annotation.AnyThread
-import androidx.annotation.IntDef
import androidx.annotation.RequiresApi
import androidx.wear.watchface.complications.data.ComplicationData
import androidx.wear.watchface.utility.TraceEvent
@@ -62,52 +59,6 @@
)
}
- /**
- * Why the remote watch face query failed.
- * @hide
- **/
- @Retention(AnnotationRetention.SOURCE)
- @WatchFaceFlavorsExperimental
- @IntDef(
- WatchFaceException.WATCHFACE_DIED,
- WatchFaceException.TRANSACTION_TOO_LARGE,
- WatchFaceException.UNKNOWN
- )
- annotation class WatchFaceExceptionReason
-
- /**
- * The watch face threw an exception while trying to service the request.
- *
- * @property reason The [WatchFaceExceptionReason] for the exception.
- */
- // TODO(b/227151490): Get rid of WatchFaceException duplication
- @WatchFaceFlavorsExperimental
- public class WatchFaceException(
- e: Exception,
- @WatchFaceExceptionReason val reason: Int
- ) : Exception(e) {
-
- companion object {
- /**
- * The watchface process died. Connecting again might work, but this isn't guaranteed.
- */
- const val WATCHFACE_DIED = 1
-
- /**
- * The watchface tried to send us too much data. Currently the limit on binder
- * transactions is 1mb. See [TransactionTooLargeException] for more details.
- */
- const val TRANSACTION_TOO_LARGE = 2
-
- /**
- * The watch face threw an exception, typically during initialization. Depending on the
- * nature of the problem this might be a transient issue or it might occur every time
- * for that particular watch face.
- */
- const val UNKNOWN = 3
- }
- }
-
/** The [Instant] to use when rendering previews. */
@get:Throws(RemoteException::class)
public val previewReferenceInstant: Instant
@@ -256,27 +207,12 @@
@OptIn(WatchFaceFlavorsExperimental::class)
override fun getUserStyleFlavors(): UserStyleFlavors =
- try {
+ callRemote {
if (iHeadlessWatchFace.apiVersion >= 3) {
UserStyleFlavors(iHeadlessWatchFace.userStyleFlavors)
} else {
UserStyleFlavors()
}
- } catch (e: DeadObjectException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.WATCHFACE_DIED
- )
- } catch (e: TransactionTooLargeException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.TRANSACTION_TOO_LARGE
- )
- } catch (e: RemoteException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.UNKNOWN
- )
}
override val complicationSlotsState: Map<Int, ComplicationSlotState>
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceException.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceException.kt
new file mode 100644
index 0000000..eb5f023
--- /dev/null
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceException.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface.client
+
+import android.os.DeadObjectException
+import android.os.RemoteException
+import android.os.TransactionTooLargeException
+import androidx.annotation.IntDef
+
+/**
+ * Why the remote watch face query failed.
+ * @hide
+ **/
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ WatchFaceException.WATCHFACE_DIED,
+ WatchFaceException.TRANSACTION_TOO_LARGE,
+ WatchFaceException.UNKNOWN
+)
+annotation class WatchFaceExceptionReason
+
+/**
+ * The watch face threw an exception while trying to service the request.
+ *
+ * @property reason The [WatchFaceExceptionReason] for the exception.
+ */
+public class WatchFaceException(
+ e: Exception,
+ @WatchFaceExceptionReason val reason: Int
+) : Exception(e) {
+
+ companion object {
+ /**
+ * The watchface process died. Connecting again might work, but this isn't guaranteed.
+ */
+ const val WATCHFACE_DIED = 1
+
+ /**
+ * The watchface tried to send us too much data. Currently the limit on binder
+ * transactions is 1mb. See [TransactionTooLargeException] for more details.
+ */
+ const val TRANSACTION_TOO_LARGE = 2
+
+ /**
+ * The watch face threw an exception, typically during initialization. Depending on the
+ * nature of the problem this might be a transient issue or it might occur every time
+ * for that particular watch face.
+ */
+ const val UNKNOWN = 3
+ }
+}
+
+@Throws(WatchFaceException::class)
+internal fun <R> callRemote(task: () -> R): R =
+ try {
+ task()
+ } catch (e: DeadObjectException) {
+ throw WatchFaceException(e, WatchFaceException.WATCHFACE_DIED)
+ } catch (e: TransactionTooLargeException) {
+ throw WatchFaceException(e, WatchFaceException.TRANSACTION_TOO_LARGE)
+ } catch (e: RemoteException) {
+ throw WatchFaceException(e, WatchFaceException.UNKNOWN)
+ }
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt
index d6b76f2..94309b4 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt
@@ -24,11 +24,7 @@
import android.content.res.XmlResourceParser
import android.graphics.RectF
import android.os.Bundle
-import android.os.DeadObjectException
import android.os.IBinder
-import android.os.RemoteException
-import android.os.TransactionTooLargeException
-import androidx.annotation.IntDef
import androidx.annotation.RestrictTo
import androidx.wear.watchface.complications.ComplicationSlotBounds
import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
@@ -168,49 +164,6 @@
public class ServiceStartFailureException(message: String = "") : Exception(message)
/**
- * Why the remote watch face query failed.
- * @hide
- **/
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(
- WatchFaceException.WATCHFACE_DIED,
- WatchFaceException.TRANSACTION_TOO_LARGE,
- WatchFaceException.UNKNOWN
- )
- annotation class WatchFaceExceptionReason
-
- /**
- * The watch face threw an exception while trying to service the request.
- *
- * @property reason The [WatchFaceExceptionReason] for the exception.
- */
- public class WatchFaceException(
- e: Exception,
- @WatchFaceExceptionReason val reason: Int
- ) : Exception(e) {
-
- companion object {
- /**
- * The watchface process died. Connecting again might work, but this isn't guaranteed.
- */
- const val WATCHFACE_DIED = 1
-
- /**
- * The watchface tried to send us too much data. Currently the limit on binder
- * transactions is 1mb. See [TransactionTooLargeException] for more details.
- */
- const val TRANSACTION_TOO_LARGE = 2
-
- /**
- * The watch face threw an exception, typically during initialization. Depending on the
- * nature of the problem this might be a transient issue or it might occur every time
- * for that particular watch face.
- */
- const val UNKNOWN = 3
- }
- }
-
- /**
* Returns the watch face's [UserStyleSchema].
*/
@Throws(WatchFaceException::class)
@@ -311,37 +264,21 @@
}
}
- override fun getUserStyleSchema(): UserStyleSchema {
- return try {
+ override fun getUserStyleSchema(): UserStyleSchema =
+ callRemote {
if (service.apiVersion >= 3) {
UserStyleSchema(service.getUserStyleSchema(GetUserStyleSchemaParams(watchFaceName)))
} else {
headlessClient.userStyleSchema
}
- } catch (e: DeadObjectException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.WATCHFACE_DIED
- )
- } catch (e: TransactionTooLargeException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.TRANSACTION_TOO_LARGE
- )
- } catch (e: RemoteException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.UNKNOWN
- )
}
- }
override val isUserStyleSchemaStatic: Boolean
get() = false
override fun getComplicationSlotMetadataMap(): Map<Int, ComplicationSlotMetadata> {
requireNotClosed()
- return try {
+ return callRemote {
if (service.apiVersion >= 3) {
val wireFormat = service.getComplicationSlotMetadata(
GetComplicationSlotMetadataParams(watchFaceName)
@@ -389,51 +326,19 @@
)
}
}
- } catch (e: DeadObjectException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.WATCHFACE_DIED
- )
- } catch (e: TransactionTooLargeException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.TRANSACTION_TOO_LARGE
- )
- } catch (e: RemoteException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.UNKNOWN
- )
}
}
@OptIn(WatchFaceFlavorsExperimental::class)
- override fun getUserStyleFlavors(): UserStyleFlavors {
- return try {
- if (service.apiVersion >= 5) {
- UserStyleFlavors(
- service.getUserStyleFlavors(
- GetUserStyleFlavorsParams(watchFaceName)
- )
+ override fun getUserStyleFlavors(): UserStyleFlavors = callRemote {
+ if (service.apiVersion >= 5) {
+ UserStyleFlavors(
+ service.getUserStyleFlavors(
+ GetUserStyleFlavorsParams(watchFaceName)
)
- } else {
- UserStyleFlavors()
- }
- } catch (e: DeadObjectException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.WATCHFACE_DIED
)
- } catch (e: TransactionTooLargeException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.TRANSACTION_TOO_LARGE
- )
- } catch (e: RemoteException) {
- throw WatchFaceMetadataClient.WatchFaceException(
- e,
- WatchFaceMetadataClient.WatchFaceException.UNKNOWN
- )
+ } else {
+ UserStyleFlavors()
}
}
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeDifferenceText.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeDifferenceText.java
index 60d5669..84232ff 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeDifferenceText.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeDifferenceText.java
@@ -29,6 +29,7 @@
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Locale;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
@@ -71,6 +72,31 @@
mMinimumUnit = minimumUnit;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimeDifferenceText that = (TimeDifferenceText) o;
+ return mReferencePeriodStart == that.mReferencePeriodStart
+ && mReferencePeriodEnd == that.mReferencePeriodEnd && mStyle == that.mStyle
+ && mShowNowText == that.mShowNowText && mMinimumUnit == that.mMinimumUnit;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mReferencePeriodStart, mReferencePeriodEnd, mStyle, mShowNowText,
+ mMinimumUnit);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "TimeDifferenceText{mReferencePeriodStart=" + mReferencePeriodStart
+ + ", mReferencePeriodEnd=" + mReferencePeriodEnd
+ + ", mStyle=" + mStyle + ", mShowNowText=" + mShowNowText
+ + ", mMinimumUnit=" + mMinimumUnit + '}';
+ }
+
private static class SerializedForm implements Serializable {
long mReferencePeriodStart;
long mReferencePeriodEnd;
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java
index e88ced6..43b2c1d 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java
@@ -28,6 +28,7 @@
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -40,6 +41,30 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class TimeFormatText implements TimeDependentText {
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimeFormatText that = (TimeFormatText) o;
+ return mStyle == that.mStyle && mTimePrecision == that.mTimePrecision
+ && Objects.equals(mDateFormat, that.mDateFormat) && Objects.equals(
+ mTimeZone, that.mTimeZone)
+ && Objects.equals(mDate.toString(), that.mDate.toString());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDateFormat, mStyle, mTimeZone, mDate, mTimePrecision);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "TimeFormatText{mDateFormat=" + mDateFormat
+ + ", mStyle=" + mStyle + ", mTimeZone=" + mTimeZone + ", mDate=" + mDate
+ + ", mTimePrecision=" + mTimePrecision + '}';
+ }
+
private static class DateTimeFormat {
final String[] mFormatSymbols;
long mPrecision;
@@ -85,7 +110,7 @@
}
TimeFormatText(SimpleDateFormat dateFormat,
- @ComplicationText.TimeFormatStyle int style,
+ @ComplicationText.TimeFormatStyle int style,
TimeZone timeZone,
long timePrecision) {
mDateFormat = dateFormat;
@@ -97,7 +122,8 @@
private static class SerializedForm implements Serializable {
SimpleDateFormat mDateFormat;
- @ComplicationText.TimeFormatStyle int mStyle;
+ @ComplicationText.TimeFormatStyle
+ int mStyle;
TimeZone mTimeZone;
long mTimePrecision;
diff --git a/window/integration-tests/configuration-change-tests/build.gradle b/window/integration-tests/configuration-change-tests/build.gradle
new file mode 100644
index 0000000..8ae4023
--- /dev/null
+++ b/window/integration-tests/configuration-change-tests/build.gradle
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace "androidx.window.integration"
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ api(libs.kotlinCoroutinesAndroid)
+ // General dependencies to run tests.
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testRules)
+ // Dependencies for coroutines.
+ androidTestImplementation(libs.kotlinCoroutinesTest)
+ // Project dependencies.
+ androidTestImplementation project(path: ':lifecycle:lifecycle-runtime-ktx')
+ androidTestImplementation project(path: ':window:window')
+ androidTestImplementation project(path: ":core:core")
+ implementation project(path: ':activity:activity')
+
+}
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
+ }
+}
diff --git a/window/integration-tests/configuration-change-tests/src/androidTest/AndroidManifest.xml b/window/integration-tests/configuration-change-tests/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..687d459
--- /dev/null
+++ b/window/integration-tests/configuration-change-tests/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application>
+ </application>
+</manifest>
diff --git a/window/integration-tests/configuration-change-tests/src/androidTest/java/androidx/window/integration/TestConsumer.kt b/window/integration-tests/configuration-change-tests/src/androidTest/java/androidx/window/integration/TestConsumer.kt
new file mode 100644
index 0000000..4cb80f0
--- /dev/null
+++ b/window/integration-tests/configuration-change-tests/src/androidTest/java/androidx/window/integration/TestConsumer.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.window.integration
+
+import androidx.annotation.GuardedBy
+import androidx.core.util.Consumer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+import org.junit.Assert.assertTrue
+
+/**
+ * Data structure to hold values in a mutable list.
+ */
+internal class TestConsumer<T>(count: Int) : Consumer<T> {
+ private val valueLock = ReentrantLock()
+ @GuardedBy("valueLock")
+ private val values = mutableListOf<T>()
+
+ private val countDownLock = ReentrantLock()
+ @GuardedBy("countDownLock")
+ private var valueLatch = CountDownLatch(count)
+
+ private val waitTimeSeconds: Long = 3L
+
+ /**
+ * Appends the new value at the end of the mutable list values.
+ */
+ override fun accept(numLayoutFeatures: T) {
+ valueLock.withLock {
+ values.add(numLayoutFeatures)
+ }
+ countDownLock.withLock {
+ valueLatch.countDown()
+ }
+ }
+
+ /**
+ * Returns the current number of values stored.
+ */
+ private fun size(): Int {
+ valueLock.withLock {
+ return values.size
+ }
+ }
+
+ /**
+ * Waits for the mutable list's length to be at a certain number (count).
+ * The method will wait waitTimeSeconds for the count before asserting false.
+ */
+ fun waitForValueCount() {
+ assertTrue(
+ // Wait a total of waitTimeSeconds for the count before throwing an assertion error.
+ try {
+ valueLatch.await(waitTimeSeconds, TimeUnit.SECONDS)
+ } catch (e: InterruptedException) {
+ false
+ }
+ )
+ }
+
+ /**
+ * Returns {@code true} if there are no stored values, {@code false} otherwise.
+ */
+ fun isEmpty(): Boolean {
+ return size() == 0
+ }
+
+ /**
+ * Returns the object in the mutable list at the requested index.
+ */
+ fun get(valueIndex: Int): T {
+ valueLock.withLock {
+ return values[valueIndex]
+ }
+ }
+}
\ No newline at end of file
diff --git a/window/integration-tests/configuration-change-tests/src/androidTest/java/androidx/window/integration/layout/WindowInfoTrackerEndToEndTest.kt b/window/integration-tests/configuration-change-tests/src/androidTest/java/androidx/window/integration/layout/WindowInfoTrackerEndToEndTest.kt
new file mode 100644
index 0000000..f4ab61e
--- /dev/null
+++ b/window/integration-tests/configuration-change-tests/src/androidTest/java/androidx/window/integration/layout/WindowInfoTrackerEndToEndTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.window.integration.layout
+
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+import android.graphics.Rect
+import androidx.lifecycle.lifecycleScope
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.window.integration.TestActivity
+import androidx.window.integration.TestConsumer
+import androidx.window.layout.DisplayFeature
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.FoldingFeature.State.Companion.FLAT
+import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
+import androidx.window.layout.WindowInfoTracker
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+/** A collection of end to end tests for [WindowInfoTracker] to ensure correct behavior. */
+@ExperimentalCoroutinesApi
+class WindowInfoTrackerEndToEndTest {
+
+ // Specify the scope for handling coroutines.
+ private val testScope = TestScope(StandardTestDispatcher())
+
+ // Specify the rules to launch/close an activity before/after each test.
+ @get:Rule
+ val openActivityRule: ActivityScenarioRule<TestActivity> =
+ ActivityScenarioRule(TestActivity::class.java)
+
+ /** Checks that the [DisplayFeature]'s transform appropriately upon screen rotation. */
+ @Test
+ fun verifyDisplayFeatures_ScreenRotation() = testScope.runTest {
+ // Initialize a collector that stores the DisplayFeatures from an Activity.
+ // The DisplayFeatures will later be compared across different screen orientations.
+ val expectedLayoutsCollected = 1
+ val featureCollectorPortrait = TestConsumer<List<DisplayFeature>>(
+ count = expectedLayoutsCollected)
+ val featureCollectorLandscape = TestConsumer<List<DisplayFeature>>(
+ count = expectedLayoutsCollected)
+
+ // Set the screen orientation to portrait and collect its DisplayFeatures.
+ setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ getFirstDisplayFeature(featureCollectorPortrait)
+ // Assertion: there is only one value in the collector (from portrait mode).
+ featureCollectorPortrait.waitForValueCount()
+ // Get the DisplayFeatures from the portrait layout.
+ val displayFeaturesPortrait = featureCollectorPortrait.get(valueIndex = 0)
+
+ // Change the screen orientation to landscape and collect its DisplayFeatures.
+ setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ getFirstDisplayFeature(featureCollectorLandscape)
+ // Assertion: there are two values in the collector (from portrait and landscape mode).
+ featureCollectorLandscape.waitForValueCount()
+ // Get the DisplayFeatures from the landscape layout.
+ val displayFeaturesLandscape = featureCollectorLandscape.get(valueIndex = 0)
+
+ // Assertion: the number of features in both layouts are the same.
+ assertEquals(displayFeaturesPortrait.size, displayFeaturesLandscape.size)
+ // Assertion: the number of FoldingFeatures in both layouts are the same
+ assertEquals(displayFeaturesPortrait.filterIsInstance<FoldingFeature>().size,
+ displayFeaturesLandscape.filterIsInstance<FoldingFeature>().size)
+ // Check that the properties of each DisplayFeature is valid.
+ val featureStateCounterPortrait = validateDisplayFeatures(displayFeaturesPortrait)
+ val featureStateCounterLandscape = validateDisplayFeatures(displayFeaturesLandscape)
+ // Verify that the expected counts of FoldingFeature properties is consistent.
+ assertEquals(featureStateCounterPortrait, featureStateCounterLandscape)
+ }
+
+ /** Changes the screen orientation and waits for the rotation to occur. */
+ private fun setScreenOrientation(screenOrientation: Int) {
+ // Start the activity using the ActivityScenarioRule and set the orientation.
+ openActivityRule.scenario.onActivity { activity: TestActivity ->
+ // Initiate a timer and wait for the screen to rotate.
+ activity.resetLayoutCounter()
+ activity.requestedOrientation = screenOrientation
+ activity.waitForLayout()
+
+ // Assertion: the screen has properly rotated (the internal value is set).
+ assertEquals(
+ "Expected the Screen to Rotate to state $screenOrientation;",
+ activity.requestedOrientation, screenOrientation
+ )
+ }
+ }
+
+ /**
+ * Extracts a list of [DisplayFeature]s from a [TestActivity] using the first window layout.
+ * Stores the [DisplayFeature]s into a collector for later analysis after the scope ends.
+ */
+ private fun getFirstDisplayFeature(featureCollector: TestConsumer<List<DisplayFeature>>) {
+ // Start the activity using the ActivityScenarioRule and store its DisplayFeatures.
+ openActivityRule.scenario.onActivity { activity: TestActivity ->
+ activity.lifecycleScope.launch {
+ // Take the first WindowLayoutInfo from the Flow stored in WindowInfoTracker.
+ val layoutInfo = WindowInfoTracker.getOrCreate(activity)
+ .windowLayoutInfo(activity).first()
+
+ // Store the DisplayFeatures for further analysis after the coroutine is closed.
+ featureCollector.accept(layoutInfo.displayFeatures)
+ }
+ }
+ }
+
+ /**
+ * Checks that the [DisplayFeature]'s bounds have non-negative area
+ * and at least one positive dimension.
+ */
+ private fun validateDisplayFeatureBounds(displayFeatureBounds: Rect) {
+ // Assert that the DisplayFeature has positive dimensions.
+ assertFalse("Error: a display feature was found with negative dimensions.",
+ displayFeatureBounds.width() < 0 || displayFeatureBounds.height() < 0)
+ // Assert that the DisplayFeature has at least one dimension.
+ assertFalse("Error: a display feature was found with zero area.",
+ displayFeatureBounds.width() == 0 && displayFeatureBounds.height() == 0)
+ }
+
+ /** A class to keep track of the number of each [FoldingFeature]'s state. */
+ data class FoldingFeatureStateCounter(var flatCount: Int, var halfOpenedCount: Int)
+
+ /**
+ * Checks each [DisplayFeature]'s properties to make sure it is a valid [DisplayFeature].
+ */
+ private fun validateDisplayFeatures(
+ displayFeatures: List<DisplayFeature>
+ ): FoldingFeatureStateCounter {
+ // Create a counter to count the states for the features in displayFeatures.
+ val foldingFeatureStateCounter = FoldingFeatureStateCounter(
+ flatCount = 0,
+ halfOpenedCount = 0
+ )
+
+ // Loop through each DisplayFeature and verify its properties.
+ for (displayFeature in displayFeatures) {
+ // DisplayFeature bounds must be 1-dimensional, nonnull, and positive.
+ validateDisplayFeatureBounds(displayFeature.bounds)
+
+ // If the DisplayFeature is a FoldingFeature, validate the FoldingFeature properties.
+ if (displayFeature is FoldingFeature) {
+ // Keep a running counter of each state to compare them between orientations.
+ when (displayFeature.state) {
+ FLAT -> {
+ foldingFeatureStateCounter.flatCount++
+ }
+ HALF_OPENED -> {
+ foldingFeatureStateCounter.halfOpenedCount++
+ }
+ else -> {
+ fail("The FoldingFeature state ${displayFeature.state} has not been " +
+ "added to the list of checked states. Please add the state and retry.")
+ }
+ }
+ }
+ }
+ return foldingFeatureStateCounter
+ }
+}
\ No newline at end of file
diff --git a/window/integration-tests/configuration-change-tests/src/main/AndroidManifest.xml b/window/integration-tests/configuration-change-tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..996e4f4
--- /dev/null
+++ b/window/integration-tests/configuration-change-tests/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="IntegrationTest"
+ android:supportsRtl="true">
+ <activity
+ android:name="androidx.window.integration.TestActivity"
+ android:configChanges="orientation|screenLayout|screenSize"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/window/integration-tests/configuration-change-tests/src/main/java/androidx/window/integration/TestActivity.kt b/window/integration-tests/configuration-change-tests/src/main/java/androidx/window/integration/TestActivity.kt
new file mode 100644
index 0000000..7314bda
--- /dev/null
+++ b/window/integration-tests/configuration-change-tests/src/main/java/androidx/window/integration/TestActivity.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.window.integration
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import androidx.activity.ComponentActivity
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * An [Activity] that handles some configuration changes and provides helper methods to
+ * synchronize when the configuration has changed.
+ */
+class TestActivity : ComponentActivity(), View.OnLayoutChangeListener {
+ private var layoutLatch = CountDownLatch(1)
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val contentView = View(this)
+ setContentView(contentView)
+ window.decorView.addOnLayoutChangeListener(this)
+ }
+
+ override fun onLayoutChange(
+ v: View,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ layoutLatch.countDown()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ resumeLatch.countDown()
+ }
+
+ /**
+ * Resets layout counter when waiting for a layout before calling [.waitForLayout].
+ */
+ fun resetLayoutCounter() {
+ layoutLatch = CountDownLatch(1)
+ }
+
+ /**
+ * Blocks and waits for the next layout.
+ * [.resetLayoutCounter] must be called before this method.
+ * @return {@code true} if the layout happened before the timeout count reaches zero and
+ * {@code false} if the waiting time finishes before the layout occurs.
+ */
+ fun waitForLayout(): Boolean {
+ return try {
+ layoutLatch.await(3, TimeUnit.SECONDS)
+ } catch (e: InterruptedException) {
+ false
+ }
+ }
+
+ companion object {
+ private var resumeLatch = CountDownLatch(1)
+
+ /**
+ * Resets layout counter when waiting for a layout before calling [.waitForOnResume].
+ */
+ @JvmStatic
+ fun resetResumeCounter() {
+ resumeLatch = CountDownLatch(1)
+ }
+
+ /**
+ * Waits for onResume() to be called for any activity of this class.
+ * This can be used to track activity re-creation.
+ * @return {@code true} if the onResume() happened before the timeout counter reaches zero
+ * and {@code false} if the waiting time finishes before the onResume() happens.
+ */
+ @JvmStatic
+ fun waitForOnResume(): Boolean {
+ return try {
+ resumeLatch.await(3, TimeUnit.SECONDS)
+ } catch (e: InterruptedException) {
+ false
+ }
+ }
+ }
+}
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
index 78f913c..aa83e1c 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
@@ -54,6 +54,7 @@
import androidx.work.WorkRequest;
import androidx.work.impl.background.systemjob.SystemJobService;
import androidx.work.impl.workers.ConstraintTrackingWorker;
+import androidx.work.impl.workers.ConstraintTrackingWorkerKt;
import androidx.work.integration.testapp.imageprocessing.ImageProcessingActivity;
import androidx.work.integration.testapp.sherlockholmes.AnalyzeSherlockHolmesActivity;
import androidx.work.multiprocess.RemoteWorkerService;
@@ -376,7 +377,7 @@
@Override
public void onClick(View v) {
Data inputData = new Data.Builder()
- .putString(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME,
+ .putString(ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME,
ForegroundWorker.class.getName())
.build();
diff --git a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java
index d520b4e..a9c02bf 100644
--- a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java
+++ b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java
@@ -28,6 +28,8 @@
import androidx.work.impl.Schedulers;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
+import androidx.work.impl.WorkRunIds;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.utils.WakeLocks;
import androidx.work.impl.utils.WorkTimer;
@@ -51,6 +53,7 @@
private static final long AWAIT_TIME_IN_MILLISECONDS = AWAIT_TIME_IN_MINUTES * 60 * 1000;
private final WorkTimer mWorkTimer;
+ private final WorkRunIds mWorkRunIds = new WorkRunIds();
// Synthetic access
WorkManagerImpl mWorkManagerImpl;
@@ -94,15 +97,16 @@
return GcmNetworkManager.RESULT_FAILURE;
}
- WorkSpecExecutionListener listener = new WorkSpecExecutionListener(workSpecId);
+ WorkSpecExecutionListener listener = new WorkSpecExecutionListener(workSpecId, mWorkRunIds);
+ WorkRunId workRunId = mWorkRunIds.workRunIdFor(workSpecId);
WorkSpecTimeLimitExceededListener timeLimitExceededListener =
- new WorkSpecTimeLimitExceededListener(mWorkManagerImpl);
+ new WorkSpecTimeLimitExceededListener(mWorkManagerImpl, workRunId);
Processor processor = mWorkManagerImpl.getProcessor();
processor.addExecutionListener(listener);
String wakeLockTag = "WorkGcm-onRunTask (" + workSpecId + ")";
PowerManager.WakeLock wakeLock = WakeLocks.newWakeLock(
mWorkManagerImpl.getApplicationContext(), wakeLockTag);
- mWorkManagerImpl.startWork(workSpecId);
+ mWorkManagerImpl.startWork(workRunId);
mWorkTimer.startTimer(workSpecId, AWAIT_TIME_IN_MILLISECONDS, timeLimitExceededListener);
try {
@@ -171,15 +175,18 @@
private static final String TAG = Logger.tagWithPrefix("WrkTimeLimitExceededLstnr");
private final WorkManagerImpl mWorkManager;
-
- WorkSpecTimeLimitExceededListener(@NonNull WorkManagerImpl workManager) {
+ private final WorkRunId mWorkRunId;
+ WorkSpecTimeLimitExceededListener(
+ @NonNull WorkManagerImpl workManager,
+ @NonNull WorkRunId workRunId) {
mWorkManager = workManager;
+ mWorkRunId = workRunId;
}
@Override
public void onTimeLimitExceeded(@NonNull String workSpecId) {
Logger.get().debug(TAG, "WorkSpec time limit exceeded " + workSpecId);
- mWorkManager.stopWork(workSpecId);
+ mWorkManager.stopWork(mWorkRunId);
}
}
@@ -188,9 +195,13 @@
private final String mWorkSpecId;
private final CountDownLatch mLatch;
private boolean mNeedsReschedule;
+ private final WorkRunIds mWorkRunIds;
- WorkSpecExecutionListener(@NonNull String workSpecId) {
+ WorkSpecExecutionListener(
+ @NonNull String workSpecId,
+ @NonNull WorkRunIds workRunIds) {
mWorkSpecId = workSpecId;
+ mWorkRunIds = workRunIds;
mLatch = new CountDownLatch(1);
mNeedsReschedule = false;
}
@@ -209,6 +220,7 @@
Logger.get().warning(TAG,
"Notified for " + workSpecId + ", but was looking for " + mWorkSpecId);
} else {
+ mWorkRunIds.remove(workSpecId);
mNeedsReschedule = needsReschedule;
mLatch.countDown();
}
diff --git a/work/work-runtime-ktx/api/api_lint.ignore b/work/work-runtime-ktx/api/api_lint.ignore
index ee82cf9..7a1b7a5 100644
--- a/work/work-runtime-ktx/api/api_lint.ignore
+++ b/work/work-runtime-ktx/api/api_lint.ignore
@@ -1,15 +1,3 @@
// Baseline format: 1.0
AsyncSuffixFuture: androidx.work.CoroutineWorker#startWork():
Methods returning com.google.common.util.concurrent.ListenableFuture should have a suffix *Async to reserve unmodified name for a suspend function
-
-
-MissingNullability: androidx.work.OneTimeWorkRequestKt#OneTimeWorkRequestBuilder():
- Missing nullability on method `OneTimeWorkRequestBuilder` return
-MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(java.time.Duration):
- Missing nullability on method `PeriodicWorkRequestBuilder` return
-MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(java.time.Duration, java.time.Duration):
- Missing nullability on method `PeriodicWorkRequestBuilder` return
-MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(long, java.util.concurrent.TimeUnit):
- Missing nullability on method `PeriodicWorkRequestBuilder` return
-MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit):
- Missing nullability on method `PeriodicWorkRequestBuilder` return
diff --git a/work/work-runtime-ktx/api/current.ignore b/work/work-runtime-ktx/api/current.ignore
index 1c21e58..fb0fcc1 100644
--- a/work/work-runtime-ktx/api/current.ignore
+++ b/work/work-runtime-ktx/api/current.ignore
@@ -1,4 +1,5 @@
// Baseline format: 1.0
+
ParameterNameChange: androidx.work.CoroutineWorker#doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result>) parameter #0:
Attempted to remove parameter name from parameter arg1 in androidx.work.CoroutineWorker.doWork
ParameterNameChange: androidx.work.CoroutineWorker#getForegroundInfo(kotlin.coroutines.Continuation<? super androidx.work.ForegroundInfo>) parameter #0:
@@ -9,3 +10,8 @@
Attempted to remove parameter name from parameter arg2 in androidx.work.CoroutineWorker.setProgress
ParameterNameChange: androidx.work.OperationKt#await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>) parameter #1:
Attempted to remove parameter name from parameter arg2 in androidx.work.OperationKt.await
+
+RemovedClass: androidx.work.OneTimeWorkRequestKt:
+ Removed class androidx.work.OneTimeWorkRequestKt
+RemovedClass: androidx.work.PeriodicWorkRequestKt:
+ Removed class androidx.work.PeriodicWorkRequestKt
diff --git a/work/work-runtime-ktx/api/current.txt b/work/work-runtime-ktx/api/current.txt
index d1cd3d5..efdea4c 100644
--- a/work/work-runtime-ktx/api/current.txt
+++ b/work/work-runtime-ktx/api/current.txt
@@ -22,21 +22,9 @@
public final class ListenableFutureKt {
}
- public final class OneTimeWorkRequestKt {
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
- method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
- }
-
public final class OperationKt {
method public static suspend inline Object? await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>);
}
- public final class PeriodicWorkRequestKt {
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
- method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval);
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit flexTimeIntervalUnit);
- method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval, java.time.Duration flexTimeInterval);
- }
-
}
diff --git a/work/work-runtime-ktx/api/public_plus_experimental_current.txt b/work/work-runtime-ktx/api/public_plus_experimental_current.txt
index d1cd3d5..efdea4c 100644
--- a/work/work-runtime-ktx/api/public_plus_experimental_current.txt
+++ b/work/work-runtime-ktx/api/public_plus_experimental_current.txt
@@ -22,21 +22,9 @@
public final class ListenableFutureKt {
}
- public final class OneTimeWorkRequestKt {
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
- method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
- }
-
public final class OperationKt {
method public static suspend inline Object? await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>);
}
- public final class PeriodicWorkRequestKt {
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
- method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval);
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit flexTimeIntervalUnit);
- method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval, java.time.Duration flexTimeInterval);
- }
-
}
diff --git a/work/work-runtime-ktx/api/restricted_current.ignore b/work/work-runtime-ktx/api/restricted_current.ignore
index 1c21e58..fb0fcc1 100644
--- a/work/work-runtime-ktx/api/restricted_current.ignore
+++ b/work/work-runtime-ktx/api/restricted_current.ignore
@@ -1,4 +1,5 @@
// Baseline format: 1.0
+
ParameterNameChange: androidx.work.CoroutineWorker#doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result>) parameter #0:
Attempted to remove parameter name from parameter arg1 in androidx.work.CoroutineWorker.doWork
ParameterNameChange: androidx.work.CoroutineWorker#getForegroundInfo(kotlin.coroutines.Continuation<? super androidx.work.ForegroundInfo>) parameter #0:
@@ -9,3 +10,8 @@
Attempted to remove parameter name from parameter arg2 in androidx.work.CoroutineWorker.setProgress
ParameterNameChange: androidx.work.OperationKt#await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>) parameter #1:
Attempted to remove parameter name from parameter arg2 in androidx.work.OperationKt.await
+
+RemovedClass: androidx.work.OneTimeWorkRequestKt:
+ Removed class androidx.work.OneTimeWorkRequestKt
+RemovedClass: androidx.work.PeriodicWorkRequestKt:
+ Removed class androidx.work.PeriodicWorkRequestKt
diff --git a/work/work-runtime-ktx/api/restricted_current.txt b/work/work-runtime-ktx/api/restricted_current.txt
index d1cd3d5..efdea4c 100644
--- a/work/work-runtime-ktx/api/restricted_current.txt
+++ b/work/work-runtime-ktx/api/restricted_current.txt
@@ -22,21 +22,9 @@
public final class ListenableFutureKt {
}
- public final class OneTimeWorkRequestKt {
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
- method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
- }
-
public final class OperationKt {
method public static suspend inline Object? await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>);
}
- public final class PeriodicWorkRequestKt {
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
- method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval);
- method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit flexTimeIntervalUnit);
- method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval, java.time.Duration flexTimeInterval);
- }
-
}
diff --git a/work/work-runtime-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt b/work/work-runtime-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt
deleted file mode 100644
index 60a4d98..0000000
--- a/work/work-runtime-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt
+++ /dev/null
@@ -1,36 +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.
- */
-
-// Always inline ktx extension methods unless we have additional call site costs.
-@file:Suppress("NOTHING_TO_INLINE")
-
-package androidx.work
-
-import androidx.annotation.NonNull
-import kotlin.reflect.KClass
-
-/**
- * Creates a [OneTimeWorkRequest] with the given [ListenableWorker].
- */
-public inline fun <reified W : ListenableWorker> OneTimeWorkRequestBuilder():
- OneTimeWorkRequest.Builder = OneTimeWorkRequest.Builder(W::class.java)
-
-/**
- * Sets an [InputMerger] on the [OneTimeWorkRequest.Builder].
- */
-public inline fun OneTimeWorkRequest.Builder.setInputMerger(
- @NonNull inputMerger: KClass<out InputMerger>
-): OneTimeWorkRequest.Builder = setInputMerger(inputMerger.java)
diff --git a/work/work-runtime-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt b/work/work-runtime-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt
deleted file mode 100644
index a057043..0000000
--- a/work/work-runtime-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt
+++ /dev/null
@@ -1,84 +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.work
-
-import androidx.annotation.RequiresApi
-import java.time.Duration
-import java.util.concurrent.TimeUnit
-
-/**
- * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
- *
- * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
- * @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
- */
-public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Long,
- repeatIntervalTimeUnit: TimeUnit
-): PeriodicWorkRequest.Builder {
- return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, repeatIntervalTimeUnit)
-}
-
-/**
- * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
- *
- * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
- */
-@RequiresApi(26)
-public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Duration
-): PeriodicWorkRequest.Builder {
- return PeriodicWorkRequest.Builder(W::class.java, repeatInterval)
-}
-
-/**
- * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
- *
- * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
- * @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
- * @param flexTimeInterval @see [androidx.work.PeriodicWorkRequest.Builder]
- * @param flexTimeIntervalUnit @see [androidx.work.PeriodicWorkRequest.Builder]
- */
-public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Long,
- repeatIntervalTimeUnit: TimeUnit,
- flexTimeInterval: Long,
- flexTimeIntervalUnit: TimeUnit
-): PeriodicWorkRequest.Builder {
-
- return PeriodicWorkRequest.Builder(
- W::class.java,
- repeatInterval,
- repeatIntervalTimeUnit,
- flexTimeInterval,
- flexTimeIntervalUnit
- )
-}
-
-/**
- * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
- *
- * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
- * @param flexTimeInterval @see [androidx.work.PeriodicWorkRequest.Builder]
- */
-@RequiresApi(26)
-public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Duration,
- flexTimeInterval: Duration
-): PeriodicWorkRequest.Builder {
- return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, flexTimeInterval)
-}
diff --git a/work/work-runtime/api/api_lint.ignore b/work/work-runtime/api/api_lint.ignore
index 665223c..f3c1cac 100644
--- a/work/work-runtime/api/api_lint.ignore
+++ b/work/work-runtime/api/api_lint.ignore
@@ -126,6 +126,16 @@
MissingGetterMatchingBuilder: androidx.work.WorkRequest.Builder#setInputData(androidx.work.Data):
W does not declare a `getInputData()` method matching method androidx.work.WorkRequest.Builder.setInputData(androidx.work.Data)
+MissingNullability: androidx.work.OneTimeWorkRequestKt#OneTimeWorkRequestBuilder():
+ Missing nullability on method `OneTimeWorkRequestBuilder` return
+MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(java.time.Duration):
+ Missing nullability on method `PeriodicWorkRequestBuilder` return
+MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(java.time.Duration, java.time.Duration):
+ Missing nullability on method `PeriodicWorkRequestBuilder` return
+MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(long, java.util.concurrent.TimeUnit):
+ Missing nullability on method `PeriodicWorkRequestBuilder` return
+MissingNullability: androidx.work.PeriodicWorkRequestKt#PeriodicWorkRequestBuilder(long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit):
+ Missing nullability on method `PeriodicWorkRequestBuilder` return
NoByteOrShort: androidx.work.Data#getByte(String, byte):
Should avoid odd sized primitives; use `int` instead of `byte` in method androidx.work.Data.getByte(String,byte)
diff --git a/work/work-runtime/api/current.ignore b/work/work-runtime/api/current.ignore
index d568462..759c5da 100644
--- a/work/work-runtime/api/current.ignore
+++ b/work/work-runtime/api/current.ignore
@@ -1,3 +1,27 @@
// Baseline format: 1.0
AddedAbstractMethod: androidx.work.WorkManager#getConfiguration():
Added method androidx.work.WorkManager.getConfiguration()
+
+
+ChangedType: androidx.work.OneTimeWorkRequest#from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>>):
+ Method androidx.work.OneTimeWorkRequest.from has changed return type from java.util.List<androidx.work.OneTimeWorkRequest!> to java.util.List<androidx.work.OneTimeWorkRequest>
+ChangedType: androidx.work.WorkRequest.Builder#addTag(String):
+ Method androidx.work.WorkRequest.Builder.addTag has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#keepResultsForAtLeast(java.time.Duration):
+ Method androidx.work.WorkRequest.Builder.keepResultsForAtLeast has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#keepResultsForAtLeast(long, java.util.concurrent.TimeUnit):
+ Method androidx.work.WorkRequest.Builder.keepResultsForAtLeast has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration):
+ Method androidx.work.WorkRequest.Builder.setBackoffCriteria has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit):
+ Method androidx.work.WorkRequest.Builder.setBackoffCriteria has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setConstraints(androidx.work.Constraints):
+ Method androidx.work.WorkRequest.Builder.setConstraints has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setExpedited(androidx.work.OutOfQuotaPolicy):
+ Method androidx.work.WorkRequest.Builder.setExpedited has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(java.time.Duration):
+ Method androidx.work.WorkRequest.Builder.setInitialDelay has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(long, java.util.concurrent.TimeUnit):
+ Method androidx.work.WorkRequest.Builder.setInitialDelay has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setInputData(androidx.work.Data):
+ Method androidx.work.WorkRequest.Builder.setInputData has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 6df5943..3d3cc10 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -208,13 +208,24 @@
}
public final class OneTimeWorkRequest extends androidx.work.WorkRequest {
- method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker>);
- method public static java.util.List<androidx.work.OneTimeWorkRequest!> from(java.util.List<java.lang.Class<? extends androidx.work.ListenableWorker>!>);
+ method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public static java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>> workerClasses);
+ field public static final androidx.work.OneTimeWorkRequest.Companion Companion;
}
public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
- ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>);
- method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
+ ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger> inputMerger);
+ }
+
+ public static final class OneTimeWorkRequest.Companion {
+ method public androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>> workerClasses);
+ }
+
+ public final class OneTimeWorkRequestKt {
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
+ method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
}
public interface Operation {
@@ -247,15 +258,26 @@
}
public final class PeriodicWorkRequest extends androidx.work.WorkRequest {
+ field public static final androidx.work.PeriodicWorkRequest.Companion Companion;
field public static final long MIN_PERIODIC_FLEX_MILLIS = 300000L; // 0x493e0L
field public static final long MIN_PERIODIC_INTERVAL_MILLIS = 900000L; // 0xdbba0L
}
public static final class PeriodicWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.PeriodicWorkRequest.Builder,androidx.work.PeriodicWorkRequest> {
- ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit);
- ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration);
- ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit);
- ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
+ }
+
+ public static final class PeriodicWorkRequest.Companion {
+ }
+
+ public final class PeriodicWorkRequestKt {
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval);
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit flexTimeIntervalUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval, java.time.Duration flexTimeInterval);
}
public interface ProgressUpdater {
@@ -364,23 +386,28 @@
public abstract class WorkRequest {
method public java.util.UUID getId();
+ property public java.util.UUID id;
+ field public static final androidx.work.WorkRequest.Companion Companion;
field public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_MILLIS = 18000000L; // 0x112a880L
field public static final long MIN_BACKOFF_MILLIS = 10000L; // 0x2710L
}
- public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder<?, ?>, W extends androidx.work.WorkRequest> {
- method public final B addTag(String);
+ public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder<B, ?>, W extends androidx.work.WorkRequest> {
+ method public final B addTag(String tag);
method public final W build();
- method public final B keepResultsForAtLeast(long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration);
- method public final B setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration);
- method public final B setConstraints(androidx.work.Constraints);
- method public B setExpedited(androidx.work.OutOfQuotaPolicy);
- method public B setInitialDelay(long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public B setInitialDelay(java.time.Duration);
- method public final B setInputData(androidx.work.Data);
+ method public final B keepResultsForAtLeast(long duration, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration duration);
+ method public final B setBackoffCriteria(androidx.work.BackoffPolicy backoffPolicy, long backoffDelay, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy backoffPolicy, java.time.Duration duration);
+ method public final B setConstraints(androidx.work.Constraints constraints);
+ method public B setExpedited(androidx.work.OutOfQuotaPolicy policy);
+ method public B setInitialDelay(long duration, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public B setInitialDelay(java.time.Duration duration);
+ method public final B setInputData(androidx.work.Data inputData);
+ }
+
+ public static final class WorkRequest.Companion {
}
public abstract class Worker extends androidx.work.ListenableWorker {
diff --git a/work/work-runtime/api/public_plus_experimental_current.txt b/work/work-runtime/api/public_plus_experimental_current.txt
index 6df5943..3d3cc10 100644
--- a/work/work-runtime/api/public_plus_experimental_current.txt
+++ b/work/work-runtime/api/public_plus_experimental_current.txt
@@ -208,13 +208,24 @@
}
public final class OneTimeWorkRequest extends androidx.work.WorkRequest {
- method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker>);
- method public static java.util.List<androidx.work.OneTimeWorkRequest!> from(java.util.List<java.lang.Class<? extends androidx.work.ListenableWorker>!>);
+ method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public static java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>> workerClasses);
+ field public static final androidx.work.OneTimeWorkRequest.Companion Companion;
}
public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
- ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>);
- method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
+ ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger> inputMerger);
+ }
+
+ public static final class OneTimeWorkRequest.Companion {
+ method public androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>> workerClasses);
+ }
+
+ public final class OneTimeWorkRequestKt {
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
+ method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
}
public interface Operation {
@@ -247,15 +258,26 @@
}
public final class PeriodicWorkRequest extends androidx.work.WorkRequest {
+ field public static final androidx.work.PeriodicWorkRequest.Companion Companion;
field public static final long MIN_PERIODIC_FLEX_MILLIS = 300000L; // 0x493e0L
field public static final long MIN_PERIODIC_INTERVAL_MILLIS = 900000L; // 0xdbba0L
}
public static final class PeriodicWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.PeriodicWorkRequest.Builder,androidx.work.PeriodicWorkRequest> {
- ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit);
- ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration);
- ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit);
- ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
+ }
+
+ public static final class PeriodicWorkRequest.Companion {
+ }
+
+ public final class PeriodicWorkRequestKt {
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval);
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit flexTimeIntervalUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval, java.time.Duration flexTimeInterval);
}
public interface ProgressUpdater {
@@ -364,23 +386,28 @@
public abstract class WorkRequest {
method public java.util.UUID getId();
+ property public java.util.UUID id;
+ field public static final androidx.work.WorkRequest.Companion Companion;
field public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_MILLIS = 18000000L; // 0x112a880L
field public static final long MIN_BACKOFF_MILLIS = 10000L; // 0x2710L
}
- public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder<?, ?>, W extends androidx.work.WorkRequest> {
- method public final B addTag(String);
+ public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder<B, ?>, W extends androidx.work.WorkRequest> {
+ method public final B addTag(String tag);
method public final W build();
- method public final B keepResultsForAtLeast(long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration);
- method public final B setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration);
- method public final B setConstraints(androidx.work.Constraints);
- method public B setExpedited(androidx.work.OutOfQuotaPolicy);
- method public B setInitialDelay(long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public B setInitialDelay(java.time.Duration);
- method public final B setInputData(androidx.work.Data);
+ method public final B keepResultsForAtLeast(long duration, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration duration);
+ method public final B setBackoffCriteria(androidx.work.BackoffPolicy backoffPolicy, long backoffDelay, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy backoffPolicy, java.time.Duration duration);
+ method public final B setConstraints(androidx.work.Constraints constraints);
+ method public B setExpedited(androidx.work.OutOfQuotaPolicy policy);
+ method public B setInitialDelay(long duration, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public B setInitialDelay(java.time.Duration duration);
+ method public final B setInputData(androidx.work.Data inputData);
+ }
+
+ public static final class WorkRequest.Companion {
}
public abstract class Worker extends androidx.work.ListenableWorker {
diff --git a/work/work-runtime/api/restricted_current.ignore b/work/work-runtime/api/restricted_current.ignore
index d568462..759c5da 100644
--- a/work/work-runtime/api/restricted_current.ignore
+++ b/work/work-runtime/api/restricted_current.ignore
@@ -1,3 +1,27 @@
// Baseline format: 1.0
AddedAbstractMethod: androidx.work.WorkManager#getConfiguration():
Added method androidx.work.WorkManager.getConfiguration()
+
+
+ChangedType: androidx.work.OneTimeWorkRequest#from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>>):
+ Method androidx.work.OneTimeWorkRequest.from has changed return type from java.util.List<androidx.work.OneTimeWorkRequest!> to java.util.List<androidx.work.OneTimeWorkRequest>
+ChangedType: androidx.work.WorkRequest.Builder#addTag(String):
+ Method androidx.work.WorkRequest.Builder.addTag has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#keepResultsForAtLeast(java.time.Duration):
+ Method androidx.work.WorkRequest.Builder.keepResultsForAtLeast has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#keepResultsForAtLeast(long, java.util.concurrent.TimeUnit):
+ Method androidx.work.WorkRequest.Builder.keepResultsForAtLeast has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration):
+ Method androidx.work.WorkRequest.Builder.setBackoffCriteria has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit):
+ Method androidx.work.WorkRequest.Builder.setBackoffCriteria has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setConstraints(androidx.work.Constraints):
+ Method androidx.work.WorkRequest.Builder.setConstraints has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setExpedited(androidx.work.OutOfQuotaPolicy):
+ Method androidx.work.WorkRequest.Builder.setExpedited has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(java.time.Duration):
+ Method androidx.work.WorkRequest.Builder.setInitialDelay has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(long, java.util.concurrent.TimeUnit):
+ Method androidx.work.WorkRequest.Builder.setInitialDelay has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
+ChangedType: androidx.work.WorkRequest.Builder#setInputData(androidx.work.Data):
+ Method androidx.work.WorkRequest.Builder.setInputData has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 6df5943..3d3cc10 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -208,13 +208,24 @@
}
public final class OneTimeWorkRequest extends androidx.work.WorkRequest {
- method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker>);
- method public static java.util.List<androidx.work.OneTimeWorkRequest!> from(java.util.List<java.lang.Class<? extends androidx.work.ListenableWorker>!>);
+ method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public static java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>> workerClasses);
+ field public static final androidx.work.OneTimeWorkRequest.Companion Companion;
}
public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
- ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>);
- method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
+ ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger> inputMerger);
+ }
+
+ public static final class OneTimeWorkRequest.Companion {
+ method public androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker> workerClass);
+ method public java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<? extends java.lang.Class<? extends androidx.work.ListenableWorker>> workerClasses);
+ }
+
+ public final class OneTimeWorkRequestKt {
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
+ method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
}
public interface Operation {
@@ -247,15 +258,26 @@
}
public final class PeriodicWorkRequest extends androidx.work.WorkRequest {
+ field public static final androidx.work.PeriodicWorkRequest.Companion Companion;
field public static final long MIN_PERIODIC_FLEX_MILLIS = 300000L; // 0x493e0L
field public static final long MIN_PERIODIC_INTERVAL_MILLIS = 900000L; // 0xdbba0L
}
public static final class PeriodicWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.PeriodicWorkRequest.Builder,androidx.work.PeriodicWorkRequest> {
- ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit);
- ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration);
- ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit);
- ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
+ }
+
+ public static final class PeriodicWorkRequest.Companion {
+ }
+
+ public final class PeriodicWorkRequestKt {
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval);
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit flexTimeIntervalUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration repeatInterval, java.time.Duration flexTimeInterval);
}
public interface ProgressUpdater {
@@ -364,23 +386,28 @@
public abstract class WorkRequest {
method public java.util.UUID getId();
+ property public java.util.UUID id;
+ field public static final androidx.work.WorkRequest.Companion Companion;
field public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_MILLIS = 18000000L; // 0x112a880L
field public static final long MIN_BACKOFF_MILLIS = 10000L; // 0x2710L
}
- public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder<?, ?>, W extends androidx.work.WorkRequest> {
- method public final B addTag(String);
+ public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder<B, ?>, W extends androidx.work.WorkRequest> {
+ method public final B addTag(String tag);
method public final W build();
- method public final B keepResultsForAtLeast(long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration);
- method public final B setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration);
- method public final B setConstraints(androidx.work.Constraints);
- method public B setExpedited(androidx.work.OutOfQuotaPolicy);
- method public B setInitialDelay(long, java.util.concurrent.TimeUnit);
- method @RequiresApi(26) public B setInitialDelay(java.time.Duration);
- method public final B setInputData(androidx.work.Data);
+ method public final B keepResultsForAtLeast(long duration, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration duration);
+ method public final B setBackoffCriteria(androidx.work.BackoffPolicy backoffPolicy, long backoffDelay, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy backoffPolicy, java.time.Duration duration);
+ method public final B setConstraints(androidx.work.Constraints constraints);
+ method public B setExpedited(androidx.work.OutOfQuotaPolicy policy);
+ method public B setInitialDelay(long duration, java.util.concurrent.TimeUnit timeUnit);
+ method @RequiresApi(26) public B setInitialDelay(java.time.Duration duration);
+ method public final B setInputData(androidx.work.Data inputData);
+ }
+
+ public static final class WorkRequest.Companion {
}
public abstract class Worker extends androidx.work.ListenableWorker {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java
index 85eef8b..2b13c3e 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java
@@ -63,7 +63,7 @@
@SmallTest
public void testStopWork_invalidWorkId() {
String id = "INVALID_WORK_ID";
- assertThat(mProcessor.stopWork(id), is(false));
+ assertThat(mProcessor.stopWork(new WorkRunId(id)), is(false));
}
@Test
@@ -72,8 +72,8 @@
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class).build();
String id = work.getStringId();
insertWork(work);
- assertThat(mProcessor.startWork(id), is(true));
- assertThat(mProcessor.startWork(id), is(false));
+ assertThat(mProcessor.startWork(new WorkRunId(id)), is(true));
+ assertThat(mProcessor.startWork(new WorkRunId(id)), is(false));
}
@Test
@@ -83,7 +83,7 @@
insertWork(work);
assertThat(mProcessor.hasWork(), is(false));
- mProcessor.startWork(work.getStringId());
+ mProcessor.startWork(new WorkRunId(work.getStringId()));
assertThat(mProcessor.hasWork(), is(true));
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
index 10e1b9a..56fc900 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
@@ -105,24 +105,26 @@
@Test
@MediumTest
fun testInterruptNotInCriticalSection() {
- val request = OneTimeWorkRequest.Builder(StopLatchWorker::class.java)
- .build()
- insertWork(request)
+ val request1 = OneTimeWorkRequest.Builder(StopLatchWorker::class.java).build()
+ val request2 = OneTimeWorkRequest.Builder(StopLatchWorker::class.java).build()
+ insertWork(request1)
+ insertWork(request2)
var listenerCalled = false
val listener = ExecutionListener { id, _ ->
if (!listenerCalled) {
listenerCalled = true
- assertEquals(request.workSpec.id, id)
+ assertEquals(request1.workSpec.id, id)
}
}
processor.addExecutionListener(listener)
- processor.startWork(request.workSpec.id)
+ val workRunId = WorkRunId(request1.workSpec.id)
+ processor.startWork(workRunId)
val firstWorker = runBlocking { lastCreatedWorker.filterNotNull().first() }
val blockedThread = Executors.newSingleThreadExecutor()
blockedThread.execute {
// gonna stall for 10 seconds
- processor.stopWork(request.workSpec.id)
+ processor.stopWork(workRunId)
}
assertTrue((firstWorker as StopLatchWorker).awaitOnStopCall())
// onStop call results in onExecuted. It happens on "main thread", which is instant
@@ -133,7 +135,7 @@
val executionFinished = CountDownLatch(1)
processor.addExecutionListener { _, _ -> executionFinished.countDown() }
// This would have previously failed trying to acquire a lock
- processor.startWork(request.workSpec.id)
+ processor.startWork(WorkRunId(request2.workSpec.id))
val secondWorker =
runBlocking { lastCreatedWorker.filterNotNull().filter { it != firstWorker }.first() }
(secondWorker as StopLatchWorker).countDown()
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index 2edd07d..52cc8fe 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -29,6 +29,7 @@
import static androidx.work.WorkInfo.State.RUNNING;
import static androidx.work.WorkInfo.State.SUCCEEDED;
import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
+import static androidx.work.impl.workers.ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
@@ -104,6 +105,7 @@
import androidx.work.impl.utils.PreferenceUtils;
import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
import androidx.work.impl.workers.ConstraintTrackingWorker;
+import androidx.work.impl.workers.ConstraintTrackingWorkerKt;
import androidx.work.worker.InfiniteTestWorker;
import androidx.work.worker.StopAwareWorker;
import androidx.work.worker.TestWorker;
@@ -1793,7 +1795,7 @@
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
assertThat(workSpec.input.getString(
- ConstraintTrackingWorker.ARGUMENT_CLASS_NAME),
+ ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME),
is(TestWorker.class.getName()));
}
@@ -1811,8 +1813,7 @@
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
- assertThat(workSpec.input.getString(
- ConstraintTrackingWorker.ARGUMENT_CLASS_NAME),
+ assertThat(workSpec.input.getString(ARGUMENT_CLASS_NAME),
is(TestWorker.class.getName()));
}
@@ -1822,7 +1823,7 @@
public void testEnqueueApi23To25_withConstraintTrackingWorker_expectsOriginalWorker()
throws ExecutionException, InterruptedException {
Data data = new Data.Builder()
- .put(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, TestWorker.class.getName())
+ .put(ARGUMENT_CLASS_NAME, TestWorker.class.getName())
.build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ConstraintTrackingWorker.class)
@@ -1835,8 +1836,7 @@
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
- assertThat(workSpec.input.getString(
- ConstraintTrackingWorker.ARGUMENT_CLASS_NAME),
+ assertThat(workSpec.input.getString(ARGUMENT_CLASS_NAME),
is(TestWorker.class.getName()));
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
index b381ac7..3de3389 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
@@ -16,6 +16,8 @@
package androidx.work.impl.background.greedy;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -34,6 +36,7 @@
import androidx.work.WorkManagerTest;
import androidx.work.impl.Processor;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
import androidx.work.impl.constraints.WorkConstraintsTracker;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -42,6 +45,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import java.util.Collections;
@@ -86,7 +90,9 @@
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
WorkSpec workSpec = work.getWorkSpec();
mGreedyScheduler.schedule(workSpec);
- verify(mWorkManagerImpl).startWork(workSpec.id);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mWorkManagerImpl).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpec.id);
}
@Test
@@ -99,7 +105,9 @@
// PeriodicWorkRequests are special because their periodStartTime is set to 0.
// So the first invocation will always result in startWork(). Subsequent runs will
// use `delayedStartWork()`.
- verify(mWorkManagerImpl).startWork(periodicWork.getStringId());
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mWorkManagerImpl).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(periodicWork.getStringId());
}
@Test
@@ -141,14 +149,20 @@
@SmallTest
public void testGreedyScheduler_startsWorkWhenConstraintsMet() {
mGreedyScheduler.onAllConstraintsMet(Collections.singletonList(TEST_ID));
- verify(mWorkManagerImpl).startWork(TEST_ID);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mWorkManagerImpl).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(TEST_ID);
}
@Test
@SmallTest
public void testGreedyScheduler_stopsWorkWhenConstraintsNotMet() {
+ // in order to stop the work, we should start it first.
+ mGreedyScheduler.onAllConstraintsMet(Collections.singletonList(TEST_ID));
mGreedyScheduler.onAllConstraintsNotMet(Collections.singletonList(TEST_ID));
- verify(mWorkManagerImpl).stopWork(TEST_ID);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mWorkManagerImpl).stopWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(TEST_ID);
}
@Test
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 06050bd..55000fb 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -16,6 +16,8 @@
package androidx.work.impl.background.systemalarm;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
@@ -23,6 +25,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -51,6 +54,7 @@
import androidx.work.impl.Processor;
import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
import androidx.work.impl.constraints.NetworkState;
import androidx.work.impl.constraints.trackers.BatteryNotLowTracker;
import androidx.work.impl.constraints.trackers.ConstraintTracker;
@@ -236,7 +240,9 @@
new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, intent, START_ID));
mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
assertThat(mLatch.getCount(), is(0L));
- verify(mSpyProcessor, times(1)).startWork(workSpecId);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mSpyProcessor, times(1)).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
}
@Test
@@ -259,7 +265,7 @@
CommandHandler.ACTION_DELAY_MET,
CommandHandler.ACTION_STOP_WORK,
CommandHandler.ACTION_EXECUTION_COMPLETED));
- verify(mSpyProcessor, times(0)).startWork(workSpecId);
+ verify(mSpyProcessor, times(0)).startWork(any(WorkRunId.class));
}
@Test
@@ -285,8 +291,13 @@
mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
assertThat(mLatch.getCount(), is(0L));
- verify(mSpyProcessor, times(1)).startWork(workSpecId);
- verify(mWorkManager, times(1)).stopWork(workSpecId);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mSpyProcessor, times(1)).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+
+ ArgumentCaptor<WorkRunId> captorStop = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mWorkManager, times(1)).stopWork(captorStop.capture());
+ assertThat(captorStop.getValue().getWorkSpecId()).isEqualTo(workSpecId);
}
@Test
@@ -310,8 +321,13 @@
mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
assertThat(mLatch.getCount(), is(0L));
- verify(mSpyProcessor, times(1)).startWork(workSpecId);
- verify(mWorkManager, times(1)).stopWork(workSpecId);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mSpyProcessor, times(1)).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+
+ ArgumentCaptor<WorkRunId> captorStop = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mWorkManager, times(1)).stopWork(captorStop.capture());
+ assertThat(captorStop.getValue().getWorkSpecId()).isEqualTo(workSpecId);
}
@Test
@@ -329,7 +345,10 @@
mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
assertThat(mLatch.getCount(), is(0L));
- verify(mSpyProcessor, times(1)).startWork(workSpecId);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mSpyProcessor, times(1)).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+
List<String> intentActions = mSpyDispatcher.getIntentActions();
assertThat(intentActions,
IsIterableContainingInOrder.contains(
@@ -368,7 +387,7 @@
mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
assertThat(mLatch.getCount(), is(0L));
// Should not call startWork, but schedule an alarm.
- verify(mSpyProcessor, times(0)).startWork(workSpecId);
+ verify(mSpyProcessor, times(0)).startWork(any(WorkRunId.class));
}
@Test
@@ -404,7 +423,9 @@
mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
assertThat(mLatch.getCount(), is(0L));
- verify(mSpyProcessor, times(1)).startWork(workSpecId);
+ ArgumentCaptor<WorkRunId> captor = ArgumentCaptor.forClass(WorkRunId.class);
+ verify(mSpyProcessor, times(1)).startWork(captor.capture());
+ assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
}
@Test
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
index da7de00..d869cf6 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
@@ -35,6 +35,7 @@
import androidx.work.impl.Scheduler
import androidx.work.impl.WorkDatabase
import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.WorkRunId
import androidx.work.impl.constraints.WorkConstraintsCallback
import androidx.work.impl.constraints.WorkConstraintsTracker
import androidx.work.impl.foreground.SystemForegroundDispatcher.createCancelWorkIntent
@@ -381,7 +382,9 @@
val metadata = ForegroundInfo(notificationId, notification)
val intent = createStartForegroundIntent(context, request.stringId, metadata)
dispatcher.onStartCommand(intent)
- val stopWorkRunnable = StopWorkRunnable(workManager, request.stringId, false)
+ val stopWorkRunnable = StopWorkRunnable(
+ workManager, WorkRunId(request.stringId), false
+ )
stopWorkRunnable.run()
val state = workDatabase.workSpecDao().getState(request.stringId)
assertThat(state, `is`(WorkInfo.State.RUNNING))
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index d1ed219..eeb8176 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -16,6 +16,8 @@
package androidx.work.impl.workers;
+import static androidx.work.impl.workers.ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME;
+
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -318,7 +320,7 @@
.build();
Data input = new Data.Builder()
- .putString(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, delegateName)
+ .putString(ARGUMENT_CLASS_NAME, delegateName)
.putBoolean(TEST_ARGUMENT_NAME, true)
.build();
@@ -350,7 +352,6 @@
is(CoreMatchers.<ListenableWorker>instanceOf(ConstraintTrackingWorker.class)));
// mWorker is already a spy
mWorker = (ConstraintTrackingWorker) worker;
- when(mWorker.getWorkDatabase()).thenReturn(mDatabase);
}
private WorkerWrapper.Builder createWorkerWrapperBuilder() {
diff --git a/work/work-runtime/src/main/java/androidx/work/BackoffPolicy.java b/work/work-runtime/src/main/java/androidx/work/BackoffPolicy.java
deleted file mode 100644
index c721565..0000000
--- a/work/work-runtime/src/main/java/androidx/work/BackoffPolicy.java
+++ /dev/null
@@ -1,40 +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.work;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * An enumeration of backoff policies when retrying work. These policies are used when you have a
- * return {@link ListenableWorker.Result#retry()} from a worker to determine the correct backoff
- * time. Backoff policies are set in
- * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)} or one of its
- * variants.
- */
-
-public enum BackoffPolicy {
-
- /**
- * Used to indicate that {@link WorkManager} should increase the backoff time exponentially
- */
- EXPONENTIAL,
-
- /**
- * Used to indicate that {@link WorkManager} should increase the backoff time linearly
- */
- LINEAR
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/BackoffPolicy.kt b/work/work-runtime/src/main/java/androidx/work/BackoffPolicy.kt
new file mode 100644
index 0000000..e0539b6
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/BackoffPolicy.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.work
+
+/**
+ * An enumeration of backoff policies when retrying work. These policies are used when you have a
+ * return [ListenableWorker.Result.retry] from a worker to determine the correct backoff time.
+ * Backoff policies are set in [WorkRequest.Builder.setBackoffCriteria] or one of its variants.
+ */
+enum class BackoffPolicy {
+ /**
+ * Used to indicate that [WorkManager] should increase the backoff time exponentially
+ */
+ EXPONENTIAL,
+
+ /**
+ * Used to indicate that [WorkManager] should increase the backoff time linearly
+ */
+ LINEAR
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/ExistingPeriodicWorkPolicy.java b/work/work-runtime/src/main/java/androidx/work/ExistingPeriodicWorkPolicy.kt
similarity index 89%
rename from work/work-runtime/src/main/java/androidx/work/ExistingPeriodicWorkPolicy.java
rename to work/work-runtime/src/main/java/androidx/work/ExistingPeriodicWorkPolicy.kt
index 3fc33f9..d0bc5e3 100644
--- a/work/work-runtime/src/main/java/androidx/work/ExistingPeriodicWorkPolicy.java
+++ b/work/work-runtime/src/main/java/androidx/work/ExistingPeriodicWorkPolicy.kt
@@ -13,16 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package androidx.work;
+package androidx.work
/**
* An enumeration of the conflict resolution policies available to unique
- * {@link PeriodicWorkRequest}s in case of a collision.
+ * [PeriodicWorkRequest]s in case of a collision.
*/
-
-public enum ExistingPeriodicWorkPolicy {
-
+enum class ExistingPeriodicWorkPolicy {
/**
* If there is existing pending (uncompleted) work with the same unique name, cancel and delete
* it. Then, insert the newly-specified work.
@@ -34,4 +31,4 @@
* Otherwise, insert the newly-specified work.
*/
KEEP
-}
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/ExistingWorkPolicy.java b/work/work-runtime/src/main/java/androidx/work/ExistingWorkPolicy.kt
similarity index 73%
rename from work/work-runtime/src/main/java/androidx/work/ExistingWorkPolicy.java
rename to work/work-runtime/src/main/java/androidx/work/ExistingWorkPolicy.kt
index 5161c30..455c0d5 100644
--- a/work/work-runtime/src/main/java/androidx/work/ExistingWorkPolicy.java
+++ b/work/work-runtime/src/main/java/androidx/work/ExistingWorkPolicy.kt
@@ -13,19 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package androidx.work;
+package androidx.work
/**
* An enumeration of the conflict resolution policies available to unique
- * {@link OneTimeWorkRequest}s in case of a collision.
+ * [OneTimeWorkRequest]s in case of a collision.
*/
-
-public enum ExistingWorkPolicy {
-
+enum class ExistingWorkPolicy {
/**
* If there is existing pending (uncompleted) work with the same unique name, cancel and delete
- * it. Then, insert the newly-specified work.
+ * it. Then, insert the newly-specified work.
*/
REPLACE,
@@ -39,10 +36,10 @@
* If there is existing pending (uncompleted) work with the same unique name, append the
* newly-specified work as a child of all the leaves of that work sequence. Otherwise, insert
* the newly-specified work as the start of a new sequence.
- * <br/>
- * <b>Note:</b> When using APPEND with failed or cancelled prerequisites, newly enqueued work
+ *
+ * **Note:** When using APPEND with failed or cancelled prerequisites, newly enqueued work
* will also be marked as failed or cancelled respectively. Use
- * {@link ExistingWorkPolicy#APPEND_OR_REPLACE} to create a new chain of work.
+ * [ExistingWorkPolicy.APPEND_OR_REPLACE] to create a new chain of work.
*/
APPEND,
@@ -50,9 +47,9 @@
* If there is existing pending (uncompleted) work with the same unique name, append the
* newly-specified work as the child of all the leaves of that work sequence. Otherwise, insert
* the newly-specified work as the start of a new sequence.
- * <br/>
- * <b>Note:</b> If there are failed or cancelled prerequisites, these prerequisites are
- * <i>dropped</i> and the newly-specified work is the start of a new sequence.
+ *
+ * **Note:** If there are failed or cancelled prerequisites, these prerequisites are
+ * *dropped* and the newly-specified work is the start of a new sequence.
*/
- APPEND_OR_REPLACE,
-}
+ APPEND_OR_REPLACE
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/NetworkType.java b/work/work-runtime/src/main/java/androidx/work/NetworkType.kt
similarity index 83%
rename from work/work-runtime/src/main/java/androidx/work/NetworkType.java
rename to work/work-runtime/src/main/java/androidx/work/NetworkType.kt
index 06d7e3a..ac00245 100644
--- a/work/work-runtime/src/main/java/androidx/work/NetworkType.java
+++ b/work/work-runtime/src/main/java/androidx/work/NetworkType.kt
@@ -13,17 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package androidx.work
-package androidx.work;
-
-import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresApi
/**
- * An enumeration of various network types that can be used as {@link Constraints} for work.
+ * An enumeration of various network types that can be used as [Constraints] for work.
*/
-
-public enum NetworkType {
-
+enum class NetworkType {
/**
* A network is not required for this work.
*/
@@ -54,9 +51,9 @@
* generally metered, but are currently unmetered.
*
* Note: This capability can be changed at any time. When it is removed,
- * {@link ListenableWorker}s are responsible for stopping any data transfer that should not
+ * [ListenableWorker]s are responsible for stopping any data transfer that should not
* occur on a metered network.
*/
@RequiresApi(30)
TEMPORARILY_UNMETERED
-}
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.java b/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.java
deleted file mode 100644
index d33851e..0000000
--- a/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.java
+++ /dev/null
@@ -1,118 +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.work;
-
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A {@link WorkRequest} for non-repeating work.
- * <p>
- * OneTimeWorkRequests can be put in simple or complex graphs of work by using methods like
- * {@link WorkManager#beginWith(OneTimeWorkRequest)} or {@link WorkManager#beginWith(List)}.
- */
-
-public final class OneTimeWorkRequest extends WorkRequest {
-
- /**
- * Creates a {@link OneTimeWorkRequest} with defaults from a {@link ListenableWorker} class
- * name.
- *
- * @param workerClass An {@link ListenableWorker} class name
- * @return A {@link OneTimeWorkRequest} constructed by using defaults in the {@link Builder}
- */
- public static @NonNull OneTimeWorkRequest from(
- @NonNull Class<? extends ListenableWorker> workerClass) {
- return new OneTimeWorkRequest.Builder(workerClass).build();
- }
-
- /**
- * Creates a list of {@link OneTimeWorkRequest}s with defaults from an array of
- * {@link ListenableWorker} class names.
- *
- * @param workerClasses A list of {@link ListenableWorker} class names
- * @return A list of {@link OneTimeWorkRequest} constructed by using defaults in the {@link
- * Builder}
- */
- public static @NonNull List<OneTimeWorkRequest> from(
- @NonNull List<Class<? extends ListenableWorker>> workerClasses) {
- List<OneTimeWorkRequest> workList = new ArrayList<>(workerClasses.size());
- for (Class<? extends ListenableWorker> workerClass : workerClasses) {
- workList.add(new OneTimeWorkRequest.Builder(workerClass).build());
- }
- return workList;
- }
-
- OneTimeWorkRequest(Builder builder) {
- super(builder.mId, builder.mWorkSpec, builder.mTags);
- }
-
- /**
- * Builder for {@link OneTimeWorkRequest}s.
- */
- public static final class Builder extends WorkRequest.Builder<Builder, OneTimeWorkRequest> {
-
- /**
- * Creates a {@link OneTimeWorkRequest}.
- *
- * @param workerClass The {@link ListenableWorker} class to run for this work
- */
- public Builder(@NonNull Class<? extends ListenableWorker> workerClass) {
- super(workerClass);
- mWorkSpec.inputMergerClassName = OverwritingInputMerger.class.getName();
- }
-
- /**
- * Specifies the {@link InputMerger} class name for this {@link OneTimeWorkRequest}.
- * <p>
- * Before workers run, they receive input {@link Data} from their parent workers, as well as
- * anything specified directly to them via {@link WorkRequest.Builder#setInputData(Data)}.
- * An InputMerger takes all of these objects and converts them to a single merged
- * {@link Data} to be used as the worker input. The default InputMerger is
- * {@link OverwritingInputMerger}. This library also offers
- * {@link ArrayCreatingInputMerger}; you can also specify your own.
- *
- * @param inputMerger The class name of the {@link InputMerger} for this
- * {@link OneTimeWorkRequest}
- * @return The current {@link Builder}
- */
- public @NonNull Builder setInputMerger(@NonNull Class<? extends InputMerger> inputMerger) {
- mWorkSpec.inputMergerClassName = inputMerger.getName();
- return this;
- }
-
- @Override
- @NonNull OneTimeWorkRequest buildInternal() {
- if (mBackoffCriteriaSet
- && Build.VERSION.SDK_INT >= 23
- && mWorkSpec.constraints.requiresDeviceIdle()) {
- throw new IllegalArgumentException(
- "Cannot set backoff criteria on an idle mode job");
- }
- return new OneTimeWorkRequest(this);
- }
-
- @Override
- @NonNull Builder getThis() {
- return this;
- }
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt
new file mode 100644
index 0000000..f8f696a
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.work
+
+import android.os.Build
+import androidx.annotation.NonNull
+import kotlin.reflect.KClass
+
+/**
+ * A [WorkRequest] for non-repeating work.
+ *
+ * OneTimeWorkRequests can be put in simple or complex graphs of work by using methods like
+ * [WorkManager.beginWith] or [WorkManager.beginWith].
+ */
+class OneTimeWorkRequest internal constructor(builder: Builder) :
+ WorkRequest(builder.id, builder.workSpec, builder.tags) {
+ /**
+ * Builder for [OneTimeWorkRequest]s.
+ *
+ * @param workerClass The [ListenableWorker] class to run for this work
+ */
+ class Builder(workerClass: Class<out ListenableWorker>) :
+ WorkRequest.Builder<Builder, OneTimeWorkRequest>(workerClass) {
+
+ init {
+ workSpec.inputMergerClassName = OverwritingInputMerger::class.java.name
+ }
+
+ /**
+ * Specifies the [InputMerger] class name for this [OneTimeWorkRequest].
+ *
+ * Before workers run, they receive input [Data] from their parent workers, as well as
+ * anything specified directly to them via [WorkRequest.Builder.setInputData].
+ * An InputMerger takes all of these objects and converts them to a single merged
+ * [Data] to be used as the worker input. The default InputMerger is
+ * [OverwritingInputMerger]. This library also offers
+ * [ArrayCreatingInputMerger]; you can also specify your own.
+ *
+ * @param inputMerger The class name of the [InputMerger] for this
+ * [OneTimeWorkRequest]
+ * @return The current [Builder]
+ */
+ fun setInputMerger(inputMerger: Class<out InputMerger>): Builder {
+ workSpec.inputMergerClassName = inputMerger.name
+ return this
+ }
+
+ override fun buildInternal(): OneTimeWorkRequest {
+ require(
+ !(backoffCriteriaSet && Build.VERSION.SDK_INT >= 23 &&
+ workSpec.constraints.requiresDeviceIdle())
+ ) { "Cannot set backoff criteria on an idle mode job" }
+ return OneTimeWorkRequest(this)
+ }
+
+ override val thisObject: Builder
+ get() = this
+ }
+
+ companion object {
+ /**
+ * Creates a [OneTimeWorkRequest] with defaults from a [ListenableWorker] class
+ * name.
+ *
+ * @param workerClass An [ListenableWorker] class name
+ * @return A [OneTimeWorkRequest] constructed by using defaults in the [Builder]
+ */
+ @JvmStatic
+ fun from(workerClass: Class<out ListenableWorker>): OneTimeWorkRequest {
+ return Builder(workerClass).build()
+ }
+
+ /**
+ * Creates a list of [OneTimeWorkRequest]s with defaults from an array of
+ * [ListenableWorker] class names.
+ *
+ * @param workerClasses A list of [ListenableWorker] class names
+ * @return A list of [OneTimeWorkRequest] constructed by using defaults in the [ ]
+ */
+ @JvmStatic
+ fun from(workerClasses: List<Class<out ListenableWorker>>): List<OneTimeWorkRequest> {
+ return workerClasses.map { Builder(it).build() }
+ }
+ }
+}
+
+/**
+ * Creates a [OneTimeWorkRequest] with the given [ListenableWorker].
+ */
+public inline fun <reified W : ListenableWorker> OneTimeWorkRequestBuilder():
+ OneTimeWorkRequest.Builder = OneTimeWorkRequest.Builder(W::class.java)
+
+/**
+ * Sets an [InputMerger] on the [OneTimeWorkRequest.Builder].
+ */
+@Suppress("NOTHING_TO_INLINE")
+public inline fun OneTimeWorkRequest.Builder.setInputMerger(
+ @NonNull inputMerger: KClass<out InputMerger>
+): OneTimeWorkRequest.Builder = setInputMerger(inputMerger.java)
diff --git a/work/work-runtime/src/main/java/androidx/work/OutOfQuotaPolicy.java b/work/work-runtime/src/main/java/androidx/work/OutOfQuotaPolicy.kt
similarity index 92%
rename from work/work-runtime/src/main/java/androidx/work/OutOfQuotaPolicy.java
rename to work/work-runtime/src/main/java/androidx/work/OutOfQuotaPolicy.kt
index aaa7b25..0d8747a 100644
--- a/work/work-runtime/src/main/java/androidx/work/OutOfQuotaPolicy.java
+++ b/work/work-runtime/src/main/java/androidx/work/OutOfQuotaPolicy.kt
@@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package androidx.work;
+package androidx.work
/**
* An enumeration of policies that help determine out of quota behavior for expedited jobs.
*/
-public enum OutOfQuotaPolicy {
-
+enum class OutOfQuotaPolicy {
/**
* When the app does not have any expedited job quota, the expedited work request will
* fallback to a regular work request.
@@ -31,5 +29,5 @@
* When the app does not have any expedited job quota, the expedited work request will
* be dropped and no work requests are enqueued.
*/
- DROP_WORK_REQUEST;
-}
+ DROP_WORK_REQUEST
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.java b/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.java
deleted file mode 100644
index 0d1a219..0000000
--- a/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.java
+++ /dev/null
@@ -1,207 +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.work;
-
-import static androidx.work.impl.utils.DurationApi26Impl.toMillisCompat;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.time.Duration;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A {@link WorkRequest} for repeating work. This work executes multiple times until it is
- * cancelled, with the first execution happening immediately or as soon as the given
- * {@link Constraints} are met. The next execution will happen during the period interval; note
- * that execution may be delayed because {@link WorkManager} is subject to OS battery optimizations,
- * such as doze mode.
- * <p>
- * You can control when the work executes in the period interval more exactly - see
- * {@link PeriodicWorkRequest.Builder} for documentation on {@code flexInterval}s.
- * <p>
- * Periodic work has a minimum interval of 15 minutes.
- * <p>
- * Periodic work is intended for use cases where you want a fairly consistent delay between
- * consecutive runs, and you are willing to accept inexactness due to battery optimizations and doze
- * mode. Please note that if your periodic work has constraints, it will not execute until the
- * constraints are met, even if the delay between periods has been met.
- * <p>
- * If you need to schedule work that happens exactly at a certain time or only during a certain time
- * window, you should consider using {@link OneTimeWorkRequest}s.
- * <p>
- * The normal lifecycle of a PeriodicWorkRequest is {@code ENQUEUED -> RUNNING -> ENQUEUED}. By
- * definition, periodic work cannot terminate in a succeeded or failed state, since it must recur.
- * It can only terminate if explicitly cancelled. However, in the case of retries, periodic work
- * will still back off according to
- * {@link PeriodicWorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}.
- * <p>
- * Periodic work cannot be part of a chain or graph of work.
- */
-
-public final class PeriodicWorkRequest extends WorkRequest {
-
- /**
- * The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
- */
- @SuppressLint("MinMaxConstant")
- public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
- /**
- * The minimum flex duration for {@link PeriodicWorkRequest} (in milliseconds).
- */
- @SuppressLint("MinMaxConstant")
- public static final long MIN_PERIODIC_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes.
-
- PeriodicWorkRequest(Builder builder) {
- super(builder.mId, builder.mWorkSpec, builder.mTags);
- }
-
- /**
- * Builder for {@link PeriodicWorkRequest}s.
- */
- public static final class Builder extends WorkRequest.Builder<Builder, PeriodicWorkRequest> {
-
- /**
- * Creates a {@link PeriodicWorkRequest} to run periodically once every interval period. The
- * {@link PeriodicWorkRequest} is guaranteed to run exactly one time during this interval
- * (subject to OS battery optimizations, such as doze mode). The repeat interval must
- * be greater than or equal to {@link PeriodicWorkRequest#MIN_PERIODIC_INTERVAL_MILLIS}. It
- * may run immediately, at the end of the period, or any time in between so long as the
- * other conditions are satisfied at the time. The run time of the
- * {@link PeriodicWorkRequest} can be restricted to a flex period within an interval (see
- * {@code #Builder(Class, long, TimeUnit, long, TimeUnit)}).
- *
- * @param workerClass The {@link ListenableWorker} class to run for this work
- * @param repeatInterval The repeat interval in {@code repeatIntervalTimeUnit} units
- * @param repeatIntervalTimeUnit The {@link TimeUnit} for {@code repeatInterval}
- */
- public Builder(
- @NonNull Class<? extends ListenableWorker> workerClass,
- long repeatInterval,
- @NonNull TimeUnit repeatIntervalTimeUnit) {
- super(workerClass);
- mWorkSpec.setPeriodic(repeatIntervalTimeUnit.toMillis(repeatInterval));
- }
-
- /**
- * Creates a {@link PeriodicWorkRequest} to run periodically once every interval period. The
- * {@link PeriodicWorkRequest} is guaranteed to run exactly one time during this interval
- * (subject to OS battery optimizations, such as doze mode). The repeat interval must
- * be greater than or equal to {@link PeriodicWorkRequest#MIN_PERIODIC_INTERVAL_MILLIS}. It
- * may run immediately, at the end of the period, or any time in between so long as the
- * other conditions are satisfied at the time. The run time of the
- * {@link PeriodicWorkRequest} can be restricted to a flex period within an interval (see
- * {@code #Builder(Class, Duration, Duration)}).
- *
- * @param workerClass The {@link ListenableWorker} class to run for this work
- * @param repeatInterval The repeat interval
- */
- @RequiresApi(26)
- public Builder(
- @NonNull Class<? extends ListenableWorker> workerClass,
- @NonNull Duration repeatInterval) {
- super(workerClass);
- mWorkSpec.setPeriodic(toMillisCompat(repeatInterval));
- }
-
- /**
- * Creates a {@link PeriodicWorkRequest} to run periodically once within the
- * <strong>flex period</strong> of every interval period. See diagram below. The flex
- * period begins at {@code repeatInterval - flexInterval} to the end of the interval.
- * The repeat interval must be greater than or equal to
- * {@link PeriodicWorkRequest#MIN_PERIODIC_INTERVAL_MILLIS} and the flex interval must
- * be greater than or equal to {@link PeriodicWorkRequest#MIN_PERIODIC_FLEX_MILLIS}.
- *
- * <p><pre>
- * [ before flex | flex ][ before flex | flex ]...
- * [ cannot run work | can run work ][ cannot run work | can run work ]...
- * \____________________________________/\____________________________________/...
- * interval 1 interval 2 ...(repeat)
- * </pre></p>
- *
- * @param workerClass The {@link ListenableWorker} class to run for this work
- * @param repeatInterval The repeat interval in {@code repeatIntervalTimeUnit} units
- * @param repeatIntervalTimeUnit The {@link TimeUnit} for {@code repeatInterval}
- * @param flexInterval The duration in {@code flexIntervalTimeUnit} units for which this
- * work repeats from the end of the {@code repeatInterval}
- * @param flexIntervalTimeUnit The {@link TimeUnit} for {@code flexInterval}
- */
- public Builder(
- @NonNull Class<? extends ListenableWorker> workerClass,
- long repeatInterval,
- @NonNull TimeUnit repeatIntervalTimeUnit,
- long flexInterval,
- @NonNull TimeUnit flexIntervalTimeUnit) {
- super(workerClass);
- mWorkSpec.setPeriodic(
- repeatIntervalTimeUnit.toMillis(repeatInterval),
- flexIntervalTimeUnit.toMillis(flexInterval));
- }
-
- /**
- * Creates a {@link PeriodicWorkRequest} to run periodically once within the
- * <strong>flex period</strong> of every interval period. See diagram below. The flex
- * period begins at {@code repeatInterval - flexInterval} to the end of the interval.
- * The repeat interval must be greater than or equal to
- * {@link PeriodicWorkRequest#MIN_PERIODIC_INTERVAL_MILLIS} and the flex interval must
- * be greater than or equal to {@link PeriodicWorkRequest#MIN_PERIODIC_FLEX_MILLIS}.
- *
- * <p><pre>
- * [ before flex | flex ][ before flex | flex ]...
- * [ cannot run work | can run work ][ cannot run work | can run work ]...
- * \____________________________________/\____________________________________/...
- * interval 1 interval 2 ...(repeat)
- * </pre></p>
- *
- * @param workerClass The {@link ListenableWorker} class to run for this work
- * @param repeatInterval The repeat interval
- * @param flexInterval The duration in for which this work repeats from the end of the
- * {@code repeatInterval}
- */
- @RequiresApi(26)
- public Builder(
- @NonNull Class<? extends ListenableWorker> workerClass,
- @NonNull Duration repeatInterval,
- @NonNull Duration flexInterval) {
- super(workerClass);
- mWorkSpec.setPeriodic(toMillisCompat(repeatInterval),
- toMillisCompat(flexInterval));
- }
-
- @Override
- @NonNull PeriodicWorkRequest buildInternal() {
- if (mBackoffCriteriaSet
- && Build.VERSION.SDK_INT >= 23
- && mWorkSpec.constraints.requiresDeviceIdle()) {
- throw new IllegalArgumentException(
- "Cannot set backoff criteria on an idle mode job");
- }
- if (mWorkSpec.expedited) {
- throw new IllegalArgumentException(
- "PeriodicWorkRequests cannot be expedited");
- }
- return new PeriodicWorkRequest(this);
- }
-
- @Override
- @NonNull Builder getThis() {
- return this;
- }
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt
new file mode 100644
index 0000000..f007495
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt
@@ -0,0 +1,254 @@
+/*
+ * 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.work
+
+import android.annotation.SuppressLint
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.work.impl.utils.toMillisCompat
+import java.time.Duration
+import java.util.concurrent.TimeUnit
+
+/**
+ * A [WorkRequest] for repeating work. This work executes multiple times until it is
+ * cancelled, with the first execution happening immediately or as soon as the given
+ * [Constraints] are met. The next execution will happen during the period interval; note
+ * that execution may be delayed because [WorkManager] is subject to OS battery optimizations,
+ * such as doze mode.
+ *
+ * You can control when the work executes in the period interval more exactly - see
+ * [PeriodicWorkRequest.Builder] for documentation on `flexInterval`s.
+ *
+ * Periodic work has a minimum interval of 15 minutes.
+ *
+ * Periodic work is intended for use cases where you want a fairly consistent delay between
+ * consecutive runs, and you are willing to accept inexactness due to battery optimizations and doze
+ * mode. Please note that if your periodic work has constraints, it will not execute until the
+ * constraints are met, even if the delay between periods has been met.
+ *
+ * If you need to schedule work that happens exactly at a certain time or only during a certain time
+ * window, you should consider using [OneTimeWorkRequest]s.
+ *
+ * The normal lifecycle of a PeriodicWorkRequest is `ENQUEUED -> RUNNING -> ENQUEUED`. By
+ * definition, periodic work cannot terminate in a succeeded or failed state, since it must recur.
+ * It can only terminate if explicitly cancelled. However, in the case of retries, periodic work
+ * will still back off according to [PeriodicWorkRequest.Builder.setBackoffCriteria].
+ *
+ * Periodic work cannot be part of a chain or graph of work.
+ */
+class PeriodicWorkRequest internal constructor(
+ builder: Builder
+) : WorkRequest(builder.id, builder.workSpec, builder.tags) {
+ /**
+ * Builder for [PeriodicWorkRequest]s.
+ */
+ class Builder : WorkRequest.Builder<Builder, PeriodicWorkRequest> {
+ /**
+ * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
+ * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval
+ * (subject to OS battery optimizations, such as doze mode). The repeat interval must
+ * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It
+ * may run immediately, at the end of the period, or any time in between so long as the
+ * other conditions are satisfied at the time. The run time of the
+ * [PeriodicWorkRequest] can be restricted to a flex period within an interval (see
+ * `#Builder(Class, long, TimeUnit, long, TimeUnit)`).
+ *
+ * @param workerClass The [ListenableWorker] class to run for this work
+ * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
+ * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
+ */
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit
+ ) : super(workerClass) {
+ workSpec.setPeriodic(repeatIntervalTimeUnit.toMillis(repeatInterval))
+ }
+
+ /**
+ * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
+ * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval
+ * (subject to OS battery optimizations, such as doze mode). The repeat interval must
+ * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It
+ * may run immediately, at the end of the period, or any time in between so long as the
+ * other conditions are satisfied at the time. The run time of the
+ * [PeriodicWorkRequest] can be restricted to a flex period within an interval (see
+ * `#Builder(Class, Duration, Duration)`).
+ *
+ * @param workerClass The [ListenableWorker] class to run for this work
+ * @param repeatInterval The repeat interval
+ */
+ @RequiresApi(26)
+ constructor(
+ workerClass: Class<out ListenableWorker>,
+ repeatInterval: Duration
+ ) : super(workerClass) {
+ workSpec.setPeriodic(repeatInterval.toMillisCompat())
+ }
+
+ /**
+ * Creates a [PeriodicWorkRequest] to run periodically once within the
+ * **flex period** of every interval period. See diagram below. The flex
+ * period begins at `repeatInterval - flexInterval` to the end of the interval.
+ * The repeat interval must be greater than or equal to
+ * [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must
+ * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
+ * ```
+ * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
+ * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
+ * \____________________________________/\____________________________________/...
+ * interval 1 interval 2 ...(repeat)
+ * ```
+ *
+ * @param workerClass The [ListenableWorker] class to run for this work
+ * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
+ * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
+ * @param flexInterval The duration in `flexIntervalTimeUnit` units for which this
+ * work repeats from the end of the `repeatInterval`
+ * @param flexIntervalTimeUnit The [TimeUnit] for `flexInterval`
+ */
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit,
+ flexInterval: Long,
+ flexIntervalTimeUnit: TimeUnit
+ ) : super(workerClass) {
+ workSpec.setPeriodic(
+ repeatIntervalTimeUnit.toMillis(repeatInterval),
+ flexIntervalTimeUnit.toMillis(flexInterval)
+ )
+ }
+
+ /**
+ * Creates a [PeriodicWorkRequest] to run periodically once within the
+ * **flex period** of every interval period. See diagram below. The flex
+ * period begins at `repeatInterval - flexInterval` to the end of the interval.
+ * The repeat interval must be greater than or equal to
+ * [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must
+ * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
+ *
+ * ```
+ * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
+ * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
+ * \____________________________________/\____________________________________/...
+ * interval 1 interval 2 ...(repeat)
+ * ```
+ *
+ * @param workerClass The [ListenableWorker] class to run for this work
+ * @param repeatInterval The repeat interval
+ * @param flexInterval The duration in for which this work repeats from the end of the
+ * `repeatInterval`
+ */
+ @RequiresApi(26)
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Duration,
+ flexInterval: Duration
+ ) : super(workerClass) {
+ workSpec.setPeriodic(repeatInterval.toMillisCompat(), flexInterval.toMillisCompat())
+ }
+
+ override fun buildInternal(): PeriodicWorkRequest {
+ require(
+ !(backoffCriteriaSet && Build.VERSION.SDK_INT >= 23 &&
+ workSpec.constraints.requiresDeviceIdle())
+ ) { "Cannot set backoff criteria on an idle mode job" }
+ require(!workSpec.expedited) { "PeriodicWorkRequests cannot be expedited" }
+ return PeriodicWorkRequest(this)
+ }
+
+ override val thisObject: Builder
+ get() = this
+ }
+
+ companion object {
+ /**
+ * The minimum interval duration for [PeriodicWorkRequest] (in milliseconds).
+ */
+ @SuppressLint("MinMaxConstant")
+ const val MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L // 15 minutes.
+
+ /**
+ * The minimum flex duration for [PeriodicWorkRequest] (in milliseconds).
+ */
+ @SuppressLint("MinMaxConstant")
+ const val MIN_PERIODIC_FLEX_MILLIS = 5 * 60 * 1000L // 5 minutes.
+ }
+}
+
+/**
+ * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
+ *
+ * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
+ * @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
+ */
+public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit
+): PeriodicWorkRequest.Builder {
+ return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, repeatIntervalTimeUnit)
+}
+
+/**
+ * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
+ *
+ * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
+ */
+@RequiresApi(26)
+public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
+ repeatInterval: Duration
+): PeriodicWorkRequest.Builder {
+ return PeriodicWorkRequest.Builder(W::class.java, repeatInterval)
+}
+
+/**
+ * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
+ *
+ * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
+ * @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
+ * @param flexTimeInterval @see [androidx.work.PeriodicWorkRequest.Builder]
+ * @param flexTimeIntervalUnit @see [androidx.work.PeriodicWorkRequest.Builder]
+ */
+public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit,
+ flexTimeInterval: Long,
+ flexTimeIntervalUnit: TimeUnit
+): PeriodicWorkRequest.Builder {
+
+ return PeriodicWorkRequest.Builder(
+ W::class.java,
+ repeatInterval,
+ repeatIntervalTimeUnit,
+ flexTimeInterval,
+ flexTimeIntervalUnit
+ )
+}
+
+/**
+ * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
+ *
+ * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
+ * @param flexTimeInterval @see [androidx.work.PeriodicWorkRequest.Builder]
+ */
+@RequiresApi(26)
+public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
+ repeatInterval: Duration,
+ flexTimeInterval: Duration
+): PeriodicWorkRequest.Builder {
+ return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, flexTimeInterval)
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkRequest.java b/work/work-runtime/src/main/java/androidx/work/WorkRequest.java
deleted file mode 100644
index 6e9c7f5..0000000
--- a/work/work-runtime/src/main/java/androidx/work/WorkRequest.java
+++ /dev/null
@@ -1,403 +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.work;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.work.impl.model.WorkSpec;
-import androidx.work.impl.utils.DurationApi26Impl;
-
-import java.time.Duration;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The base class for specifying parameters for work that should be enqueued in {@link WorkManager}.
- * There are two concrete implementations of this class: {@link OneTimeWorkRequest} and
- * {@link PeriodicWorkRequest}.
- */
-
-public abstract class WorkRequest {
-
- /**
- * The default initial backoff time (in milliseconds) for work that has to be retried.
- */
- public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L;
-
- /**
- * The maximum backoff time (in milliseconds) for work that has to be retried.
- */
- @SuppressLint("MinMaxConstant")
- public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.
-
- /**
- * The minimum backoff time for work (in milliseconds) that has to be retried.
- */
- @SuppressLint("MinMaxConstant")
- public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.
-
- private @NonNull UUID mId;
- private @NonNull WorkSpec mWorkSpec;
- private @NonNull Set<String> mTags;
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- protected WorkRequest(@NonNull UUID id, @NonNull WorkSpec workSpec, @NonNull Set<String> tags) {
- mId = id;
- mWorkSpec = workSpec;
- mTags = tags;
- }
-
- /**
- * Gets the unique identifier associated with this unit of work.
- *
- * @return The identifier for this unit of work
- */
- public @NonNull UUID getId() {
- return mId;
- }
-
- /**
- * Gets the string for the unique identifier associated with this unit of work.
- *
- * @return The string identifier for this unit of work
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public @NonNull String getStringId() {
- return mId.toString();
- }
-
- /**
- * Gets the {@link WorkSpec} associated with this unit of work.
- *
- * @return The {@link WorkSpec} for this unit of work
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public @NonNull WorkSpec getWorkSpec() {
- return mWorkSpec;
- }
-
- /**
- * Gets the tags associated with this unit of work.
- *
- * @return The tags for this unit of work
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public @NonNull Set<String> getTags() {
- return mTags;
- }
-
- /**
- * A builder for {@link WorkRequest}s. There are two concrete implementations of this class:
- * {@link OneTimeWorkRequest.Builder} and {@link PeriodicWorkRequest.Builder}.
- *
- * @param <B> The concrete implementation of this Builder
- * @param <W> The type of work object built by this Builder
- */
- public abstract static class Builder<B extends Builder<?, ?>, W extends WorkRequest> {
-
- boolean mBackoffCriteriaSet = false;
- UUID mId;
- WorkSpec mWorkSpec;
- Set<String> mTags = new HashSet<>();
- Class<? extends ListenableWorker> mWorkerClass;
-
- Builder(@NonNull Class<? extends ListenableWorker> workerClass) {
- mId = UUID.randomUUID();
- mWorkerClass = workerClass;
- mWorkSpec = new WorkSpec(mId.toString(), workerClass.getName());
- addTag(workerClass.getName());
- }
-
- /**
- * Sets the backoff policy and backoff delay for the work. The default values are
- * {@link BackoffPolicy#EXPONENTIAL} and
- * {@value WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS}, respectively. {@code backoffDelay}
- * will be clamped between {@link WorkRequest#MIN_BACKOFF_MILLIS} and
- * {@link WorkRequest#MAX_BACKOFF_MILLIS}.
- *
- * @param backoffPolicy The {@link BackoffPolicy} to use when increasing backoff time
- * @param backoffDelay Time to wait before retrying the work in {@code timeUnit} units
- * @param timeUnit The {@link TimeUnit} for {@code backoffDelay}
- * @return The current {@link Builder}
- */
- public final @NonNull B setBackoffCriteria(
- @NonNull BackoffPolicy backoffPolicy,
- long backoffDelay,
- @NonNull TimeUnit timeUnit) {
- mBackoffCriteriaSet = true;
- mWorkSpec.backoffPolicy = backoffPolicy;
- mWorkSpec.setBackoffDelayDuration(timeUnit.toMillis(backoffDelay));
- return getThis();
- }
-
- /**
- * Sets the backoff policy and backoff delay for the work. The default values are
- * {@link BackoffPolicy#EXPONENTIAL} and
- * {@value WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS}, respectively. {@code duration} will
- * be clamped between {@link WorkRequest#MIN_BACKOFF_MILLIS} and
- * {@link WorkRequest#MAX_BACKOFF_MILLIS}.
- *
- * @param backoffPolicy The {@link BackoffPolicy} to use when increasing backoff time
- * @param duration Time to wait before retrying the work
- * @return The current {@link Builder}
- */
- @RequiresApi(26)
- public final @NonNull B setBackoffCriteria(
- @NonNull BackoffPolicy backoffPolicy,
- @NonNull Duration duration) {
- mBackoffCriteriaSet = true;
- mWorkSpec.backoffPolicy = backoffPolicy;
- mWorkSpec.setBackoffDelayDuration(DurationApi26Impl.toMillisCompat(duration));
- return getThis();
- }
-
- /**
- * Adds constraints to the {@link WorkRequest}.
- *
- * @param constraints The constraints for the work
- * @return The current {@link Builder}
- */
- public final @NonNull B setConstraints(@NonNull Constraints constraints) {
- mWorkSpec.constraints = constraints;
- return getThis();
- }
-
- /**
- * Adds input {@link Data} to the work. If a worker has prerequisites in its chain, this
- * Data will be merged with the outputs of the prerequisites using an {@link InputMerger}.
- *
- * @param inputData key/value pairs that will be provided to the worker
- * @return The current {@link Builder}
- */
- public final @NonNull B setInputData(@NonNull Data inputData) {
- mWorkSpec.input = inputData;
- return getThis();
- }
-
- /**
- * Adds a tag for the work. You can query and cancel work by tags. Tags are particularly
- * useful for modules or libraries to find and operate on their own work.
- *
- * @param tag A tag for identifying the work in queries.
- * @return The current {@link Builder}
- */
- public final @NonNull B addTag(@NonNull String tag) {
- mTags.add(tag);
- return getThis();
- }
-
- /**
- * Specifies that the results of this work should be kept for at least the specified amount
- * of time. After this time has elapsed, the results <b>may</b> be pruned at the discretion
- * of WorkManager when there are no pending dependent jobs.
- * <p>
- * When the results of a work are pruned, it becomes impossible to query for its
- * {@link WorkInfo}.
- * <p>
- * Specifying a long duration here may adversely affect performance in terms of app storage
- * and database query time.
- *
- * @param duration The minimum duration of time (in {@code timeUnit} units) to keep the
- * results of this work
- * @param timeUnit The unit of time for {@code duration}
- * @return The current {@link Builder}
- */
- public final @NonNull B keepResultsForAtLeast(long duration, @NonNull TimeUnit timeUnit) {
- mWorkSpec.minimumRetentionDuration = timeUnit.toMillis(duration);
- return getThis();
- }
-
- /**
- * Specifies that the results of this work should be kept for at least the specified amount
- * of time. After this time has elapsed, the results <p>may</p> be pruned at the discretion
- * of WorkManager when this WorkRequest has reached a finished state (see
- * {@link WorkInfo.State#isFinished()}) and there are no pending dependent jobs.
- * <p>
- * When the results of a work are pruned, it becomes impossible to query for its
- * {@link WorkInfo}.
- * <p>
- * Specifying a long duration here may adversely affect performance in terms of app storage
- * and database query time.
- *
- * @param duration The minimum duration of time to keep the results of this work
- * @return The current {@link Builder}
- */
- @RequiresApi(26)
- public final @NonNull B keepResultsForAtLeast(@NonNull Duration duration) {
- mWorkSpec.minimumRetentionDuration = DurationApi26Impl.toMillisCompat(duration);
- return getThis();
- }
-
- /**
- * Sets an initial delay for the {@link WorkRequest}.
- *
- * @param duration The length of the delay in {@code timeUnit} units
- * @param timeUnit The units of time for {@code duration}
- * @return The current {@link Builder}
- * @throws IllegalArgumentException if the given initial delay will push the execution time
- * past {@code Long.MAX_VALUE} and cause an overflow
- */
- public @NonNull B setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
- mWorkSpec.initialDelay = timeUnit.toMillis(duration);
- if (Long.MAX_VALUE - System.currentTimeMillis() <= mWorkSpec.initialDelay) {
- throw new IllegalArgumentException("The given initial delay is too large and will"
- + " cause an overflow!");
- }
- return getThis();
- }
-
- /**
- * Sets an initial delay for the {@link WorkRequest}.
- *
- * @param duration The length of the delay
- * @return The current {@link Builder} *
- * @throws IllegalArgumentException if the given initial delay will push the execution time
- * past {@code Long.MAX_VALUE} and cause an overflow
- */
- @RequiresApi(26)
- public @NonNull B setInitialDelay(@NonNull Duration duration) {
- mWorkSpec.initialDelay = DurationApi26Impl.toMillisCompat(duration);
- if (Long.MAX_VALUE - System.currentTimeMillis() <= mWorkSpec.initialDelay) {
- throw new IllegalArgumentException("The given initial delay is too large and will"
- + " cause an overflow!");
- }
- return getThis();
- }
-
- /**
- * Marks the {@link WorkRequest} as important to the user. In this case, WorkManager
- * provides an additional signal to the OS that this work is important.
- *
- * @param policy The {@link OutOfQuotaPolicy} to be used.
- */
- @SuppressLint("MissingGetterMatchingBuilder")
- public @NonNull B setExpedited(@NonNull OutOfQuotaPolicy policy) {
- mWorkSpec.expedited = true;
- mWorkSpec.outOfQuotaPolicy = policy;
- return getThis();
- }
-
- /**
- * Builds a {@link WorkRequest} based on this {@link Builder}.
- *
- * @return A {@link WorkRequest} based on this {@link Builder}
- */
- public final @NonNull W build() {
- W returnValue = buildInternal();
- Constraints constraints = mWorkSpec.constraints;
- // Check for unsupported constraints.
- boolean hasUnsupportedConstraints =
- (Build.VERSION.SDK_INT >= 24 && constraints.hasContentUriTriggers())
- || constraints.requiresBatteryNotLow()
- || constraints.requiresCharging()
- || (Build.VERSION.SDK_INT >= 23 && constraints.requiresDeviceIdle());
-
- if (mWorkSpec.expedited) {
- if (hasUnsupportedConstraints) {
- throw new IllegalArgumentException(
- "Expedited jobs only support network and storage constraints");
- }
- if (mWorkSpec.initialDelay > 0) {
- throw new IllegalArgumentException("Expedited jobs cannot be delayed");
- }
- }
- // Create a new id and WorkSpec so this WorkRequest.Builder can be used multiple times.
- mId = UUID.randomUUID();
- mWorkSpec = new WorkSpec(mId.toString(), mWorkSpec);
- return returnValue;
- }
-
- abstract @NonNull W buildInternal();
-
- abstract @NonNull B getThis();
-
- /**
- * Sets the initial state for this work. Used in testing only.
- *
- * @param state The {@link WorkInfo.State} to set
- * @return The current {@link Builder}
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- public final @NonNull B setInitialState(@NonNull WorkInfo.State state) {
- mWorkSpec.state = state;
- return getThis();
- }
-
- /**
- * Sets the initial run attempt count for this work. Used in testing only.
- *
- * @param runAttemptCount The initial run attempt count
- * @return The current {@link Builder}
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- public final @NonNull B setInitialRunAttemptCount(int runAttemptCount) {
- mWorkSpec.runAttemptCount = runAttemptCount;
- return getThis();
- }
-
- /**
- * Sets the period start time for this work. Used in testing only.
- *
- * @param periodStartTime the period start time in {@code timeUnit} units
- * @param timeUnit The {@link TimeUnit} for {@code periodStartTime}
- * @return The current {@link Builder}
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- @NonNull
- public final B setLastEnqueueTime(
- long periodStartTime,
- @NonNull TimeUnit timeUnit) {
- mWorkSpec.lastEnqueueTime = timeUnit.toMillis(periodStartTime);
- return getThis();
- }
-
- /**
- * Sets when the scheduler actually schedules the worker.
- *
- * @param scheduleRequestedAt The time at which the scheduler scheduled a worker.
- * @param timeUnit The {@link TimeUnit} for {@code scheduleRequestedAt}
- * @return The current {@link Builder}
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- public final @NonNull B setScheduleRequestedAt(
- long scheduleRequestedAt,
- @NonNull TimeUnit timeUnit) {
- mWorkSpec.scheduleRequestedAt = timeUnit.toMillis(scheduleRequestedAt);
- return getThis();
- }
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
new file mode 100644
index 0000000..21d240f
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
@@ -0,0 +1,349 @@
+/*
+ * 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.work
+
+import android.annotation.SuppressLint
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
+import androidx.work.impl.utils.toMillisCompat
+import androidx.work.impl.model.WorkSpec
+import java.time.Duration
+import java.util.UUID
+import java.util.concurrent.TimeUnit
+
+/**
+ * The base class for specifying parameters for work that should be enqueued in [WorkManager].
+ * There are two concrete implementations of this class: [OneTimeWorkRequest] and
+ * [PeriodicWorkRequest].
+ */
+abstract class WorkRequest internal constructor(
+ /**
+ * The unique identifier associated with this unit of work.
+ */
+ open val id: UUID,
+ /**
+ * The [WorkSpec] associated with this unit of work.
+ *
+ * @hide
+ */
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ val workSpec: WorkSpec,
+ /**
+ * The tags associated with this unit of work.
+ *
+ */
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ val tags: Set<String>
+) {
+
+ /**
+ * Gets the string for the unique identifier associated with this unit of work.
+ *
+ * @return The string identifier for this unit of work
+ * @hide
+ */
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ val stringId: String
+ get() = id.toString()
+
+ /**
+ * A builder for [WorkRequest]s. There are two concrete implementations of this class:
+ * [OneTimeWorkRequest.Builder] and [PeriodicWorkRequest.Builder].
+ */
+ abstract class Builder<B : Builder<B, *>, W : WorkRequest> internal constructor(
+ internal val workerClass: Class<out ListenableWorker>
+ ) {
+ internal var backoffCriteriaSet = false
+ internal var id: UUID = UUID.randomUUID()
+ internal var workSpec: WorkSpec = WorkSpec(id.toString(), workerClass.name)
+ internal val tags: MutableSet<String> = mutableSetOf(workerClass.name)
+
+ /**
+ * Sets the backoff policy and backoff delay for the work. The default values are
+ * [BackoffPolicy.EXPONENTIAL] and
+ * {@value WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS}, respectively. `backoffDelay`
+ * will be clamped between [WorkRequest.MIN_BACKOFF_MILLIS] and
+ * [WorkRequest.MAX_BACKOFF_MILLIS].
+ *
+ * @param backoffPolicy The [BackoffPolicy] to use when increasing backoff time
+ * @param backoffDelay Time to wait before retrying the work in `timeUnit` units
+ * @param timeUnit The [TimeUnit] for `backoffDelay`
+ * @return The current [Builder]
+ */
+ fun setBackoffCriteria(
+ backoffPolicy: BackoffPolicy,
+ backoffDelay: Long,
+ timeUnit: TimeUnit
+ ): B {
+ backoffCriteriaSet = true
+ workSpec.backoffPolicy = backoffPolicy
+ workSpec.setBackoffDelayDuration(timeUnit.toMillis(backoffDelay))
+ return thisObject
+ }
+
+ /**
+ * Sets the backoff policy and backoff delay for the work. The default values are
+ * [BackoffPolicy.EXPONENTIAL] and
+ * {@value WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS}, respectively. `duration` will
+ * be clamped between [WorkRequest.MIN_BACKOFF_MILLIS] and
+ * [WorkRequest.MAX_BACKOFF_MILLIS].
+ *
+ * @param backoffPolicy The [BackoffPolicy] to use when increasing backoff time
+ * @param duration Time to wait before retrying the work
+ * @return The current [Builder]
+ */
+ @RequiresApi(26)
+ fun setBackoffCriteria(backoffPolicy: BackoffPolicy, duration: Duration): B {
+ backoffCriteriaSet = true
+ workSpec.backoffPolicy = backoffPolicy
+ workSpec.setBackoffDelayDuration(duration.toMillisCompat())
+ return thisObject
+ }
+
+ /**
+ * Adds constraints to the [WorkRequest].
+ *
+ * @param constraints The constraints for the work
+ * @return The current [Builder]
+ */
+ fun setConstraints(constraints: Constraints): B {
+ workSpec.constraints = constraints
+ return thisObject
+ }
+
+ /**
+ * Adds input [Data] to the work. If a worker has prerequisites in its chain, this
+ * Data will be merged with the outputs of the prerequisites using an [InputMerger].
+ *
+ * @param inputData key/value pairs that will be provided to the worker
+ * @return The current [Builder]
+ */
+ fun setInputData(inputData: Data): B {
+ workSpec.input = inputData
+ return thisObject
+ }
+
+ /**
+ * Adds a tag for the work. You can query and cancel work by tags. Tags are particularly
+ * useful for modules or libraries to find and operate on their own work.
+ *
+ * @param tag A tag for identifying the work in queries.
+ * @return The current [Builder]
+ */
+ fun addTag(tag: String): B {
+ tags.add(tag)
+ return thisObject
+ }
+
+ /**
+ * Specifies that the results of this work should be kept for at least the specified amount
+ * of time. After this time has elapsed, the results **may** be pruned at the discretion
+ * of WorkManager when there are no pending dependent jobs.
+ *
+ * When the results of a work are pruned, it becomes impossible to query for its
+ * [WorkInfo].
+ *
+ * Specifying a long duration here may adversely affect performance in terms of app storage
+ * and database query time.
+ *
+ * @param duration The minimum duration of time (in `timeUnit` units) to keep the
+ * results of this work
+ * @param timeUnit The unit of time for `duration`
+ * @return The current [Builder]
+ */
+ fun keepResultsForAtLeast(duration: Long, timeUnit: TimeUnit): B {
+ workSpec.minimumRetentionDuration = timeUnit.toMillis(duration)
+ return thisObject
+ }
+
+ /**
+ * Specifies that the results of this work should be kept for at least the specified amount
+ * of time. After this time has elapsed, the results may be pruned at the discretion
+ * of WorkManager when this WorkRequest has reached a finished state (see
+ * [WorkInfo.State.isFinished]) and there are no pending dependent jobs.
+ *
+ * When the results of a work are pruned, it becomes impossible to query for its
+ * [WorkInfo].
+ *
+ * Specifying a long duration here may adversely affect performance in terms of app storage
+ * and database query time.
+ *
+ * @param duration The minimum duration of time to keep the results of this work
+ * @return The current [Builder]
+ */
+ @RequiresApi(26)
+ fun keepResultsForAtLeast(duration: Duration): B {
+ workSpec.minimumRetentionDuration = duration.toMillisCompat()
+ return thisObject
+ }
+
+ /**
+ * Sets an initial delay for the [WorkRequest].
+ *
+ * @param duration The length of the delay in `timeUnit` units
+ * @param timeUnit The units of time for `duration`
+ * @return The current [Builder]
+ * @throws IllegalArgumentException if the given initial delay will push the execution time
+ * past `Long.MAX_VALUE` and cause an overflow
+ */
+ open fun setInitialDelay(duration: Long, timeUnit: TimeUnit): B {
+ workSpec.initialDelay = timeUnit.toMillis(duration)
+ require(Long.MAX_VALUE - System.currentTimeMillis() > workSpec.initialDelay) {
+ ("The given initial delay is too large and will cause an overflow!")
+ }
+ return thisObject
+ }
+
+ /**
+ * Sets an initial delay for the [WorkRequest].
+ *
+ * @param duration The length of the delay
+ * @return The current [Builder] *
+ * @throws IllegalArgumentException if the given initial delay will push the execution time
+ * past `Long.MAX_VALUE` and cause an overflow
+ */
+ @RequiresApi(26)
+ open fun setInitialDelay(duration: Duration): B {
+ workSpec.initialDelay = duration.toMillisCompat()
+ require(Long.MAX_VALUE - System.currentTimeMillis() > workSpec.initialDelay) {
+ "The given initial delay is too large and will cause an overflow!"
+ }
+ return thisObject
+ }
+
+ /**
+ * Marks the [WorkRequest] as important to the user. In this case, WorkManager
+ * provides an additional signal to the OS that this work is important.
+ *
+ * @param policy The [OutOfQuotaPolicy] to be used.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ open fun setExpedited(policy: OutOfQuotaPolicy): B {
+ workSpec.expedited = true
+ workSpec.outOfQuotaPolicy = policy
+ return thisObject
+ }
+
+ /**
+ * Builds a [WorkRequest] based on this [Builder].
+ *
+ * @return A [WorkRequest] based on this [Builder]
+ */
+ fun build(): W {
+ val returnValue = buildInternal()
+ val constraints = workSpec.constraints
+ // Check for unsupported constraints.
+ val hasUnsupportedConstraints =
+ (Build.VERSION.SDK_INT >= 24 && constraints.hasContentUriTriggers() ||
+ constraints.requiresBatteryNotLow() ||
+ constraints.requiresCharging() ||
+ Build.VERSION.SDK_INT >= 23 && constraints.requiresDeviceIdle())
+ if (workSpec.expedited) {
+ require(!hasUnsupportedConstraints) {
+ "Expedited jobs only support network and storage constraints"
+ }
+ require(workSpec.initialDelay <= 0) { "Expedited jobs cannot be delayed" }
+ }
+ // Create a new id and WorkSpec so this WorkRequest.Builder can be used multiple times.
+ id = UUID.randomUUID()
+ workSpec = WorkSpec(id.toString(), workSpec)
+ return returnValue
+ }
+
+ internal abstract fun buildInternal(): W
+
+ internal abstract val thisObject: B
+
+ /**
+ * Sets the initial state for this work. Used in testing only.
+ *
+ * @param state The [WorkInfo.State] to set
+ * @return The current [Builder]
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @VisibleForTesting
+ fun setInitialState(state: WorkInfo.State): B {
+ workSpec.state = state
+ return thisObject
+ }
+
+ /**
+ * Sets the initial run attempt count for this work. Used in testing only.
+ *
+ * @param runAttemptCount The initial run attempt count
+ * @return The current [Builder]
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @VisibleForTesting
+ fun setInitialRunAttemptCount(runAttemptCount: Int): B {
+ workSpec.runAttemptCount = runAttemptCount
+ return thisObject
+ }
+
+ /**
+ * Sets the period start time for this work. Used in testing only.
+ *
+ * @param periodStartTime the period start time in `timeUnit` units
+ * @param timeUnit The [TimeUnit] for `periodStartTime`
+ * @return The current [Builder]
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @VisibleForTesting
+ fun setLastEnqueueTime(periodStartTime: Long, timeUnit: TimeUnit): B {
+ workSpec.lastEnqueueTime = timeUnit.toMillis(periodStartTime)
+ return thisObject
+ }
+
+ /**
+ * Sets when the scheduler actually schedules the worker.
+ *
+ * @param scheduleRequestedAt The time at which the scheduler scheduled a worker.
+ * @param timeUnit The [TimeUnit] for `scheduleRequestedAt`
+ * @return The current [Builder]
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @VisibleForTesting
+ fun setScheduleRequestedAt(scheduleRequestedAt: Long, timeUnit: TimeUnit): B {
+ workSpec.scheduleRequestedAt = timeUnit.toMillis(scheduleRequestedAt)
+ return thisObject
+ }
+ }
+
+ companion object {
+ /**
+ * The default initial backoff time (in milliseconds) for work that has to be retried.
+ */
+ const val DEFAULT_BACKOFF_DELAY_MILLIS = 30000L
+
+ /**
+ * The maximum backoff time (in milliseconds) for work that has to be retried.
+ */
+ @SuppressLint("MinMaxConstant")
+ const val MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000L // 5 hours
+
+ /**
+ * The minimum backoff time for work (in milliseconds) that has to be retried.
+ */
+ @SuppressLint("MinMaxConstant")
+ const val MIN_BACKOFF_MILLIS = 10 * 1000L // 10 seconds.
+ }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
index 16b5661..cc18063 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
@@ -63,6 +63,8 @@
private WorkDatabase mWorkDatabase;
private Map<String, WorkerWrapper> mForegroundWorkMap;
private Map<String, WorkerWrapper> mEnqueuedWorkMap;
+ // workSpecId to a Set<WorkRunId>
+ private Map<String, Set<WorkRunId>> mWorkRuns;
private List<Scheduler> mSchedulers;
private Set<String> mCancelledIds;
@@ -87,6 +89,7 @@
mOuterListeners = new ArrayList<>();
mForegroundLock = null;
mLock = new Object();
+ mWorkRuns = new HashMap<>();
}
/**
@@ -95,26 +98,29 @@
* @param id The work id to execute.
* @return {@code true} if the work was successfully enqueued for processing
*/
- public boolean startWork(@NonNull String id) {
+ public boolean startWork(@NonNull WorkRunId id) {
return startWork(id, null);
}
/**
* Starts a given unit of work in the background.
*
- * @param id The work id to execute.
+ * @param workRunId The work id to execute.
* @param runtimeExtras The {@link WorkerParameters.RuntimeExtras} for this work, if any.
* @return {@code true} if the work was successfully enqueued for processing
*/
+ @SuppressWarnings("ConstantConditions")
public boolean startWork(
- @NonNull String id,
+ @NonNull WorkRunId workRunId,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
-
+ String id = workRunId.getWorkSpecId();
WorkerWrapper workWrapper;
synchronized (mLock) {
// Work may get triggered multiple times if they have passing constraints
// and new work with those constraints are added.
if (isEnqueued(id)) {
+ // there must be another run if it is enqueued.
+ mWorkRuns.get(workRunId.getWorkSpecId()).add(workRunId);
Logger.get().debug(TAG, "Work " + id + " is already enqueued for processing");
return false;
}
@@ -135,6 +141,9 @@
new FutureListener(this, id, future),
mWorkTaskExecutor.getMainThreadExecutor());
mEnqueuedWorkMap.put(id, workWrapper);
+ HashSet<WorkRunId> set = new HashSet<>();
+ set.add(workRunId);
+ mWorkRuns.put(id, set);
}
mWorkTaskExecutor.getSerialTaskExecutor().execute(workWrapper);
Logger.get().debug(TAG, getClass().getSimpleName() + ": processing " + id);
@@ -171,6 +180,7 @@
synchronized (mLock) {
Logger.get().debug(TAG, "Processor stopping foreground work " + id);
wrapper = mForegroundWorkMap.remove(id);
+ mWorkRuns.remove(id);
}
// Move interrupt() outside the critical section.
// This is because calling interrupt() eventually calls ListenableWorker.onStopped()
@@ -182,13 +192,23 @@
/**
* Stops a unit of work.
*
- * @param id The work id to stop
+ * @param runId The work id to stop
* @return {@code true} if the work was stopped successfully
*/
- public boolean stopWork(@NonNull String id) {
+ public boolean stopWork(@NonNull WorkRunId runId) {
+ String id = runId.getWorkSpecId();
WorkerWrapper wrapper = null;
synchronized (mLock) {
+ // Processor _only_ receives stopWork() requests from the schedulers that originally
+ // scheduled the work, and not others. This means others are still notified about
+ // completion, but we avoid a accidental "stops" and lot of redundant work when
+ // attempting to stop.
+ Set<WorkRunId> runs = mWorkRuns.get(runId.getWorkSpecId());
+ if (runs == null || !runs.contains(runId)) {
+ return false;
+ }
Logger.get().debug(TAG, "Processor stopping background work " + id);
+ mWorkRuns.remove(id);
wrapper = mEnqueuedWorkMap.remove(id);
}
// Move interrupt() outside the critical section.
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index e6092bd..7a61bfb 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -667,7 +667,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public void startWork(@NonNull String workSpecId) {
+ public void startWork(@NonNull WorkRunId workSpecId) {
startWork(workSpecId, null);
}
@@ -678,7 +678,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void startWork(
- @NonNull String workSpecId,
+ @NonNull WorkRunId workSpecId,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkTaskExecutor
.executeOnTaskThread(
@@ -690,7 +690,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public void stopWork(@NonNull String workSpecId) {
+ public void stopWork(@NonNull WorkRunId workSpecId) {
mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(this, workSpecId, false));
}
@@ -701,7 +701,8 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void stopForegroundWork(@NonNull String workSpecId) {
- mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(this, workSpecId, true));
+ mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(this,
+ new WorkRunId(workSpecId), true));
}
/**
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkRunId.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkRunId.kt
new file mode 100644
index 0000000..9c58687
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkRunId.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.work.impl
+
+// Impl note: it is **not** a data class on purpose.
+// Multiple schedulers can create `WorkRunId`-s for the same workSpecId, and `WorkRunId`
+// objects should be different. Processor class relies on that and stores WorkRunId with the same
+// workSpecId in the set.
+class WorkRunId(val workSpecId: String)
+
+class WorkRunIds {
+ private val lock = Any()
+ private val runs = mutableMapOf<String, WorkRunId>()
+
+ fun workRunIdFor(workSpecId: String): WorkRunId {
+ return synchronized(lock) {
+ val workRunId = WorkRunId(workSpecId)
+ runs.getOrPut(workSpecId) { workRunId }
+ }
+ }
+
+ fun remove(workSpecId: String): WorkRunId? {
+ return synchronized(lock) {
+ runs.remove(workSpecId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index 644eeec..4a20196 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -30,6 +30,8 @@
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
+import androidx.work.impl.WorkRunIds;
import androidx.work.impl.constraints.WorkConstraintsCallback;
import androidx.work.impl.constraints.WorkConstraintsTracker;
import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
@@ -60,6 +62,7 @@
private DelayedWorkTracker mDelayedWorkTracker;
private boolean mRegisteredExecutionListener;
private final Object mLock;
+ private final WorkRunIds mWorkRunIds = new WorkRunIds();
// Internal State
Boolean mInDefaultProcess;
@@ -141,7 +144,7 @@
}
} else {
Logger.get().debug(TAG, "Starting work for " + workSpec.id);
- mWorkManagerImpl.startWork(workSpec.id);
+ mWorkManagerImpl.startWork(mWorkRunIds.workRunIdFor(workSpec.id));
}
}
}
@@ -180,14 +183,17 @@
mDelayedWorkTracker.unschedule(workSpecId);
}
// onExecutionCompleted does the cleanup.
- mWorkManagerImpl.stopWork(workSpecId);
+ WorkRunId runId = mWorkRunIds.remove(workSpecId);
+ if (runId != null) {
+ mWorkManagerImpl.stopWork(runId);
+ }
}
@Override
public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
for (String workSpecId : workSpecIds) {
Logger.get().debug(TAG, "Constraints met: Scheduling work ID " + workSpecId);
- mWorkManagerImpl.startWork(workSpecId);
+ mWorkManagerImpl.startWork(mWorkRunIds.workRunIdFor(workSpecId));
}
}
@@ -195,12 +201,16 @@
public void onAllConstraintsNotMet(@NonNull List<String> workSpecIds) {
for (String workSpecId : workSpecIds) {
Logger.get().debug(TAG, "Constraints not met: Cancelling work ID " + workSpecId);
- mWorkManagerImpl.stopWork(workSpecId);
+ WorkRunId runId = mWorkRunIds.remove(workSpecId);
+ if (runId != null) {
+ mWorkManagerImpl.stopWork(runId);
+ }
}
}
@Override
public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
+ mWorkRunIds.remove(workSpecId);
removeConstraintTrackingFor(workSpecId);
// onExecuted does not need to worry about unscheduling WorkSpecs with the mDelayedTracker.
// This is because, after onExecuted(), all schedulers are asked to cancel.
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
index 954aac2..a951a4f 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
@@ -28,6 +28,8 @@
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
+import androidx.work.impl.WorkRunIds;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
@@ -109,9 +111,11 @@
private final Context mContext;
private final Map<String, ExecutionListener> mPendingDelayMet;
private final Object mLock;
+ private final WorkRunIds mWorkRunIds;
- CommandHandler(@NonNull Context context) {
+ CommandHandler(@NonNull Context context, @NonNull WorkRunIds workRunIds) {
mContext = context;
+ mWorkRunIds = workRunIds;
mPendingDelayMet = new HashMap<>();
mLock = new Object();
}
@@ -264,7 +268,8 @@
// If we are, then there is nothing for us to do.
if (!mPendingDelayMet.containsKey(workSpecId)) {
DelayMetCommandHandler delayMetCommandHandler =
- new DelayMetCommandHandler(mContext, startId, workSpecId, dispatcher);
+ new DelayMetCommandHandler(mContext, startId, workSpecId,
+ dispatcher, mWorkRunIds);
mPendingDelayMet.put(workSpecId, delayMetCommandHandler);
delayMetCommandHandler.handleProcessWork();
} else {
@@ -282,7 +287,10 @@
String workSpecId = extras.getString(KEY_WORKSPEC_ID);
Logger.get().debug(TAG, "Handing stopWork work for " + workSpecId);
- dispatcher.getWorkManager().stopWork(workSpecId);
+ WorkRunId runId = mWorkRunIds.remove(workSpecId);
+ if (runId != null) {
+ dispatcher.getWorkManager().stopWork(runId);
+ }
Alarms.cancelAlarm(mContext, dispatcher.getWorkManager(), workSpecId);
// Notify dispatcher, so it can clean up.
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
index c834ae1..d62eafe 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
@@ -28,6 +28,7 @@
import androidx.annotation.WorkerThread;
import androidx.work.Logger;
import androidx.work.impl.ExecutionListener;
+import androidx.work.impl.WorkRunIds;
import androidx.work.impl.constraints.WorkConstraintsCallback;
import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
import androidx.work.impl.constraints.trackers.Trackers;
@@ -96,17 +97,19 @@
@Nullable private PowerManager.WakeLock mWakeLock;
private boolean mHasConstraints;
+ private final WorkRunIds mWorkRunIds;
DelayMetCommandHandler(
@NonNull Context context,
int startId,
@NonNull String workSpecId,
- @NonNull SystemAlarmDispatcher dispatcher) {
-
+ @NonNull SystemAlarmDispatcher dispatcher,
+ @NonNull WorkRunIds workRunIds) {
mContext = context;
mStartId = startId;
mDispatcher = dispatcher;
mWorkSpecId = workSpecId;
+ mWorkRunIds = workRunIds;
Trackers trackers = dispatcher.getWorkManager().getTrackers();
mSerialExecutor = dispatcher.getTaskExecutor().getSerialTaskExecutor();
mMainThreadExecutor = dispatcher.getTaskExecutor().getMainThreadExecutor();
@@ -135,7 +138,9 @@
// Constraints met, schedule execution
// Not using WorkManagerImpl#startWork() here because we need to know if the
// processor actually enqueued the work here.
- boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
+ boolean isEnqueued = mDispatcher.getProcessor().startWork(
+ mWorkRunIds.workRunIdFor(mWorkSpecId)
+ );
if (isEnqueued) {
// setup timers to enforce quotas on workers that have
@@ -156,7 +161,7 @@
public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
Logger.get().debug(TAG, "onExecuted " + workSpecId + ", " + needsReschedule);
cleanUp();
-
+ mWorkRunIds.remove(workSpecId);
if (needsReschedule) {
// We need to reschedule the WorkSpec. WorkerWrapper may also call Scheduler.schedule()
// but given that we will only consider WorkSpecs that are eligible that it safe.
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
index da0a37c..dea586c 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
@@ -31,6 +31,7 @@
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Processor;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunIds;
import androidx.work.impl.utils.SerialExecutor;
import androidx.work.impl.utils.WakeLocks;
import androidx.work.impl.utils.WorkTimer;
@@ -71,6 +72,8 @@
@Nullable
private CommandsCompletedListener mCompletedListener;
+ private WorkRunIds mWorkRunIds;
+
SystemAlarmDispatcher(@NonNull Context context) {
this(context, null, null);
}
@@ -81,7 +84,8 @@
@Nullable Processor processor,
@Nullable WorkManagerImpl workManager) {
mContext = context.getApplicationContext();
- mCommandHandler = new CommandHandler(mContext);
+ mWorkRunIds = new WorkRunIds();
+ mCommandHandler = new CommandHandler(mContext, mWorkRunIds);
mWorkManager = workManager != null ? workManager : WorkManagerImpl.getInstance(context);
mWorkTimer = new WorkTimer(mWorkManager.getConfiguration().getRunnableScheduler());
mProcessor = processor != null ? processor : mWorkManager.getProcessor();
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index 935a2bd..27ab881 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -37,6 +37,8 @@
import androidx.work.WorkerParameters;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
+import androidx.work.impl.WorkRunIds;
import java.util.Arrays;
import java.util.HashMap;
@@ -53,6 +55,7 @@
private static final String TAG = Logger.tagWithPrefix("SystemJobService");
private WorkManagerImpl mWorkManagerImpl;
private final Map<String, JobParameters> mJobParameters = new HashMap<>();
+ private final WorkRunIds mWorkRunIds = new WorkRunIds();
@Override
public void onCreate() {
@@ -145,7 +148,7 @@
// In such cases, we rely on SystemJobService to ask for a reschedule by calling
// jobFinished(params, true) in onExecuted(...);
// For more information look at b/123211993
- mWorkManagerImpl.startWork(workSpecId, runtimeExtras);
+ mWorkManagerImpl.startWork(mWorkRunIds.workRunIdFor(workSpecId), runtimeExtras);
return true;
}
@@ -167,7 +170,10 @@
synchronized (mJobParameters) {
mJobParameters.remove(workSpecId);
}
- mWorkManagerImpl.stopWork(workSpecId);
+ WorkRunId runId = mWorkRunIds.remove(workSpecId);
+ if (runId != null) {
+ mWorkManagerImpl.stopWork(runId);
+ }
return !mWorkManagerImpl.getProcessor().isCancelled(workSpecId);
}
@@ -178,6 +184,7 @@
synchronized (mJobParameters) {
parameters = mJobParameters.remove(workSpecId);
}
+ mWorkRunIds.remove(workSpecId);
if (parameters != null) {
jobFinished(parameters, needsReschedule);
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt
index 25e6d2e..fc6dbf2 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt
@@ -29,8 +29,8 @@
import androidx.work.Data
import androidx.work.Logger
import androidx.work.OutOfQuotaPolicy
-import androidx.work.PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS
-import androidx.work.PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS
+import androidx.work.PeriodicWorkRequest.Companion.MIN_PERIODIC_FLEX_MILLIS
+import androidx.work.PeriodicWorkRequest.Companion.MIN_PERIODIC_INTERVAL_MILLIS
import androidx.work.WorkInfo
import androidx.work.WorkRequest
import java.util.UUID
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index 5831aed..977793e 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -25,7 +25,7 @@
import static androidx.work.WorkInfo.State.FAILED;
import static androidx.work.WorkInfo.State.RUNNING;
import static androidx.work.WorkInfo.State.SUCCEEDED;
-import static androidx.work.impl.workers.ConstraintTrackingWorker.ARGUMENT_CLASS_NAME;
+import static androidx.work.impl.workers.ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME;
import android.content.Context;
import android.os.Build;
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.java
deleted file mode 100644
index e8256dc..0000000
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.work.impl.utils;
-
-
-import static android.content.Context.ACTIVITY_SERVICE;
-import static android.os.Build.VERSION.SDK_INT;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.Application;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Process;
-import android.text.TextUtils;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.work.Configuration;
-import androidx.work.Logger;
-import androidx.work.WorkManager;
-
-import java.lang.reflect.Method;
-import java.util.List;
-
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ProcessUtils {
- private static final String TAG = Logger.tagWithPrefix("ProcessUtils");
-
- private ProcessUtils() {
- // Does nothing
- }
-
- /**
- * @return {@code true} when {@link WorkManager} is running in the configured app process.
- */
- public static boolean isDefaultProcess(
- @NonNull Context context,
- @NonNull Configuration configuration) {
-
- String processName = getProcessName(context);
-
- if (!TextUtils.isEmpty(configuration.getDefaultProcessName())) {
- return TextUtils.equals(processName, configuration.getDefaultProcessName());
- } else {
- ApplicationInfo info = context.getApplicationInfo();
- return TextUtils.equals(processName, info.processName);
- }
- }
-
- /**
- * @return The name of the active process.
- */
- @Nullable
- @SuppressLint({"PrivateApi", "DiscouragedPrivateApi"})
- public static String getProcessName(@NonNull Context context) {
- if (SDK_INT >= 28) {
- return Api28Impl.getProcessName();
- }
-
- // Try using ActivityThread to determine the current process name.
- try {
- Class<?> activityThread = Class.forName(
- "android.app.ActivityThread",
- false,
- ProcessUtils.class.getClassLoader());
- final Object packageName;
- if (SDK_INT >= 18) {
- Method currentProcessName = activityThread.getDeclaredMethod("currentProcessName");
- currentProcessName.setAccessible(true);
- packageName = currentProcessName.invoke(null);
- } else {
- Method getActivityThread = activityThread.getDeclaredMethod(
- "currentActivityThread");
- getActivityThread.setAccessible(true);
- Method getProcessName = activityThread.getDeclaredMethod("getProcessName");
- getProcessName.setAccessible(true);
- packageName = getProcessName.invoke(getActivityThread.invoke(null));
- }
- if (packageName instanceof String) {
- return (String) packageName;
- }
- } catch (Throwable exception) {
- Logger.get().debug(TAG, "Unable to check ActivityThread for processName", exception);
- }
-
- // Fallback to the most expensive way
- int pid = Process.myPid();
- ActivityManager am =
- (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
-
- if (am != null) {
- List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
- if (processes != null && !processes.isEmpty()) {
- for (ActivityManager.RunningAppProcessInfo process : processes) {
- if (process.pid == pid) {
- return process.processName;
- }
- }
- }
- }
-
- return null;
- }
-
- @RequiresApi(28)
- static class Api28Impl {
- private Api28Impl() {
- // This class is not instantiable.
- }
-
- @DoNotInline
- static String getProcessName() {
- return Application.getProcessName();
- }
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
new file mode 100644
index 0000000..135686d
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+@file:JvmName("ProcessUtils")
+
+package androidx.work.impl.utils
+
+import android.annotation.SuppressLint
+import android.app.ActivityManager
+import android.app.Application
+import android.content.Context
+import android.os.Build
+import android.os.Process
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.work.Configuration
+import androidx.work.Logger
+import androidx.work.WorkManager
+
+private val TAG = Logger.tagWithPrefix("ProcessUtils")
+
+/**
+ * @return `true` when `WorkManager` is running in the configured app process.
+ */
+fun isDefaultProcess(context: Context, configuration: Configuration): Boolean {
+ val processName = getProcessName(context)
+ return if (!configuration.defaultProcessName.isNullOrEmpty()) {
+ processName == configuration.defaultProcessName
+ } else {
+ processName == context.applicationInfo.processName
+ }
+}
+
+/**
+ * @return The name of the active process.
+ */
+@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
+private fun getProcessName(context: Context): String? {
+ if (Build.VERSION.SDK_INT >= 28) return Api28Impl.processName
+
+ // Try using ActivityThread to determine the current process name.
+ try {
+ val activityThread = Class.forName(
+ "android.app.ActivityThread",
+ false,
+ WorkManager::class.java.classLoader
+ )
+ val packageName = if (Build.VERSION.SDK_INT >= 18) {
+ val currentProcessName = activityThread.getDeclaredMethod("currentProcessName")
+ currentProcessName.isAccessible = true
+ currentProcessName.invoke(null)!!
+ } else {
+ val getActivityThread = activityThread.getDeclaredMethod("currentActivityThread")
+ getActivityThread.isAccessible = true
+ val getProcessName = activityThread.getDeclaredMethod("getProcessName")
+ getProcessName.isAccessible = true
+ getProcessName.invoke(getActivityThread.invoke(null))!!
+ }
+ if (packageName is String) return packageName
+ } catch (exception: Throwable) {
+ Logger.get().debug(TAG, "Unable to check ActivityThread for processName", exception)
+ }
+
+ // Fallback to the most expensive way
+ val pid = Process.myPid()
+ val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ return am.runningAppProcesses?.find { process -> process.pid == pid }?.processName
+}
+
+@RequiresApi(28)
+private object Api28Impl {
+ @get:DoNotInline
+ val processName: String
+ get() = Application.getProcessName()
+}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java
index 6e245b8..0043f17 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java
@@ -19,6 +19,7 @@
import androidx.annotation.RestrictTo;
import androidx.work.WorkerParameters;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
/**
* A {@link Runnable} that can start work on the
@@ -29,12 +30,12 @@
public class StartWorkRunnable implements Runnable {
private WorkManagerImpl mWorkManagerImpl;
- private String mWorkSpecId;
+ private WorkRunId mWorkSpecId;
private WorkerParameters.RuntimeExtras mRuntimeExtras;
public StartWorkRunnable(
WorkManagerImpl workManagerImpl,
- String workSpecId,
+ WorkRunId workSpecId,
WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkManagerImpl = workManagerImpl;
mWorkSpecId = workSpecId;
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
index 680949a..34929af 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
@@ -20,6 +20,7 @@
import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRunId;
/**
* A {@link Runnable} that requests {@link androidx.work.impl.Processor} to stop the work
@@ -31,12 +32,12 @@
private static final String TAG = Logger.tagWithPrefix("StopWorkRunnable");
private final WorkManagerImpl mWorkManagerImpl;
- private final String mWorkSpecId;
+ private final WorkRunId mWorkSpecId;
private final boolean mStopInForeground;
public StopWorkRunnable(
@NonNull WorkManagerImpl workManagerImpl,
- @NonNull String workSpecId,
+ @NonNull WorkRunId workSpecId,
boolean stopInForeground) {
mWorkManagerImpl = workManagerImpl;
mWorkSpecId = workSpecId;
@@ -48,7 +49,7 @@
if (mStopInForeground) {
isStopped = mWorkManagerImpl
.getProcessor()
- .stopForegroundWork(mWorkSpecId);
+ .stopForegroundWork(mWorkSpecId.getWorkSpecId());
} else {
// This call is safe to make for foreground work because Processor ignores requests
// to stop for foreground work.
@@ -59,7 +60,8 @@
Logger.get().debug(
TAG,
- "StopWorkRunnable for " + mWorkSpecId + "; Processor.stopWork = " + isStopped);
+ "StopWorkRunnable for " + mWorkSpecId.getWorkSpecId() + "; Processor.stopWork = "
+ + isStopped);
}
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/WakeLocks.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/WakeLocks.java
deleted file mode 100644
index 652cef9..0000000
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/WakeLocks.java
+++ /dev/null
@@ -1,98 +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.work.impl.utils;
-
-import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
-
-import android.content.Context;
-import android.os.PowerManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.work.Logger;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * A common class for creating WakeLocks.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class WakeLocks {
-
- private static final String TAG = Logger.tagWithPrefix("WakeLocks");
-
- private static final WeakHashMap<PowerManager.WakeLock, String> sWakeLocks =
- new WeakHashMap<>();
-
- /**
- * Creates and returns a new WakeLock.
- *
- * @param context The context from which to get the PowerManager
- * @param tag A descriptive tag for the WakeLock; this method will prefix "WorkManager: "
- * to it
- * @return A new {@link android.os.PowerManager.WakeLock}
- */
- public static PowerManager.WakeLock newWakeLock(
- @NonNull Context context,
- @NonNull String tag) {
- PowerManager powerManager = (PowerManager) context.getApplicationContext()
- .getSystemService(Context.POWER_SERVICE);
-
- String tagWithPrefix = "WorkManager: " + tag;
- PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK, tagWithPrefix);
- // Wakelocks are created on the command processor thread, but we check if they are still
- // being held on the main thread.
- synchronized (sWakeLocks) {
- sWakeLocks.put(wakeLock, tagWithPrefix);
- }
- return wakeLock;
- }
-
- /**
- * Checks to see if there are any {@link PowerManager.WakeLock}s that
- * {@link androidx.work.impl.background.systemalarm.SystemAlarmService} holds when all the
- * pending commands have been drained in the command queue.
- */
- public static void checkWakeLocks() {
- // There is a small chance that while we are checking if all the commands in the queue are
- // drained and wake locks are no longer being held, a new command comes along and we end up
- // with a ConcurrentModificationException. The addition of commands happens on the command
- // processor thread and this check is done on the main thread.
-
- Map<PowerManager.WakeLock, String> wakeLocksCopy = new HashMap<>();
- synchronized (sWakeLocks) {
- // Copy the WakeLocks - otherwise we can get a ConcurrentModificationException if the
- // garbage collector kicks in and ends up removing something from the master copy while
- // we are iterating over it.
- wakeLocksCopy.putAll(sWakeLocks);
- }
-
- for (PowerManager.WakeLock wakeLock : wakeLocksCopy.keySet()) {
- if (wakeLock != null && wakeLock.isHeld()) {
- String message = "WakeLock held for " + wakeLocksCopy.get(wakeLock);
- Logger.get().warning(TAG, message);
- }
- }
- }
-
- private WakeLocks() {
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/WakeLocks.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/WakeLocks.kt
new file mode 100644
index 0000000..169ccf1
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/WakeLocks.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+@file:JvmName("WakeLocks")
+
+package androidx.work.impl.utils
+
+import android.content.Context
+import android.os.PowerManager
+import androidx.work.Logger
+import java.util.WeakHashMap
+
+private val TAG = Logger.tagWithPrefix("WakeLocks")
+
+/**
+ * Creates and returns a new WakeLock.
+ *
+ * @param context The context from which to get the PowerManager
+ * @param tag A descriptive tag for the WakeLock; this method will prefix "WorkManager: " to it
+ * @return A new [android.os.PowerManager.WakeLock]
+ */
+internal fun newWakeLock(context: Context, tag: String): PowerManager.WakeLock {
+ val powerManager = context.applicationContext
+ .getSystemService(Context.POWER_SERVICE) as PowerManager
+ val tagWithPrefix = "WorkManager: $tag"
+ val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tagWithPrefix)
+ // Wakelocks are created on the command processor thread, but we check if they are still
+ // being held on the main thread.
+ synchronized(WakeLocksHolder) {
+ WakeLocksHolder.wakeLocks.put(wakeLock, tagWithPrefix)
+ }
+ return wakeLock
+}
+
+/**
+ * Checks to see if there are any [PowerManager.WakeLock]s that
+ * [androidx.work.impl.background.systemalarm.SystemAlarmService] holds when all the
+ * pending commands have been drained in the command queue.
+ */
+fun checkWakeLocks() {
+ // There is a small chance that while we are checking if all the commands in the queue are
+ // drained and wake locks are no longer being held, a new command comes along and we end up
+ // with a ConcurrentModificationException. The addition of commands happens on the command
+ // processor thread and this check is done on the main thread.
+ val wakeLocksCopy = mutableMapOf<PowerManager.WakeLock?, String>()
+ synchronized(WakeLocksHolder) {
+ // Copy the WakeLocks - otherwise we can get a ConcurrentModificationException if the
+ // garbage collector kicks in and ends up removing something from the master copy while
+ // we are iterating over it.
+ wakeLocksCopy.putAll(WakeLocksHolder.wakeLocks)
+ }
+
+ wakeLocksCopy.forEach { (wakeLock, tag) ->
+ if (wakeLock?.isHeld == true) Logger.get().warning(TAG, "WakeLock held for $tag")
+ }
+}
+
+private object WakeLocksHolder {
+ val wakeLocks = WeakHashMap<PowerManager.WakeLock, String>()
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java b/work/work-runtime/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
deleted file mode 100644
index ef29c98..0000000
--- a/work/work-runtime/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
+++ /dev/null
@@ -1,43 +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.work.impl.workers;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.work.Worker;
-import androidx.work.WorkerParameters;
-
-/**
- * A {@link Worker} that helps combine work continuations.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class CombineContinuationsWorker extends Worker {
-
- public CombineContinuationsWorker(@NonNull Context context,
- @NonNull WorkerParameters workerParams) {
- super(context, workerParams);
- }
-
- @Override
- public @NonNull Result doWork() {
- return Result.success(getInputData());
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.kt b/work/work-runtime/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.kt
new file mode 100644
index 0000000..484da27
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.work.impl.workers
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+
+/**
+ * A [Worker] that helps combine work continuations.
+ */
+internal class CombineContinuationsWorker(
+ context: Context,
+ workerParams: WorkerParameters
+) : Worker(context, workerParams) {
+ override fun doWork() = Result.success(inputData)
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java b/work/work-runtime/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
deleted file mode 100644
index 10c3d99..0000000
--- a/work/work-runtime/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
+++ /dev/null
@@ -1,245 +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.work.impl.workers;
-
-import android.content.Context;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.work.ListenableWorker;
-import androidx.work.Logger;
-import androidx.work.Worker;
-import androidx.work.WorkerParameters;
-import androidx.work.impl.WorkDatabase;
-import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.constraints.WorkConstraintsCallback;
-import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
-import androidx.work.impl.constraints.trackers.Trackers;
-import androidx.work.impl.model.WorkSpec;
-import androidx.work.impl.utils.futures.SettableFuture;
-import androidx.work.impl.utils.taskexecutor.TaskExecutor;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Is an implementation of a {@link Worker} that can delegate to a different {@link Worker}
- * when the constraints are met.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ConstraintTrackingWorker extends ListenableWorker implements WorkConstraintsCallback {
-
- private static final String TAG = Logger.tagWithPrefix("ConstraintTrkngWrkr");
-
- /**
- * The {@code className} of the {@link Worker} to delegate to.
- */
- public static final String ARGUMENT_CLASS_NAME =
- "androidx.work.impl.workers.ConstraintTrackingWorker.ARGUMENT_CLASS_NAME";
-
- private WorkerParameters mWorkerParameters;
-
- // These are package-private to avoid synthetic accessor.
- final Object mLock;
- // Marking this volatile as the delegated workers could switch threads.
- volatile boolean mAreConstraintsUnmet;
- SettableFuture<Result> mFuture;
-
- @Nullable private ListenableWorker mDelegate;
-
- public ConstraintTrackingWorker(@NonNull Context appContext,
- @NonNull WorkerParameters workerParams) {
- super(appContext, workerParams);
- mWorkerParameters = workerParams;
- mLock = new Object();
- mAreConstraintsUnmet = false;
- mFuture = SettableFuture.create();
- }
-
- @NonNull
- @Override
- public ListenableFuture<Result> startWork() {
- getBackgroundExecutor().execute(new Runnable() {
- @Override
- public void run() {
- setupAndRunConstraintTrackingWork();
- }
- });
- return mFuture;
- }
-
- // Package-private to avoid synthetic accessor.
- void setupAndRunConstraintTrackingWork() {
- String className = getInputData().getString(ARGUMENT_CLASS_NAME);
- if (TextUtils.isEmpty(className)) {
- Logger.get().error(TAG, "No worker to delegate to.");
- setFutureFailed();
- return;
- }
-
- mDelegate = getWorkerFactory().createWorkerWithDefaultFallback(
- getApplicationContext(),
- className,
- mWorkerParameters);
-
- if (mDelegate == null) {
- Logger.get().debug(TAG, "No worker to delegate to.");
- setFutureFailed();
- return;
- }
-
- WorkDatabase workDatabase = getWorkDatabase();
-
- // We need to know what the real constraints are for the delegate.
- WorkSpec workSpec = workDatabase.workSpecDao().getWorkSpec(getId().toString());
- if (workSpec == null) {
- setFutureFailed();
- return;
- }
- WorkConstraintsTrackerImpl workConstraintsTracker =
- new WorkConstraintsTrackerImpl(getTrackers(), this);
-
- // Start tracking
- workConstraintsTracker.replace(Collections.singletonList(workSpec));
-
- if (workConstraintsTracker.areAllConstraintsMet(getId().toString())) {
- Logger.get().debug(TAG, "Constraints met for delegate " + className);
-
- // Wrapping the call to mDelegate#doWork() in a try catch, because
- // changes in constraints can cause the worker to throw RuntimeExceptions, and
- // that should cause a retry.
- try {
- final ListenableFuture<Result> innerFuture = mDelegate.startWork();
- innerFuture.addListener(new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (mAreConstraintsUnmet) {
- setFutureRetry();
- } else {
- mFuture.setFuture(innerFuture);
- }
- }
- }
- }, getBackgroundExecutor());
- } catch (Throwable exception) {
- Logger.get().debug(TAG, String.format(
- "Delegated worker %s threw exception in startWork.", className),
- exception);
- synchronized (mLock) {
- if (mAreConstraintsUnmet) {
- Logger.get().debug(TAG, "Constraints were unmet, Retrying.");
- setFutureRetry();
- } else {
- setFutureFailed();
- }
- }
- }
- } else {
- Logger.get().debug(TAG, String.format(
- "Constraints not met for delegate %s. Requesting retry.", className));
- setFutureRetry();
- }
-
- }
-
- // Package-private to avoid synthetic accessor.
- void setFutureFailed() {
- mFuture.set(Result.failure());
- }
-
- // Package-private to avoid synthetic accessor.
- void setFutureRetry() {
- mFuture.set(Result.retry());
- }
-
- @Override
- public void onStopped() {
- super.onStopped();
- if (mDelegate != null && !mDelegate.isStopped()) {
- // Stop is the method that sets the stopped and cancelled bits and invokes onStopped.
- mDelegate.stop();
- }
- }
-
- /**
- * @return The instance of {@link WorkDatabase}
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- @NonNull
- public WorkDatabase getWorkDatabase() {
- return WorkManagerImpl.getInstance(getApplicationContext()).getWorkDatabase();
- }
-
- /**
- * @return The instance of {@link TaskExecutor}.
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- @NonNull
- @Override
- public TaskExecutor getTaskExecutor() {
- return WorkManagerImpl.getInstance(getApplicationContext()).getWorkTaskExecutor();
- }
-
- /**
- * @return The instance of {@link Trackers}.
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- @NonNull
- public Trackers getTrackers() {
- return WorkManagerImpl.getInstance(getApplicationContext()).getTrackers();
- }
-
- /**
- * @return The {@link Worker} used for delegated work
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @VisibleForTesting
- @Nullable
- public ListenableWorker getDelegate() {
- return mDelegate;
- }
-
- @Override
- public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
- // WorkConstraintTracker notifies on the main thread. So we don't want to trampoline
- // between the background thread and the main thread in this case.
- }
-
- @Override
- public void onAllConstraintsNotMet(@NonNull List<String> workSpecIds) {
- // If at any point, constraints are not met mark it so we can retry the work.
- Logger.get().debug(TAG, "Constraints changed for " + workSpecIds);
- synchronized (mLock) {
- mAreConstraintsUnmet = true;
- }
- }
-}
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.kt b/work/work-runtime/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.kt
new file mode 100644
index 0000000..788346c
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.work.impl.workers
+
+import android.content.Context
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
+import androidx.work.ListenableWorker
+import androidx.work.ListenableWorker.Result
+import androidx.work.Logger
+import androidx.work.WorkerParameters
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.constraints.WorkConstraintsCallback
+import androidx.work.impl.constraints.WorkConstraintsTrackerImpl
+import androidx.work.impl.utils.futures.SettableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Is an implementation of a [androidx.work.Worker] that can delegate to a different
+ * [androidx.work.Worker] when the constraints are met.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ConstraintTrackingWorker(
+ appContext: Context,
+ private val workerParameters: WorkerParameters
+) : ListenableWorker(appContext, workerParameters), WorkConstraintsCallback {
+
+ private val lock = Any()
+
+ // Marking this volatile as the delegated workers could switch threads.
+ @Volatile
+ private var areConstraintsUnmet: Boolean = false
+ private val future = SettableFuture.create<Result>()
+
+ /**
+ * @return The [androidx.work.Worker] used for delegated work
+ * @hide
+ */
+ @get:VisibleForTesting
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ var delegate: ListenableWorker? = null
+ private set
+
+ override fun startWork(): ListenableFuture<Result> {
+ backgroundExecutor.execute { setupAndRunConstraintTrackingWork() }
+ return future
+ }
+
+ private fun setupAndRunConstraintTrackingWork() {
+ if (future.isCancelled) return
+
+ val className = inputData.getString(ARGUMENT_CLASS_NAME)
+ val logger = Logger.get()
+ if (className.isNullOrEmpty()) {
+ logger.error(TAG, "No worker to delegate to.")
+ future.setFailed()
+ return
+ }
+ delegate = workerFactory.createWorkerWithDefaultFallback(
+ applicationContext, className, workerParameters
+ )
+ if (delegate == null) {
+ logger.debug(TAG, "No worker to delegate to.")
+ future.setFailed()
+ return
+ }
+
+ val workManagerImpl = WorkManagerImpl.getInstance(applicationContext)
+ // We need to know what the real constraints are for the delegate.
+ val workSpec = workManagerImpl.workDatabase.workSpecDao().getWorkSpec(id.toString())
+ if (workSpec == null) {
+ future.setFailed()
+ return
+ }
+ val workConstraintsTracker = WorkConstraintsTrackerImpl(workManagerImpl.trackers, this)
+
+ // Start tracking
+ workConstraintsTracker.replace(listOf(workSpec))
+ if (workConstraintsTracker.areAllConstraintsMet(id.toString())) {
+ logger.debug(TAG, "Constraints met for delegate $className")
+
+ // Wrapping the call to mDelegate#doWork() in a try catch, because
+ // changes in constraints can cause the worker to throw RuntimeExceptions, and
+ // that should cause a retry.
+ try {
+ val innerFuture = delegate!!.startWork()
+ innerFuture.addListener({
+ synchronized(lock) {
+ if (areConstraintsUnmet) {
+ future.setRetry()
+ } else {
+ future.setFuture(innerFuture)
+ }
+ }
+ }, backgroundExecutor)
+ } catch (exception: Throwable) {
+ logger.debug(
+ TAG, "Delegated worker $className threw exception in startWork.", exception
+ )
+ synchronized(lock) {
+ if (areConstraintsUnmet) {
+ logger.debug(TAG, "Constraints were unmet, Retrying.")
+ future.setRetry()
+ } else {
+ future.setFailed()
+ }
+ }
+ }
+ } else {
+ logger.debug(
+ TAG, "Constraints not met for delegate $className. Requesting retry."
+ )
+ future.setRetry()
+ }
+ }
+
+ override fun onStopped() {
+ super.onStopped()
+ val delegateInner = delegate
+ if (delegateInner != null && !delegateInner.isStopped) {
+ // Stop is the method that sets the stopped and cancelled bits and invokes onStopped.
+ delegateInner.stop()
+ }
+ }
+
+ override fun onAllConstraintsMet(workSpecIds: List<String>) {
+ // WorkConstraintTracker notifies on the main thread. So we don't want to trampoline
+ // between the background thread and the main thread in this case.
+ }
+
+ override fun onAllConstraintsNotMet(workSpecIds: List<String>) {
+ // If at any point, constraints are not met mark it so we can retry the work.
+ Logger.get().debug(TAG, "Constraints changed for $workSpecIds")
+ synchronized(lock) { areConstraintsUnmet = true }
+ }
+}
+
+private fun SettableFuture<Result>.setFailed() = set(Result.failure())
+private fun SettableFuture<Result>.setRetry() = set(Result.retry())
+
+private val TAG = Logger.tagWithPrefix("ConstraintTrkngWrkr")
+
+/**
+ * The `className` of the [androidx.work.Worker] to delegate to.
+ */
+internal const val ARGUMENT_CLASS_NAME =
+ "androidx.work.impl.workers.ConstraintTrackingWorker.ARGUMENT_CLASS_NAME"
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.java b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.java
deleted file mode 100644
index 3f20289..0000000
--- a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.work.impl.workers;
-
-import static androidx.work.impl.Scheduler.MAX_GREEDY_SCHEDULER_LIMIT;
-
-import android.content.Context;
-import android.os.Build;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.work.Logger;
-import androidx.work.Worker;
-import androidx.work.WorkerParameters;
-import androidx.work.impl.WorkDatabase;
-import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.model.SystemIdInfo;
-import androidx.work.impl.model.SystemIdInfoDao;
-import androidx.work.impl.model.WorkNameDao;
-import androidx.work.impl.model.WorkSpec;
-import androidx.work.impl.model.WorkSpecDao;
-import androidx.work.impl.model.WorkTagDao;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The {@link androidx.work.Worker} which dumps diagnostic information.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DiagnosticsWorker extends Worker {
-
- private static final String TAG = Logger.tagWithPrefix("DiagnosticsWrkr");
-
- public DiagnosticsWorker(@NonNull Context context, @NonNull WorkerParameters parameters) {
- super(context, parameters);
- }
-
- @NonNull
- @Override
- public Result doWork() {
- WorkManagerImpl workManager = WorkManagerImpl.getInstance(getApplicationContext());
- WorkDatabase database = workManager.getWorkDatabase();
- WorkSpecDao workSpecDao = database.workSpecDao();
- WorkNameDao workNameDao = database.workNameDao();
- WorkTagDao workTagDao = database.workTagDao();
- SystemIdInfoDao systemIdInfoDao = database.systemIdInfoDao();
- long startAt = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
- List<WorkSpec> completed = workSpecDao.getRecentlyCompletedWork(startAt);
- List<WorkSpec> running = workSpecDao.getRunningWork();
- List<WorkSpec> enqueued = workSpecDao.getAllEligibleWorkSpecsForScheduling(
- MAX_GREEDY_SCHEDULER_LIMIT);
-
- if (completed != null && !completed.isEmpty()) {
- Logger.get().info(TAG, "Recently completed work:\n\n");
- Logger.get().info(TAG,
- workSpecRows(workNameDao, workTagDao, systemIdInfoDao, completed));
- }
- if (running != null && !running.isEmpty()) {
- Logger.get().info(TAG, "Running work:\n\n");
- Logger.get().info(TAG, workSpecRows(workNameDao, workTagDao, systemIdInfoDao, running));
- }
- if (enqueued != null && !enqueued.isEmpty()) {
- Logger.get().info(TAG, "Enqueued work:\n\n");
- Logger.get().info(TAG,
- workSpecRows(workNameDao, workTagDao, systemIdInfoDao, enqueued));
- }
- return Result.success();
- }
-
- @NonNull
- private static String workSpecRows(
- @NonNull WorkNameDao workNameDao,
- @NonNull WorkTagDao workTagDao,
- @NonNull SystemIdInfoDao systemIdInfoDao,
- @NonNull List<WorkSpec> workSpecs) {
-
- StringBuilder sb = new StringBuilder();
- String systemIdHeader = Build.VERSION.SDK_INT >= 23 ? "Job Id" : "Alarm Id";
- String header = String.format("\n Id \t Class Name\t %s\t State\t Unique Name\t Tags\t",
- systemIdHeader);
- sb.append(header);
- for (WorkSpec workSpec : workSpecs) {
- Integer systemId = null;
- SystemIdInfo info = systemIdInfoDao.getSystemIdInfo(workSpec.id);
- if (info != null) {
- systemId = info.systemId;
- }
- List<String> names = workNameDao.getNamesForWorkSpecId(workSpec.id);
- List<String> tags = workTagDao.getTagsForWorkSpecId(workSpec.id);
- sb.append(workSpecRow(
- workSpec,
- TextUtils.join(",", names),
- systemId,
- TextUtils.join(",", tags)
- ));
- }
- return sb.toString();
- }
-
- @NonNull
- private static String workSpecRow(
- @NonNull WorkSpec workSpec,
- @Nullable String name,
- @Nullable Integer systemId,
- @NonNull String tags) {
- return String.format(
- "\n%s\t %s\t %s\t %s\t %s\t %s\t",
- workSpec.id,
- workSpec.workerClassName,
- systemId,
- workSpec.state.name(),
- name,
- tags);
- }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
new file mode 100644
index 0000000..7755b70
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.work.impl.workers
+
+import android.content.Context
+import android.os.Build
+import androidx.work.Logger
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.impl.Scheduler
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.model.SystemIdInfoDao
+import androidx.work.impl.model.WorkNameDao
+import androidx.work.impl.model.WorkSpec
+import androidx.work.impl.model.WorkTagDao
+import java.util.concurrent.TimeUnit
+
+internal class DiagnosticsWorker(context: Context, parameters: WorkerParameters) :
+ Worker(context, parameters) {
+ override fun doWork(): Result {
+ val workManager = WorkManagerImpl.getInstance(applicationContext)
+ val database = workManager.workDatabase
+ val workSpecDao = database.workSpecDao()
+ val workNameDao = database.workNameDao()
+ val workTagDao = database.workTagDao()
+ val systemIdInfoDao = database.systemIdInfoDao()
+ val startAt = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)
+ val completed = workSpecDao.getRecentlyCompletedWork(startAt)
+ val running = workSpecDao.getRunningWork()
+ val enqueued = workSpecDao.getAllEligibleWorkSpecsForScheduling(
+ Scheduler.MAX_GREEDY_SCHEDULER_LIMIT
+ )
+ if (completed.isNotEmpty()) {
+ Logger.get().info(TAG, "Recently completed work:\n\n")
+ Logger.get().info(
+ TAG, workSpecRows(workNameDao, workTagDao, systemIdInfoDao, completed)
+ )
+ }
+ if (running.isNotEmpty()) {
+ Logger.get().info(TAG, "Running work:\n\n")
+ Logger.get().info(TAG, workSpecRows(workNameDao, workTagDao, systemIdInfoDao, running))
+ }
+ if (enqueued.isNotEmpty()) {
+ Logger.get().info(TAG, "Enqueued work:\n\n")
+ Logger.get().info(TAG, workSpecRows(workNameDao, workTagDao, systemIdInfoDao, enqueued))
+ }
+ return Result.success()
+ }
+}
+
+private val TAG = Logger.tagWithPrefix("DiagnosticsWrkr")
+
+private fun workSpecRows(
+ workNameDao: WorkNameDao,
+ workTagDao: WorkTagDao,
+ systemIdInfoDao: SystemIdInfoDao,
+ workSpecs: List<WorkSpec>
+) = buildString {
+ val systemIdHeader = if (Build.VERSION.SDK_INT >= 23) "Job Id" else "Alarm Id"
+ val header = "\n Id \t Class Name\t ${systemIdHeader}\t State\t Unique Name\t Tags\t"
+ append(header)
+ workSpecs.forEach { workSpec ->
+ val systemId = systemIdInfoDao.getSystemIdInfo(workSpec.id)?.systemId
+ val names = workNameDao.getNamesForWorkSpecId(workSpec.id).joinToString(",")
+ val tags = workTagDao.getTagsForWorkSpecId(workSpec.id).joinToString(",")
+ append(workSpecRow(workSpec, names, systemId, tags))
+ }
+}
+
+private fun workSpecRow(workSpec: WorkSpec, name: String, systemId: Int?, tags: String) =
+ "\n${workSpec.id}\t ${workSpec.workerClassName}\t $systemId\t " +
+ "${workSpec.state.name}\t $name\t $tags\t"
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
index a9fee2b..b8d6ba2 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
@@ -24,6 +24,7 @@
import androidx.work.impl.Scheduler
import androidx.work.impl.WorkDatabase
import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.WorkRunIds
import androidx.work.impl.model.WorkSpec
import androidx.work.impl.model.WorkSpecDao
import java.util.UUID
@@ -42,6 +43,7 @@
@GuardedBy("lock")
private val terminatedWorkIds = mutableSetOf<String>()
private val lock = Any()
+ private val workRunIds = WorkRunIds()
override fun hasLimitedSchedulingSlots() = true
@@ -64,7 +66,8 @@
// to enqueue() will no-op because insertWorkSpec in WorkDatabase has a conflict
// policy of @Ignore. So TestScheduler will _never_ be asked to schedule those
// WorkSpecs.
- WorkManagerImpl.getInstance(context).stopWork(workSpecId)
+ val workRunId = workRunIds.remove(workSpecId)
+ if (workRunId != null) WorkManagerImpl.getInstance(context).stopWork(workRunId)
synchronized(lock) {
val internalWorkState = pendingWorkStates[workSpecId]
if (internalWorkState != null && !internalWorkState.isPeriodic) {
@@ -154,11 +157,13 @@
pendingWorkStates.remove(workSpecId)
terminatedWorkIds.add(workSpecId)
}
+ workRunIds.remove(workSpecId)
}
}
private fun scheduleInternal(workId: String, state: InternalWorkState) {
- if (state.isRunnable) WorkManagerImpl.getInstance(context).startWork(workId)
+ if (state.isRunnable)
+ WorkManagerImpl.getInstance(context).startWork(workRunIds.workRunIdFor(workId))
}
private fun rewindLastEnqueueTime(id: String) {