Merge changes I1366e7fe,I0902eb16,I77fe618a,I6dcac248 into androidx-main
* changes:
Rename rememberSavedInstanceState() to rememberSaveable()
Rename RestorableStateHolder to SaveableStateHolder
Move Saver/listSaver/mapSaver/autoSaver() into a new package
Rename compose:runtime-saved-instance-state to compose:runtime-saveable
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index c558ee6..930cf26 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -138,7 +138,7 @@
val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha06")
val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha06")
val WEBKIT = Version("1.5.0-alpha01")
- val WINDOW = Version("1.0.0-alpha02")
+ val WINDOW = Version("1.0.0-alpha03")
val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
val WINDOW_SIDECAR = Version("0.1.0-alpha01")
val WORK = Version("2.5.0-rc01")
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index acd2ab7..2dccb46 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -81,7 +81,9 @@
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
method public default boolean isFinished(long playTime);
+ method public boolean isInfinite();
property public abstract long durationMillis;
+ property public abstract boolean isInfinite;
property public abstract T! targetValue;
property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -294,9 +296,11 @@
method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
+ method public boolean isInfinite();
property public long durationMillis;
property public final T! initialValue;
property public final V initialVelocityVector;
+ property public boolean isInfinite;
property public T! targetValue;
property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -446,7 +450,7 @@
property public final boolean hasObservers;
}
- public final class ManualFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
+ public final class ManualFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
ctor @Deprecated public ManualFrameClock(long initialTime, boolean dispatchOnSubscribe);
ctor public ManualFrameClock(long initialTime);
method public void advanceClock(long nanos);
@@ -560,8 +564,10 @@
method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
+ method public boolean isInfinite();
property public long durationMillis;
property public final T! initialValue;
+ property public boolean isInfinite;
property public T! targetValue;
property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -650,6 +656,8 @@
method public default V getEndVelocity(V start, V end, V startVelocity);
method public V getValue(long playTime, V start, V end, V startVelocity);
method public V getVelocity(long playTime, V start, V end, V startVelocity);
+ method public boolean isInfinite();
+ property public abstract boolean isInfinite;
}
public final class VectorizedAnimationSpecKt {
@@ -673,6 +681,8 @@
}
public interface VectorizedFiniteAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+ method public default boolean isInfinite();
+ property public default boolean isInfinite;
}
public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
@@ -684,6 +694,8 @@
public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+ method public boolean isInfinite();
+ property public boolean isInfinite;
}
public final class VectorizedKeyframesSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> {
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index acd2ab7..2dccb46 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -81,7 +81,9 @@
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
method public default boolean isFinished(long playTime);
+ method public boolean isInfinite();
property public abstract long durationMillis;
+ property public abstract boolean isInfinite;
property public abstract T! targetValue;
property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -294,9 +296,11 @@
method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
+ method public boolean isInfinite();
property public long durationMillis;
property public final T! initialValue;
property public final V initialVelocityVector;
+ property public boolean isInfinite;
property public T! targetValue;
property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -446,7 +450,7 @@
property public final boolean hasObservers;
}
- public final class ManualFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
+ public final class ManualFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
ctor @Deprecated public ManualFrameClock(long initialTime, boolean dispatchOnSubscribe);
ctor public ManualFrameClock(long initialTime);
method public void advanceClock(long nanos);
@@ -560,8 +564,10 @@
method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
+ method public boolean isInfinite();
property public long durationMillis;
property public final T! initialValue;
+ property public boolean isInfinite;
property public T! targetValue;
property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -650,6 +656,8 @@
method public default V getEndVelocity(V start, V end, V startVelocity);
method public V getValue(long playTime, V start, V end, V startVelocity);
method public V getVelocity(long playTime, V start, V end, V startVelocity);
+ method public boolean isInfinite();
+ property public abstract boolean isInfinite;
}
public final class VectorizedAnimationSpecKt {
@@ -673,6 +681,8 @@
}
public interface VectorizedFiniteAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+ method public default boolean isInfinite();
+ property public default boolean isInfinite;
}
public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
@@ -684,6 +694,8 @@
public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+ method public boolean isInfinite();
+ property public boolean isInfinite;
}
public final class VectorizedKeyframesSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> {
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 47a95c9..9cf625e 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -81,7 +81,9 @@
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
method public default boolean isFinished(long playTime);
+ method public boolean isInfinite();
property public abstract long durationMillis;
+ property public abstract boolean isInfinite;
property public abstract T! targetValue;
property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -294,9 +296,11 @@
method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
+ method public boolean isInfinite();
property public long durationMillis;
property public final T! initialValue;
property public final V initialVelocityVector;
+ property public boolean isInfinite;
property public T! targetValue;
property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -446,7 +450,7 @@
property public final boolean hasObservers;
}
- public final class ManualFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
+ public final class ManualFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
ctor @Deprecated public ManualFrameClock(long initialTime, boolean dispatchOnSubscribe);
ctor public ManualFrameClock(long initialTime);
method public void advanceClock(long nanos);
@@ -560,8 +564,10 @@
method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
method public T! getValue(long playTime);
method public V getVelocityVector(long playTime);
+ method public boolean isInfinite();
property public long durationMillis;
property public final T! initialValue;
+ property public boolean isInfinite;
property public T! targetValue;
property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
}
@@ -658,6 +664,8 @@
method public default V getEndVelocity(V start, V end, V startVelocity);
method public V getValue(long playTime, V start, V end, V startVelocity);
method public V getVelocity(long playTime, V start, V end, V startVelocity);
+ method public boolean isInfinite();
+ property public abstract boolean isInfinite;
}
public final class VectorizedAnimationSpecKt {
@@ -681,6 +689,8 @@
}
public interface VectorizedFiniteAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+ method public default boolean isInfinite();
+ property public default boolean isInfinite;
}
public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
@@ -692,6 +702,8 @@
public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+ method public boolean isInfinite();
+ property public boolean isInfinite;
}
public final class VectorizedKeyframesSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> {
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index 7e1f35a..c55e82a 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -42,10 +42,10 @@
api "androidx.annotation:annotation:1.1.0"
implementation project(":compose:runtime:runtime")
- implementation project(":compose:runtime:runtime-dispatch")
implementation project(":compose:ui:ui-unit")
implementation project(":compose:ui:ui-util")
implementation (KOTLIN_STDLIB)
+ api(KOTLIN_COROUTINES_CORE)
testImplementation(ANDROIDX_TEST_RULES)
testImplementation(ANDROIDX_TEST_RUNNER)
@@ -74,10 +74,10 @@
sourceSets {
commonMain.dependencies {
implementation project(":compose:runtime:runtime")
- implementation project(":compose:runtime:runtime-dispatch")
implementation project(":compose:ui:ui-unit")
implementation project(":compose:ui:ui-util")
implementation(KOTLIN_STDLIB_COMMON)
+ api(KOTLIN_COROUTINES_CORE)
}
androidMain.dependencies {
api "androidx.annotation:annotation:1.1.0"
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/InfiniteTransitionTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/InfiniteTransitionTest.kt
index e6f7b5a..0ce2950 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/InfiniteTransitionTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/InfiniteTransitionTest.kt
@@ -19,7 +19,7 @@
import androidx.compose.animation.VectorConverter
import androidx.compose.animation.animateColor
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createComposeRule
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
index 31b6963..fe66c47 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
@@ -19,7 +19,7 @@
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
index e286010..47bccf1 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
@@ -20,7 +20,7 @@
import androidx.compose.animation.animateColor
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt
index 5eef343..94ff29b 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt
@@ -53,6 +53,13 @@
val targetValue: T
/**
+ * Whether or not the [Animation] represents an infinite animation. That is, one that will
+ * not finish by itself, one that needs an external action to stop. For examples, an
+ * indeterminate progress bar, which will only stop when it is removed from the composition.
+ */
+ val isInfinite: Boolean
+
+ /**
* Returns the value of the animation at the given play time.
*
* @param playTime the play time that is used to determine the value of the animation.
@@ -216,6 +223,8 @@
initialVelocityVector?.copy() ?: typeConverter.convertToVector(initialValue)
.newInstance()
+ override val isInfinite: Boolean get() = animationSpec.isInfinite
+
override fun getValue(playTime: Long): T {
return if (playTime < durationMillis) {
typeConverter.convertFromVector(
@@ -284,6 +293,9 @@
)
override val durationMillis: Long
+ // DecayAnimation finishes by design
+ override val isInfinite: Boolean = false
+
/**
* [DecayAnimation] is an animation that slows down from [initialVelocityVector] as time goes
* on. [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/InfiniteTransition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/InfiniteTransition.kt
index e52d481..dd37aaa 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/InfiniteTransition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/InfiniteTransition.kt
@@ -22,7 +22,7 @@
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ManualFrameClock.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ManualFrameClock.kt
index 73b584f..8d7204e 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ManualFrameClock.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ManualFrameClock.kt
@@ -16,8 +16,8 @@
package androidx.compose.animation.core
-import androidx.compose.runtime.dispatch.BroadcastFrameClock
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.BroadcastFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
/**
* A [MonotonicFrameClock] built on a [BroadcastFrameClock] that keeps track of the current time.
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonotonicFrameAnimationClock.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonotonicFrameAnimationClock.kt
index ffe6bb5..d866d77 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonotonicFrameAnimationClock.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonotonicFrameAnimationClock.kt
@@ -16,9 +16,9 @@
package androidx.compose.animation.core
-import androidx.compose.runtime.dispatch.DefaultMonotonicFrameClock
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
-import androidx.compose.runtime.dispatch.withFrameMillis
+import androidx.compose.runtime.DefaultMonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.runtime.withFrameMillis
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
index d6a0072..5594bfd 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
@@ -16,7 +16,7 @@
package androidx.compose.animation.core
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import kotlinx.coroutines.CancellationException
/**
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index d8f3999..fb21aa8 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -25,7 +25,7 @@
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
index 39e4e01..5b5677a 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
@@ -41,6 +41,13 @@
*/
interface VectorizedAnimationSpec<V : AnimationVector> {
/**
+ * Whether or not the [VectorizedAnimationSpec] specifies an infinite animation. That is, one
+ * that will not finish by itself, one that needs an external action to stop. For examples, an
+ * indeterminate progress bar, which will only stop when it is removed from the composition.
+ */
+ val isInfinite: Boolean
+
+ /**
* Calculates the value of the animation at given the playtime, with the provided start/end
* values, and start velocity.
*
@@ -111,7 +118,9 @@
* [VectorizedSnapSpec], [VectorizedSpringSpec], etc. The [VectorizedAnimationSpec] that does
* __not__ implement this is: [InfiniteRepeatableSpec].
*/
-interface VectorizedFiniteAnimationSpec<V : AnimationVector> : VectorizedAnimationSpec<V>
+interface VectorizedFiniteAnimationSpec<V : AnimationVector> : VectorizedAnimationSpec<V> {
+ override val isInfinite: Boolean get() = false
+}
/**
* Base class for [VectorizedAnimationSpec]s that are based on a fixed [durationMillis].
@@ -290,7 +299,9 @@
private val animation: VectorizedDurationBasedAnimationSpec<V>,
private val repeatMode: RepeatMode = RepeatMode.Restart
) : VectorizedAnimationSpec<V> by
- VectorizedRepeatableSpec<V>(InfiniteIterations, animation, repeatMode)
+ VectorizedRepeatableSpec<V>(InfiniteIterations, animation, repeatMode) {
+ override val isInfinite: Boolean get() = true
+}
/**
* This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/IsInfiniteTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/IsInfiniteTest.kt
new file mode 100644
index 0000000..f4a65b4
--- /dev/null
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/IsInfiniteTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.animation.core
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class IsInfiniteTest {
+ @Test
+ fun testTweenIsFinite() {
+ val tweenSpec = tween<Float>()
+ assertThat(tweenSpec.vectorize().isInfinite).isFalse()
+ assertThat(tweenSpec.asAnimation().isInfinite).isFalse()
+ }
+
+ @Test
+ fun testSnapIsFinite() {
+ val snapSpec = snap<Float>()
+ assertThat(snapSpec.vectorize().isInfinite).isFalse()
+ assertThat(snapSpec.asAnimation().isInfinite).isFalse()
+ }
+
+ @Test
+ fun testKeyFramesIsFinite() {
+ val keyFramesSpec = keyframes<Float> { durationMillis = 100 }
+ assertThat(keyFramesSpec.vectorize().isInfinite).isFalse()
+ assertThat(keyFramesSpec.asAnimation().isInfinite).isFalse()
+ }
+
+ @Test
+ fun testSpringIsFinite() {
+ val springSpec = spring<Float>()
+ val animation = springSpec.asAnimation()
+ assertThat(springSpec.vectorize().isInfinite).isFalse()
+ assertThat(animation.isInfinite).isFalse()
+ }
+
+ @Test
+ fun testFiniteRepeatableIsFinite() {
+ val spring = repeatable(10, tween<Float>())
+ assertThat(spring.vectorize().isInfinite).isFalse()
+ assertThat(spring.asAnimation().isInfinite).isFalse()
+ }
+
+ @Test
+ fun testInfiniteRepeatableIsInfinite() {
+ val spring = infiniteRepeatable(tween<Float>())
+ assertThat(spring.vectorize().isInfinite).isTrue()
+ assertThat(spring.asAnimation().isInfinite).isTrue()
+ }
+
+ @Test
+ fun testExponentialDecayAnimationIsFinite() {
+ val decaySpec = exponentialDecay<Float>()
+ assertThat(decaySpec.asAnimation().isInfinite).isFalse()
+ }
+
+ @Test
+ fun testDecayAnimationIsFinite() {
+ val decaySpec = FloatExponentialDecaySpec()
+ assertThat(decaySpec.asAnimation().isInfinite).isFalse()
+ }
+
+ private fun AnimationSpec<Float>.vectorize(): VectorizedAnimationSpec<AnimationVector1D> {
+ return vectorize(Float.VectorConverter)
+ }
+
+ private fun AnimationSpec<Float>.asAnimation(): Animation<Float, AnimationVector1D> {
+ return TargetBasedAnimation(vectorize(), Float.VectorConverter, 0f, 0f)
+ }
+
+ private fun DecayAnimationSpec<Float>.asAnimation(): Animation<Float, AnimationVector1D> {
+ return DecayAnimation(this, Float.VectorConverter, 0f, 0f)
+ }
+
+ private fun FloatDecayAnimationSpec.asAnimation(): Animation<Float, AnimationVector1D> {
+ return DecayAnimation(this, 0f)
+ }
+}
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt
index 9d09692..a734bd1 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt
@@ -16,7 +16,7 @@
package androidx.compose.animation.core
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.ui.geometry.Offset
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt
index 48e8991..9fe9099 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt
@@ -24,13 +24,13 @@
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composer
import androidx.compose.runtime.Composition
-import androidx.compose.runtime.EmbeddingContext
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.Recomposer
import androidx.compose.ui.node.UiApplier
import androidx.compose.ui.platform.AmbientContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
@@ -122,9 +122,8 @@
companion object {
@OptIn(ExperimentalCoroutinesApi::class)
private val recomposer = run {
- val embeddingContext = EmbeddingContext()
val mainScope = CoroutineScope(
- NonCancellable + embeddingContext.mainThreadCompositionContext()
+ NonCancellable + Dispatchers.Main
)
Recomposer(mainScope.coroutineContext).also {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 99f155a..6032f46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -16,13 +16,14 @@
package androidx.compose.foundation
-import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
-import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.gesture.doubleTapGestureFilter
+import androidx.compose.ui.gesture.longPressGestureFilter
+import androidx.compose.ui.gesture.pressIndicatorGestureFilter
+import androidx.compose.ui.gesture.tapGestureFilter
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.disabled
@@ -106,6 +107,7 @@
* @param onDoubleClick will be called when user double clicks on the element
* @param onClick will be called when user clicks on the element
*/
+@Suppress("DEPRECATION")
fun Modifier.clickable(
enabled: Boolean = true,
interactionState: InteractionState,
@@ -131,32 +133,25 @@
disabled()
}
}
- val onClickState = rememberUpdatedState(onClick)
- val interactionStateState = rememberUpdatedState(interactionState)
- val gesture =
+ val interactionUpdate =
if (enabled) {
- remember(onDoubleClick, onLongClick) {
- Modifier.pointerInput {
- detectTapGestures(
- onDoubleTap = if (onDoubleClick != null) {
- { onDoubleClick() }
- } else {
- null
- },
- onLongPress = if (onLongClick != null) {
- { onLongClick() }
- } else {
- null
- },
- onPress = {
- interactionStateState.value.addInteraction(Interaction.Pressed, it)
- tryAwaitRelease()
- interactionStateState.value.removeInteraction(Interaction.Pressed)
- },
- onTap = { onClickState.value.invoke() }
- )
- }
- }
+ Modifier.pressIndicatorGestureFilter(
+ onStart = { interactionState.addInteraction(Interaction.Pressed, it) },
+ onStop = { interactionState.removeInteraction(Interaction.Pressed) },
+ onCancel = { interactionState.removeInteraction(Interaction.Pressed) }
+ )
+ } else {
+ Modifier
+ }
+ val tap = if (enabled) tapGestureFilter(onTap = { onClick() }) else Modifier
+ val longTap = if (enabled && onLongClick != null) {
+ longPressGestureFilter(onLongPress = { onLongClick() })
+ } else {
+ Modifier
+ }
+ val doubleTap =
+ if (enabled && onDoubleClick != null) {
+ doubleTapGestureFilter(onDoubleTap = { onDoubleClick() })
} else {
Modifier
}
@@ -166,8 +161,11 @@
}
}
semanticModifier
- .then(gesture)
+ .then(interactionUpdate)
.indication(interactionState, indication)
+ .then(tap)
+ .then(longTap)
+ .then(doubleTap)
},
inspectorInfo = debugInspectorInfo {
name = "clickable"
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/Scrolling.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/Scrolling.kt
index 5ea0c9c..5eb7e0d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/Scrolling.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/Scrolling.kt
@@ -20,7 +20,7 @@
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Scrollable
-import androidx.compose.runtime.dispatch.withFrameMillis
+import androidx.compose.runtime.withFrameMillis
/**
* Smooth scroll by [value] pixels.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index ed255f1..504f56c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -21,14 +21,13 @@
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.Strings
-import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.indication
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
-import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.gesture.pressIndicatorGestureFilter
+import androidx.compose.ui.gesture.tapGestureFilter
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.disabled
@@ -231,7 +230,7 @@
}
)
-@Suppress("ModifierInspectorInfo")
+@Suppress("ModifierInspectorInfo", "DEPRECATION")
private fun Modifier.toggleableImpl(
state: ToggleableState,
enabled: Boolean,
@@ -257,22 +256,17 @@
disabled()
}
}
- val clickState = rememberUpdatedState(onClick)
- val interactionStateState = rememberUpdatedState(interactionState)
- val gestures = if (enabled) {
- Modifier.pointerInput {
- detectTapGestures(
- onTap = { clickState.value.invoke() },
- onPress = {
- interactionStateState.value.addInteraction(Interaction.Pressed, it)
- tryAwaitRelease()
- interactionStateState.value.removeInteraction(Interaction.Pressed)
- }
+ val interactionUpdate =
+ if (enabled) {
+ Modifier.pressIndicatorGestureFilter(
+ onStart = { interactionState.addInteraction(Interaction.Pressed, it) },
+ onStop = { interactionState.removeInteraction(Interaction.Pressed) },
+ onCancel = { interactionState.removeInteraction(Interaction.Pressed) }
)
+ } else {
+ Modifier
}
- } else {
- Modifier
- }
+ val click = if (enabled) Modifier.tapGestureFilter { onClick() } else Modifier
DisposableEffect(interactionState) {
onDispose {
@@ -282,5 +276,6 @@
this
.then(semantics)
.indication(interactionState, indication)
- .then(gestures)
+ .then(interactionUpdate)
+ .then(click)
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
index 0726cf4..3d4fe80 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
@@ -25,7 +25,7 @@
import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.ControlledComposition
import androidx.compose.runtime.currentComposer
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.withRunningRecomposer
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
index c08712c..badd5b5 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
@@ -18,7 +18,7 @@
// Ignore lint warnings in documentation snippets
@file:Suppress(
"unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
- "RedundantSuspendModifier", "CascadeIf", "ClassName"
+ "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments"
)
package androidx.compose.integration.docs.interoperability
@@ -48,6 +48,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.integration.docs.databinding.ExampleLayoutBinding
import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
@@ -71,6 +72,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.AmbientConfiguration
import androidx.compose.ui.platform.AmbientContext
import androidx.compose.ui.platform.ComposeView
@@ -630,6 +632,40 @@
private object InteropSnippet10 {
@Composable
+ fun LoginButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = MaterialTheme.colors.secondary
+ ),
+ onClick = onClick,
+ modifier = modifier,
+ ) {
+ Text(stringResource(R.string.login))
+ }
+ }
+
+ class LoginViewButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+ ) : AbstractComposeView(context, attrs, defStyle) {
+
+ var onClick by mutableStateOf<() -> Unit>({})
+
+ @Composable
+ override fun Content() {
+ YourAppTheme {
+ LoginButton(onClick)
+ }
+ }
+ }
+ }
+
+ private object InteropSnippet11 {
+ @Composable
fun SystemBroadcastReceiver(
systemAction: String,
onSystemEvent: (intent: Intent?) -> Unit
@@ -686,15 +722,16 @@
object string {
const val ok = 4
const val plane_description = 5
+ const val login = 6
}
object dimen {
- const val padding_small = 6
+ const val padding_small = 7
}
object drawable {
- const val ic_plane = 7
+ const val ic_plane = 8
}
object color {
- const val Blue700 = 8
+ const val Blue700 = 9
}
}
@@ -730,6 +767,7 @@
@Composable private fun AppCompatTheme(content: @Composable () -> Unit) { }
@Composable private fun BlueTheme(content: @Composable () -> Unit) { }
@Composable private fun PinkTheme(content: @Composable () -> Unit) { }
+@Composable private fun YourAppTheme(content: @Composable () -> Unit) { }
@Composable private fun ProvideWindowInsets(content: @Composable () -> Unit) { }
@Composable private fun Icon() { }
diff --git a/compose/runtime/runtime-dispatch/api/current.txt b/compose/runtime/runtime-dispatch/api/current.txt
index f8f939f..059af93 100644
--- a/compose/runtime/runtime-dispatch/api/current.txt
+++ b/compose/runtime/runtime-dispatch/api/current.txt
@@ -1,60 +1,13 @@
// Signature format: 4.0
package androidx.compose.runtime.dispatch {
- public final class ActualAndroidKt {
- method public static androidx.compose.runtime.dispatch.MonotonicFrameClock getDefaultMonotonicFrameClock();
- }
-
- public final class AndroidUiDispatcher extends kotlinx.coroutines.CoroutineDispatcher {
- method public void dispatch(kotlin.coroutines.CoroutineContext context, Runnable block);
- method public android.view.Choreographer getChoreographer();
- method public androidx.compose.runtime.dispatch.MonotonicFrameClock getFrameClock();
- property public final android.view.Choreographer choreographer;
- property public final androidx.compose.runtime.dispatch.MonotonicFrameClock frameClock;
- field public static final androidx.compose.runtime.dispatch.AndroidUiDispatcher.Companion Companion;
- }
-
- public static final class AndroidUiDispatcher.Companion {
- method public kotlin.coroutines.CoroutineContext getCurrentThread();
- method public kotlin.coroutines.CoroutineContext getMain();
- property public final kotlin.coroutines.CoroutineContext CurrentThread;
- property public final kotlin.coroutines.CoroutineContext Main;
- }
-
- public final class AndroidUiDispatcherKt {
- }
-
- public final class AndroidUiFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public AndroidUiFrameClock(android.view.Choreographer choreographer);
- method public android.view.Choreographer getChoreographer();
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public final android.view.Choreographer choreographer;
- }
-
- public final class BroadcastFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public BroadcastFrameClock(kotlin.jvm.functions.Function0<kotlin.Unit>? onNewAwaiters);
- ctor public BroadcastFrameClock();
- method public void cancel(optional java.util.concurrent.CancellationException cancellationException);
- method public boolean getHasAwaiters();
- method public void sendFrame(long timeNanos);
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public final boolean hasAwaiters;
- }
-
- public interface MonotonicFrameClock extends kotlin.coroutines.CoroutineContext.Element {
- method public default kotlin.coroutines.CoroutineContext.Key<?> getKey();
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public default kotlin.coroutines.CoroutineContext.Key<?> key;
- field public static final androidx.compose.runtime.dispatch.MonotonicFrameClock.Key Key;
- }
-
- public static final class MonotonicFrameClock.Key implements kotlin.coroutines.CoroutineContext.Key<androidx.compose.runtime.dispatch.MonotonicFrameClock> {
+ public final class ExpectKt {
+ method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
}
public final class MonotonicFrameClockKt {
- method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.dispatch.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method @Deprecated public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method @Deprecated public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
}
}
diff --git a/compose/runtime/runtime-dispatch/api/public_plus_experimental_current.txt b/compose/runtime/runtime-dispatch/api/public_plus_experimental_current.txt
index f8f939f..059af93 100644
--- a/compose/runtime/runtime-dispatch/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime-dispatch/api/public_plus_experimental_current.txt
@@ -1,60 +1,13 @@
// Signature format: 4.0
package androidx.compose.runtime.dispatch {
- public final class ActualAndroidKt {
- method public static androidx.compose.runtime.dispatch.MonotonicFrameClock getDefaultMonotonicFrameClock();
- }
-
- public final class AndroidUiDispatcher extends kotlinx.coroutines.CoroutineDispatcher {
- method public void dispatch(kotlin.coroutines.CoroutineContext context, Runnable block);
- method public android.view.Choreographer getChoreographer();
- method public androidx.compose.runtime.dispatch.MonotonicFrameClock getFrameClock();
- property public final android.view.Choreographer choreographer;
- property public final androidx.compose.runtime.dispatch.MonotonicFrameClock frameClock;
- field public static final androidx.compose.runtime.dispatch.AndroidUiDispatcher.Companion Companion;
- }
-
- public static final class AndroidUiDispatcher.Companion {
- method public kotlin.coroutines.CoroutineContext getCurrentThread();
- method public kotlin.coroutines.CoroutineContext getMain();
- property public final kotlin.coroutines.CoroutineContext CurrentThread;
- property public final kotlin.coroutines.CoroutineContext Main;
- }
-
- public final class AndroidUiDispatcherKt {
- }
-
- public final class AndroidUiFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public AndroidUiFrameClock(android.view.Choreographer choreographer);
- method public android.view.Choreographer getChoreographer();
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public final android.view.Choreographer choreographer;
- }
-
- public final class BroadcastFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public BroadcastFrameClock(kotlin.jvm.functions.Function0<kotlin.Unit>? onNewAwaiters);
- ctor public BroadcastFrameClock();
- method public void cancel(optional java.util.concurrent.CancellationException cancellationException);
- method public boolean getHasAwaiters();
- method public void sendFrame(long timeNanos);
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public final boolean hasAwaiters;
- }
-
- public interface MonotonicFrameClock extends kotlin.coroutines.CoroutineContext.Element {
- method public default kotlin.coroutines.CoroutineContext.Key<?> getKey();
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public default kotlin.coroutines.CoroutineContext.Key<?> key;
- field public static final androidx.compose.runtime.dispatch.MonotonicFrameClock.Key Key;
- }
-
- public static final class MonotonicFrameClock.Key implements kotlin.coroutines.CoroutineContext.Key<androidx.compose.runtime.dispatch.MonotonicFrameClock> {
+ public final class ExpectKt {
+ method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
}
public final class MonotonicFrameClockKt {
- method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.dispatch.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method @Deprecated public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method @Deprecated public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
}
}
diff --git a/compose/runtime/runtime-dispatch/api/restricted_current.txt b/compose/runtime/runtime-dispatch/api/restricted_current.txt
index f8f939f..059af93 100644
--- a/compose/runtime/runtime-dispatch/api/restricted_current.txt
+++ b/compose/runtime/runtime-dispatch/api/restricted_current.txt
@@ -1,60 +1,13 @@
// Signature format: 4.0
package androidx.compose.runtime.dispatch {
- public final class ActualAndroidKt {
- method public static androidx.compose.runtime.dispatch.MonotonicFrameClock getDefaultMonotonicFrameClock();
- }
-
- public final class AndroidUiDispatcher extends kotlinx.coroutines.CoroutineDispatcher {
- method public void dispatch(kotlin.coroutines.CoroutineContext context, Runnable block);
- method public android.view.Choreographer getChoreographer();
- method public androidx.compose.runtime.dispatch.MonotonicFrameClock getFrameClock();
- property public final android.view.Choreographer choreographer;
- property public final androidx.compose.runtime.dispatch.MonotonicFrameClock frameClock;
- field public static final androidx.compose.runtime.dispatch.AndroidUiDispatcher.Companion Companion;
- }
-
- public static final class AndroidUiDispatcher.Companion {
- method public kotlin.coroutines.CoroutineContext getCurrentThread();
- method public kotlin.coroutines.CoroutineContext getMain();
- property public final kotlin.coroutines.CoroutineContext CurrentThread;
- property public final kotlin.coroutines.CoroutineContext Main;
- }
-
- public final class AndroidUiDispatcherKt {
- }
-
- public final class AndroidUiFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public AndroidUiFrameClock(android.view.Choreographer choreographer);
- method public android.view.Choreographer getChoreographer();
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public final android.view.Choreographer choreographer;
- }
-
- public final class BroadcastFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public BroadcastFrameClock(kotlin.jvm.functions.Function0<kotlin.Unit>? onNewAwaiters);
- ctor public BroadcastFrameClock();
- method public void cancel(optional java.util.concurrent.CancellationException cancellationException);
- method public boolean getHasAwaiters();
- method public void sendFrame(long timeNanos);
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public final boolean hasAwaiters;
- }
-
- public interface MonotonicFrameClock extends kotlin.coroutines.CoroutineContext.Element {
- method public default kotlin.coroutines.CoroutineContext.Key<?> getKey();
- method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- property public default kotlin.coroutines.CoroutineContext.Key<?> key;
- field public static final androidx.compose.runtime.dispatch.MonotonicFrameClock.Key Key;
- }
-
- public static final class MonotonicFrameClock.Key implements kotlin.coroutines.CoroutineContext.Key<androidx.compose.runtime.dispatch.MonotonicFrameClock> {
+ public final class ExpectKt {
+ method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
}
public final class MonotonicFrameClockKt {
- method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.dispatch.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
- method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method @Deprecated public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method @Deprecated public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
}
}
diff --git a/compose/runtime/runtime-dispatch/build.gradle b/compose/runtime/runtime-dispatch/build.gradle
index 7694ed5..7967eab 100644
--- a/compose/runtime/runtime-dispatch/build.gradle
+++ b/compose/runtime/runtime-dispatch/build.gradle
@@ -40,6 +40,8 @@
api(KOTLIN_COROUTINES_CORE)
api "androidx.annotation:annotation:1.1.0"
+ api(project(":compose:ui:ui"))
+
implementation(KOTLIN_STDLIB_COMMON)
implementation(KOTLIN_STDLIB)
implementation("androidx.core:core-ktx:1.1.0")
@@ -69,6 +71,7 @@
commonMain.dependencies {
implementation(KOTLIN_STDLIB_COMMON)
api(KOTLIN_COROUTINES_CORE)
+ api(project(":compose:ui:ui"))
}
jvmMain.dependencies {
implementation(KOTLIN_STDLIB)
diff --git a/compose/runtime/runtime-dispatch/src/androidAndroidTest/kotlin/androidx/compose/runtime/dispatch/TestActivity.kt b/compose/runtime/runtime-dispatch/src/androidAndroidTest/kotlin/androidx/compose/runtime/dispatch/TestActivity.kt
deleted file mode 100644
index 83fa029..0000000
--- a/compose/runtime/runtime-dispatch/src/androidAndroidTest/kotlin/androidx/compose/runtime/dispatch/TestActivity.kt
+++ /dev/null
@@ -1,21 +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.compose.runtime.dispatch
-
-import androidx.core.app.ComponentActivity
-
-class TestActivity : ComponentActivity()
\ No newline at end of file
diff --git a/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/ActualAndroid.kt b/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/ActualAndroid.kt
deleted file mode 100644
index 1beb3a0..0000000
--- a/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/ActualAndroid.kt
+++ /dev/null
@@ -1,45 +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.compose.runtime.dispatch
-
-import android.os.Looper
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.withContext
-
-/**
- * This is an inaccurate implementation that will only be used when running linked against
- * Android SDK stubs in host-side tests. A real implementation should synchronize with the
- * device's default display's vsync rate.
- */
-private object SdkStubsFallbackFrameClock : MonotonicFrameClock {
- private const val DefaultFrameDelay = 16L // milliseconds
-
- override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R =
- withContext(Dispatchers.Main) {
- delay(DefaultFrameDelay)
- onFrame(System.nanoTime())
- }
-}
-
-actual val DefaultMonotonicFrameClock: MonotonicFrameClock by lazy {
- // When linked against Android SDK stubs and running host-side tests, APIs such as
- // Looper.getMainLooper() that will never return null on a real device will return null.
- // This branch offers an alternative solution.
- if (Looper.getMainLooper() != null) AndroidUiDispatcher.Main[MonotonicFrameClock]!!
- else SdkStubsFallbackFrameClock
-}
diff --git a/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/Expect.kt b/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/Expect.kt
index 44010e8..d8e4796 100644
--- a/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/Expect.kt
+++ b/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/Expect.kt
@@ -16,12 +16,17 @@
package androidx.compose.runtime.dispatch
+import androidx.compose.runtime.withFrameMillis
+import androidx.compose.runtime.withFrameNanos
+
/**
* The [MonotonicFrameClock] used by [withFrameNanos] and [withFrameMillis] if one is not present
* in the calling [kotlin.coroutines.CoroutineContext].
*/
-// Implementor's note:
-// This frame clock implementation should try to synchronize with the vsync rate of the device's
-// default display. Without this synchronization, any usage of this default clock will result
-// in inconsistent animation frame timing and associated visual artifacts.
-expect val DefaultMonotonicFrameClock: MonotonicFrameClock
+@Suppress("DEPRECATION")
+@Deprecated(
+ "Moved to androidx.compose.runtime",
+ ReplaceWith("androidx.compose.runtime.DefaultMonotonicFrameClock")
+)
+val DefaultMonotonicFrameClock: MonotonicFrameClock
+ get() = androidx.compose.runtime.DefaultMonotonicFrameClock
diff --git a/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/MonotonicFrameClock.kt b/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/MonotonicFrameClock.kt
index 157c309..3882c52 100644
--- a/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/MonotonicFrameClock.kt
+++ b/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/MonotonicFrameClock.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,91 +16,29 @@
package androidx.compose.runtime.dispatch
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.coroutineContext
-
-/**
- * Provides a time source for display frames and the ability to perform an action on the next frame.
- * This may be used for matching timing with the refresh rate of a display or otherwise
- * synchronizing work with a desired frame rate.
- */
-interface MonotonicFrameClock : CoroutineContext.Element {
- /**
- * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
- * in nanoseconds in the calling context of frame dispatch, then resumes with the result from
- * [onFrame].
- *
- * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
- * as it may be normalized to the target time for the frame, not necessarily a direct,
- * "now" value.
- *
- * The time base of the value provided by [withFrameNanos] is implementation defined.
- * Time values provided are monotonically increasing; after a call to [withFrameNanos]
- * completes it must not provide the same value again for a subsequent call.
- */
- suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R
-
- override val key: CoroutineContext.Key<*> get() = Key
-
- companion object Key : CoroutineContext.Key<MonotonicFrameClock>
-}
+@Deprecated("Moved to androidx.compose.runtime in the compose:runtime artifact")
+typealias MonotonicFrameClock = androidx.compose.runtime.MonotonicFrameClock
/**
* Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
* in nanoseconds in the calling context of frame dispatch, then resumes with the result from
* [onFrame].
- *
- * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
- * as it may be normalized to the target time for the frame, not necessarily a direct,
- * "now" value.
- *
- * The time base of the value provided by [MonotonicFrameClock.withFrameMillis] is
- * implementation defined. Time values provided are monotonically increasing; after a call to
- * [MonotonicFrameClock.withFrameMillis] completes it must not provide the same value again for
- * a subsequent call.
*/
-@Suppress("UnnecessaryLambdaCreation")
-suspend inline fun <R> MonotonicFrameClock.withFrameMillis(
- crossinline onFrame: (frameTimeMillis: Long) -> R
-): R = withFrameNanos { onFrame(it / 1_000_000L) }
-
-/**
- * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
- * in nanoseconds in the calling context of frame dispatch, then resumes with the result from
- * [onFrame].
- *
- * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
- * as it may be normalized to the target time for the frame, not necessarily a direct,
- * "now" value.
- *
- * The time base of the value provided by [withFrameNanos] is implementation defined.
- * Time values provided are monotonically increasing; after a call to [withFrameNanos]
- * completes it must not provide the same value again for a subsequent call.
- *
- * This function will invoke [MonotonicFrameClock.withFrameNanos] using the calling
- * [CoroutineContext]'s [MonotonicFrameClock] or a default frame clock if one is not present
- * in the [CoroutineContext].
- */
+@Deprecated(
+ "Moved to androidx.compose.runtime",
+ ReplaceWith("androidx.compose.runtime.withFrameNanos(onFrame)")
+)
suspend fun <R> withFrameNanos(onFrame: (frameTimeMillis: Long) -> R): R =
- (coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock).withFrameNanos(onFrame)
+ androidx.compose.runtime.withFrameNanos(onFrame)
/**
* Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
* in nanoseconds in the calling context of frame dispatch, then resumes with the result from
* [onFrame].
- *
- * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
- * as it may be normalized to the target time for the frame, not necessarily a direct,
- * "now" value.
- *
- * The time base of the value provided by [MonotonicFrameClock.withFrameMillis] is
- * implementation defined. Time values provided are monotonically increasing; after a call to
- * [MonotonicFrameClock.withFrameMillis] completes it must not provide the same value again for
- * a subsequent call.
- *
- * This function will invoke [MonotonicFrameClock.withFrameNanos] using the calling
- * [CoroutineContext]'s [MonotonicFrameClock] or a default frame clock if one is not present
- * in the [CoroutineContext].
*/
+@Deprecated(
+ "Moved to androidx.compose.runtime",
+ ReplaceWith("androidx.compose.runtime.withFrameMillis(onFrame)")
+)
suspend fun <R> withFrameMillis(onFrame: (frameTimeMillis: Long) -> R): R =
- (coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock).withFrameMillis(onFrame)
+ androidx.compose.runtime.withFrameMillis(onFrame)
diff --git a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt
deleted file mode 100644
index 711bd90..0000000
--- a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt
+++ /dev/null
@@ -1,49 +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.compose.runtime.dispatch
-
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.yield
-import java.awt.DisplayMode
-import java.awt.GraphicsEnvironment
-
-// TODO implement local Recomposer in each Window, so each Window can have own MonotonicFrameClock.
-// It is needed for smooth animations and for the case when user have multiple windows on multiple
-// monitors with different refresh rates.
-// see https://github.com/JetBrains/compose-jb/issues/137
-actual val DefaultMonotonicFrameClock: MonotonicFrameClock by lazy {
- object : MonotonicFrameClock {
- override suspend fun <R> withFrameNanos(
- onFrame: (Long) -> R
- ): R {
- if (GraphicsEnvironment.isHeadless()) {
- yield()
- } else {
- delay(1000L / getFramesPerSecond())
- }
- return onFrame(System.nanoTime())
- }
-
- private fun getFramesPerSecond(): Int {
- val refreshRate = GraphicsEnvironment
- .getLocalGraphicsEnvironment()
- .screenDevices.maxOfOrNull { it.displayMode.refreshRate }
- ?: DisplayMode.REFRESH_RATE_UNKNOWN
- return if (refreshRate != DisplayMode.REFRESH_RATE_UNKNOWN) refreshRate else 60
- }
- }
-}
\ No newline at end of file
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index dfe7b99..833d1e5 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -17,7 +17,7 @@
}
public final class ActualAndroidKt {
- method public static androidx.compose.runtime.EmbeddingContext EmbeddingContext();
+ method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
}
public final class ActualJvmKt {
@@ -51,6 +51,16 @@
public final class BitwiseOperatorsKt {
}
+ public final class BroadcastFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public BroadcastFrameClock(kotlin.jvm.functions.Function0<kotlin.Unit>? onNewAwaiters);
+ ctor public BroadcastFrameClock();
+ method public void cancel(optional java.util.concurrent.CancellationException cancellationException);
+ method public boolean getHasAwaiters();
+ method public void sendFrame(long timeNanos);
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public final boolean hasAwaiters;
+ }
+
@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
}
@@ -219,11 +229,6 @@
method public inline androidx.compose.runtime.DisposableEffectResult onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> onDisposeEffect);
}
- public interface EmbeddingContext {
- method public boolean isMainThread();
- method public kotlin.coroutines.CoroutineContext mainThreadCompositionContext();
- }
-
public final class ExpectKt {
}
@@ -269,6 +274,22 @@
method @androidx.compose.runtime.InternalComposeApi public static void resetSourceInfo();
}
+ public interface MonotonicFrameClock extends kotlin.coroutines.CoroutineContext.Element {
+ method public default kotlin.coroutines.CoroutineContext.Key<?> getKey();
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public default kotlin.coroutines.CoroutineContext.Key<?> key;
+ field public static final androidx.compose.runtime.MonotonicFrameClock.Key Key;
+ }
+
+ public static final class MonotonicFrameClock.Key implements kotlin.coroutines.CoroutineContext.Key<androidx.compose.runtime.MonotonicFrameClock> {
+ }
+
+ public final class MonotonicFrameClockKt {
+ method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ }
+
@androidx.compose.runtime.Stable public interface MutableState<T> extends androidx.compose.runtime.State<T> {
method public operator T! component1();
method public operator kotlin.jvm.functions.Function1<T,kotlin.Unit> component2();
@@ -294,8 +315,8 @@
@kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface NoLiveLiterals {
}
- public final class PausableMonotonicFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public PausableMonotonicFrameClock(androidx.compose.runtime.dispatch.MonotonicFrameClock frameClock);
+ public final class PausableMonotonicFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public PausableMonotonicFrameClock(androidx.compose.runtime.MonotonicFrameClock frameClock);
method public boolean isPaused();
method public void pause();
method public void resume();
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index dfe7b99..833d1e5 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -17,7 +17,7 @@
}
public final class ActualAndroidKt {
- method public static androidx.compose.runtime.EmbeddingContext EmbeddingContext();
+ method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
}
public final class ActualJvmKt {
@@ -51,6 +51,16 @@
public final class BitwiseOperatorsKt {
}
+ public final class BroadcastFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public BroadcastFrameClock(kotlin.jvm.functions.Function0<kotlin.Unit>? onNewAwaiters);
+ ctor public BroadcastFrameClock();
+ method public void cancel(optional java.util.concurrent.CancellationException cancellationException);
+ method public boolean getHasAwaiters();
+ method public void sendFrame(long timeNanos);
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public final boolean hasAwaiters;
+ }
+
@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
}
@@ -219,11 +229,6 @@
method public inline androidx.compose.runtime.DisposableEffectResult onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> onDisposeEffect);
}
- public interface EmbeddingContext {
- method public boolean isMainThread();
- method public kotlin.coroutines.CoroutineContext mainThreadCompositionContext();
- }
-
public final class ExpectKt {
}
@@ -269,6 +274,22 @@
method @androidx.compose.runtime.InternalComposeApi public static void resetSourceInfo();
}
+ public interface MonotonicFrameClock extends kotlin.coroutines.CoroutineContext.Element {
+ method public default kotlin.coroutines.CoroutineContext.Key<?> getKey();
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public default kotlin.coroutines.CoroutineContext.Key<?> key;
+ field public static final androidx.compose.runtime.MonotonicFrameClock.Key Key;
+ }
+
+ public static final class MonotonicFrameClock.Key implements kotlin.coroutines.CoroutineContext.Key<androidx.compose.runtime.MonotonicFrameClock> {
+ }
+
+ public final class MonotonicFrameClockKt {
+ method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ }
+
@androidx.compose.runtime.Stable public interface MutableState<T> extends androidx.compose.runtime.State<T> {
method public operator T! component1();
method public operator kotlin.jvm.functions.Function1<T,kotlin.Unit> component2();
@@ -294,8 +315,8 @@
@kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface NoLiveLiterals {
}
- public final class PausableMonotonicFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public PausableMonotonicFrameClock(androidx.compose.runtime.dispatch.MonotonicFrameClock frameClock);
+ public final class PausableMonotonicFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public PausableMonotonicFrameClock(androidx.compose.runtime.MonotonicFrameClock frameClock);
method public boolean isPaused();
method public void pause();
method public void resume();
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 48d7439..07e1499 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -17,7 +17,7 @@
}
public final class ActualAndroidKt {
- method public static androidx.compose.runtime.EmbeddingContext EmbeddingContext();
+ method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
}
public final class ActualJvmKt {
@@ -52,6 +52,16 @@
public final class BitwiseOperatorsKt {
}
+ public final class BroadcastFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public BroadcastFrameClock(kotlin.jvm.functions.Function0<kotlin.Unit>? onNewAwaiters);
+ ctor public BroadcastFrameClock();
+ method public void cancel(optional java.util.concurrent.CancellationException cancellationException);
+ method public boolean getHasAwaiters();
+ method public void sendFrame(long timeNanos);
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public final boolean hasAwaiters;
+ }
+
@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
}
@@ -242,11 +252,6 @@
method public inline androidx.compose.runtime.DisposableEffectResult onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> onDisposeEffect);
}
- public interface EmbeddingContext {
- method public boolean isMainThread();
- method public kotlin.coroutines.CoroutineContext mainThreadCompositionContext();
- }
-
public final class ExpectKt {
method @kotlin.PublishedApi internal static inline <R> R! synchronized(Object lock, kotlin.jvm.functions.Function0<? extends R> block);
}
@@ -293,6 +298,22 @@
method @androidx.compose.runtime.InternalComposeApi public static void resetSourceInfo();
}
+ public interface MonotonicFrameClock extends kotlin.coroutines.CoroutineContext.Element {
+ method public default kotlin.coroutines.CoroutineContext.Key<?> getKey();
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public default kotlin.coroutines.CoroutineContext.Key<?> key;
+ field public static final androidx.compose.runtime.MonotonicFrameClock.Key Key;
+ }
+
+ public static final class MonotonicFrameClock.Key implements kotlin.coroutines.CoroutineContext.Key<androidx.compose.runtime.MonotonicFrameClock> {
+ }
+
+ public final class MonotonicFrameClockKt {
+ method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ }
+
@androidx.compose.runtime.Stable public interface MutableState<T> extends androidx.compose.runtime.State<T> {
method public operator T! component1();
method public operator kotlin.jvm.functions.Function1<T,kotlin.Unit> component2();
@@ -318,8 +339,8 @@
@kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface NoLiveLiterals {
}
- public final class PausableMonotonicFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
- ctor public PausableMonotonicFrameClock(androidx.compose.runtime.dispatch.MonotonicFrameClock frameClock);
+ public final class PausableMonotonicFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public PausableMonotonicFrameClock(androidx.compose.runtime.MonotonicFrameClock frameClock);
method public boolean isPaused();
method public void pause();
method public void resume();
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 3bec43a..b0d5f77 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -38,7 +38,6 @@
* corresponding block below
*/
- api project(':compose:runtime:runtime-dispatch')
api(KOTLIN_COROUTINES_ANDROID)
implementation "androidx.annotation:annotation:1.1.0"
@@ -76,7 +75,6 @@
commonMain.dependencies {
implementation(KOTLIN_STDLIB_COMMON)
implementation(KOTLIN_COROUTINES_CORE)
- api project(':compose:runtime:runtime-dispatch')
implementation "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3"
}
jvmMain.dependencies {
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SideEffectTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SideEffectTests.kt
index 386994c..e5a5fd3 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SideEffectTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SideEffectTests.kt
@@ -17,8 +17,6 @@
package androidx.compose.runtime
import android.view.Choreographer
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
-import androidx.compose.runtime.dispatch.withFrameNanos
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import kotlinx.coroutines.channels.Channel
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt
index 443d8f3..75c706f 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt
@@ -17,8 +17,6 @@
package androidx.compose.runtime
import android.view.Choreographer
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
-import androidx.compose.runtime.dispatch.withFrameNanos
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import kotlinx.coroutines.CoroutineScope
diff --git a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.kt b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.kt
index 86e3745..1aab3c2 100644
--- a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.kt
+++ b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.kt
@@ -17,29 +17,12 @@
package androidx.compose.runtime
import android.os.Looper
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher
+import android.view.Choreographer
import kotlinx.coroutines.Dispatchers
-import kotlin.coroutines.CoroutineContext
-
-private object AndroidEmbeddingContext : EmbeddingContext {
-
- override fun isMainThread(): Boolean {
- return Looper.myLooper() == Looper.getMainLooper()
- }
-
- override fun mainThreadCompositionContext(): CoroutineContext {
- return MainAndroidUiContext
- }
-}
-
-actual fun EmbeddingContext(): EmbeddingContext = AndroidEmbeddingContext
-
-// TODO: Our host-side tests still grab the Android actuals based on SDK stubs that return null.
-// Satisfy their dependencies.
-private val MainAndroidUiContext: CoroutineContext by lazy {
- if (Looper.getMainLooper() != null) AndroidUiDispatcher.Main
- else Dispatchers.Main
-}
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
internal actual object Trace {
actual fun beginSection(name: String): Any? {
@@ -55,3 +38,47 @@
internal actual typealias MainThread = androidx.annotation.MainThread
internal actual typealias CheckResult = androidx.annotation.CheckResult
+
+/**
+ * This is an inaccurate implementation that will only be used when running linked against
+ * Android SDK stubs in host-side tests. A real implementation should synchronize with the
+ * device's default display's vsync rate.
+ */
+private object SdkStubsFallbackFrameClock : MonotonicFrameClock {
+ private const val DefaultFrameDelay = 16L // milliseconds
+
+ override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R =
+ withContext(Dispatchers.Main) {
+ delay(DefaultFrameDelay)
+ onFrame(System.nanoTime())
+ }
+}
+
+private object DefaultChoreographerFrameClock : MonotonicFrameClock {
+ private val choreographer = runBlocking(Dispatchers.Main.immediate) {
+ Choreographer.getInstance()
+ }
+
+ override suspend fun <R> withFrameNanos(
+ onFrame: (frameTimeNanos: Long) -> R
+ ): R = suspendCancellableCoroutine<R> { co ->
+ val callback = Choreographer.FrameCallback { frameTimeNanos ->
+ co.resumeWith(runCatching { onFrame(frameTimeNanos) })
+ }
+ choreographer.postFrameCallback(callback)
+ co.invokeOnCancellation { choreographer.removeFrameCallback(callback) }
+ }
+}
+
+// For local testing
+private const val DisallowDefaultMonotonicFrameClock = false
+
+actual val DefaultMonotonicFrameClock: MonotonicFrameClock by lazy {
+ if (DisallowDefaultMonotonicFrameClock) error("Disallowed use of DefaultMonotonicFrameClock")
+
+ // When linked against Android SDK stubs and running host-side tests, APIs such as
+ // Looper.getMainLooper() that will never return null on a real device will return null.
+ // This branch offers an alternative solution.
+ if (Looper.getMainLooper() != null) DefaultChoreographerFrameClock
+ else SdkStubsFallbackFrameClock
+}
diff --git a/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/BroadcastFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
similarity index 97%
rename from compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/BroadcastFrameClock.kt
rename to compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
index 79db2ef..e5112e9 100644
--- a/compose/runtime/runtime-dispatch/src/commonMain/kotlin/androidx/compose/runtime/dispatch/BroadcastFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.compose.runtime.dispatch
+package androidx.compose.runtime
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.suspendCancellableCoroutine
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/EmbeddingContext.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/EmbeddingContext.kt
deleted file mode 100644
index d2958264..0000000
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/EmbeddingContext.kt
+++ /dev/null
@@ -1,26 +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.compose.runtime
-
-import kotlin.coroutines.CoroutineContext
-
-interface EmbeddingContext {
- fun isMainThread(): Boolean
- fun mainThreadCompositionContext(): CoroutineContext
-}
-
-expect fun EmbeddingContext(): EmbeddingContext
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
index 9edecf5..2fa9cba 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
@@ -68,3 +68,13 @@
expect annotation class CheckResult(
val suggest: String
)
+
+/**
+ * The [MonotonicFrameClock] used by [withFrameNanos] and [withFrameMillis] if one is not present
+ * in the calling [kotlin.coroutines.CoroutineContext].
+ */
+// Implementor's note:
+// This frame clock implementation should try to synchronize with the vsync rate of the device's
+// default display. Without this synchronization, any usage of this default clock will result
+// in inconsistent animation frame timing and associated visual artifacts.
+expect val DefaultMonotonicFrameClock: MonotonicFrameClock
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt
new file mode 100644
index 0000000..17d6bff
--- /dev/null
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime
+
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.coroutineContext
+
+/**
+ * Provides a time source for display frames and the ability to perform an action on the next frame.
+ * This may be used for matching timing with the refresh rate of a display or otherwise
+ * synchronizing work with a desired frame rate.
+ */
+interface MonotonicFrameClock : CoroutineContext.Element {
+ /**
+ * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
+ * in nanoseconds in the calling context of frame dispatch, then resumes with the result from
+ * [onFrame].
+ *
+ * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
+ * as it may be normalized to the target time for the frame, not necessarily a direct,
+ * "now" value.
+ *
+ * The time base of the value provided by [withFrameNanos] is implementation defined.
+ * Time values provided are monotonically increasing; after a call to [withFrameNanos]
+ * completes it must not provide the same value again for a subsequent call.
+ */
+ suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R
+
+ override val key: CoroutineContext.Key<*> get() = Key
+
+ companion object Key : CoroutineContext.Key<MonotonicFrameClock>
+}
+
+/**
+ * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
+ * in nanoseconds in the calling context of frame dispatch, then resumes with the result from
+ * [onFrame].
+ *
+ * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
+ * as it may be normalized to the target time for the frame, not necessarily a direct,
+ * "now" value.
+ *
+ * The time base of the value provided by [MonotonicFrameClock.withFrameMillis] is
+ * implementation defined. Time values provided are monotonically increasing; after a call to
+ * [MonotonicFrameClock.withFrameMillis] completes it must not provide the same value again for
+ * a subsequent call.
+ */
+@Suppress("UnnecessaryLambdaCreation")
+suspend inline fun <R> MonotonicFrameClock.withFrameMillis(
+ crossinline onFrame: (frameTimeMillis: Long) -> R
+): R = withFrameNanos { onFrame(it / 1_000_000L) }
+
+/**
+ * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
+ * in nanoseconds in the calling context of frame dispatch, then resumes with the result from
+ * [onFrame].
+ *
+ * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
+ * as it may be normalized to the target time for the frame, not necessarily a direct,
+ * "now" value.
+ *
+ * The time base of the value provided by [withFrameNanos] is implementation defined.
+ * Time values provided are monotonically increasing; after a call to [withFrameNanos]
+ * completes it must not provide the same value again for a subsequent call.
+ *
+ * This function will invoke [MonotonicFrameClock.withFrameNanos] using the calling
+ * [CoroutineContext]'s [MonotonicFrameClock] or a default frame clock if one is not present
+ * in the [CoroutineContext].
+ */
+suspend fun <R> withFrameNanos(onFrame: (frameTimeMillis: Long) -> R): R =
+ (coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock).withFrameNanos(onFrame)
+
+/**
+ * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
+ * in nanoseconds in the calling context of frame dispatch, then resumes with the result from
+ * [onFrame].
+ *
+ * `frameTimeNanos` should be used when calculating animation time deltas from frame to frame
+ * as it may be normalized to the target time for the frame, not necessarily a direct,
+ * "now" value.
+ *
+ * The time base of the value provided by [MonotonicFrameClock.withFrameMillis] is
+ * implementation defined. Time values provided are monotonically increasing; after a call to
+ * [MonotonicFrameClock.withFrameMillis] completes it must not provide the same value again for
+ * a subsequent call.
+ *
+ * This function will invoke [MonotonicFrameClock.withFrameNanos] using the calling
+ * [CoroutineContext]'s [MonotonicFrameClock] or a default frame clock if one is not present
+ * in the [CoroutineContext].
+ */
+suspend fun <R> withFrameMillis(onFrame: (frameTimeMillis: Long) -> R): R =
+ (coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock).withFrameMillis(onFrame)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableMonotonicFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableMonotonicFrameClock.kt
index 2312443..b8a9246c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableMonotonicFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableMonotonicFrameClock.kt
@@ -16,8 +16,6 @@
package androidx.compose.runtime
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
-
/**
* A [MonotonicFrameClock] wrapper that can be [pause]d and [resume]d.
*
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 3dbe264..8cc6928 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -16,9 +16,6 @@
package androidx.compose.runtime
-import androidx.compose.runtime.dispatch.BroadcastFrameClock
-import androidx.compose.runtime.dispatch.DefaultMonotonicFrameClock
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
import androidx.compose.runtime.snapshots.MutableSnapshot
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.SnapshotApplyResult
diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt
index 84b217f..6da8ac9b 100644
--- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt
+++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt
@@ -16,23 +16,10 @@
package androidx.compose.runtime
-import androidx.compose.runtime.dispatch.DefaultMonotonicFrameClock
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.swing.Swing
-import javax.swing.SwingUtilities
-import kotlin.coroutines.CoroutineContext
-
-class SwingEmbeddingContext : EmbeddingContext {
- override fun isMainThread(): Boolean {
- return SwingUtilities.isEventDispatchThread()
- }
-
- override fun mainThreadCompositionContext(): CoroutineContext {
- return Dispatchers.Swing + DefaultMonotonicFrameClock
- }
-}
-
-actual fun EmbeddingContext(): EmbeddingContext = SwingEmbeddingContext()
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.yield
+import java.awt.DisplayMode
+import java.awt.GraphicsEnvironment
internal actual object Trace {
actual fun beginSection(name: String): Any? {
@@ -104,4 +91,31 @@
// TODO(igotti): do we need actual processing for those?
actual annotation class MainThread()
-actual annotation class CheckResult(actual val suggest: String)
\ No newline at end of file
+actual annotation class CheckResult(actual val suggest: String)
+
+// TODO implement local Recomposer in each Window, so each Window can have own MonotonicFrameClock.
+// It is needed for smooth animations and for the case when user have multiple windows on multiple
+// monitors with different refresh rates.
+// see https://github.com/JetBrains/compose-jb/issues/137
+actual val DefaultMonotonicFrameClock: MonotonicFrameClock by lazy {
+ object : MonotonicFrameClock {
+ override suspend fun <R> withFrameNanos(
+ onFrame: (Long) -> R
+ ): R {
+ if (GraphicsEnvironment.isHeadless()) {
+ yield()
+ } else {
+ delay(1000L / getFramesPerSecond())
+ }
+ return onFrame(System.nanoTime())
+ }
+
+ private fun getFramesPerSecond(): Int {
+ val refreshRate = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .screenDevices.maxOfOrNull { it.displayMode.refreshRate }
+ ?: DisplayMode.REFRESH_RATE_UNKNOWN
+ return if (refreshRate != DisplayMode.REFRESH_RATE_UNKNOWN) refreshRate else 60
+ }
+ }
+}
diff --git a/compose/runtime/runtime-dispatch/src/test/kotlin/androidx/compose/runtime/dispatch/BroadcastFrameClockTest.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/BroadcastFrameClockTest.kt
similarity index 91%
rename from compose/runtime/runtime-dispatch/src/test/kotlin/androidx/compose/runtime/dispatch/BroadcastFrameClockTest.kt
rename to compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/BroadcastFrameClockTest.kt
index 7204639..b348e9b 100644
--- a/compose/runtime/runtime-dispatch/src/test/kotlin/androidx/compose/runtime/dispatch/BroadcastFrameClockTest.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/BroadcastFrameClockTest.kt
@@ -29,7 +29,7 @@
class BroadcastFrameClockTest {
@Test
fun sendAndReceiveFrames() = runBlockingTest {
- val clock = BroadcastFrameClock()
+ val clock = androidx.compose.runtime.BroadcastFrameClock()
val frameAwaiter = async { clock.withFrameNanos { it } }
@@ -49,7 +49,7 @@
@Test
fun cancelClock() = runBlockingTest {
- val clock = BroadcastFrameClock()
+ val clock = androidx.compose.runtime.BroadcastFrameClock()
val frameAwaiter = async { clock.withFrameNanos { it } }
clock.cancel()
@@ -64,7 +64,7 @@
@Test
fun failClockWhenNewAwaitersNotified() = runBlockingTest {
- val clock = BroadcastFrameClock {
+ val clock = androidx.compose.runtime.BroadcastFrameClock {
throw CancellationException("failed frame clock")
}
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/TestMonotonicFrameClock.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/TestMonotonicFrameClock.kt
index d7d6eb2..87ba6a4 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/TestMonotonicFrameClock.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/TestMonotonicFrameClock.kt
@@ -20,7 +20,7 @@
*/
package androidx.compose.runtime.mock
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
index 61e30d5..78b7b8f 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
@@ -31,7 +31,7 @@
import androidx.activity.ComponentActivity
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.Recomposer
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.platform.ViewRootForTest
import androidx.compose.ui.platform.setContent
diff --git a/compose/ui/ui-inspection/build.gradle b/compose/ui/ui-inspection/build.gradle
index b0b764a..e562922 100644
--- a/compose/ui/ui-inspection/build.gradle
+++ b/compose/ui/ui-inspection/build.gradle
@@ -79,3 +79,7 @@
]
}
}
+
+inspection {
+ name = "compose-ui-inspection.jar"
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
index b54bb4e..3895bc6 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
@@ -30,11 +30,11 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.State
-import androidx.compose.runtime.dispatch.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 343c76f..61c92645 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -279,7 +279,7 @@
public final class TestContext {
}
- @kotlinx.coroutines.ExperimentalCoroutinesApi public final class TestMonotonicFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
+ @kotlinx.coroutines.ExperimentalCoroutinesApi public final class TestMonotonicFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
ctor public TestMonotonicFrameClock(kotlinx.coroutines.CoroutineScope coroutineScope, kotlinx.coroutines.test.DelayController delayController, long frameDelayNanos);
method public long getFrameDelayNanos();
method public boolean getHasAwaiters();
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index 343c76f..61c92645 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -279,7 +279,7 @@
public final class TestContext {
}
- @kotlinx.coroutines.ExperimentalCoroutinesApi public final class TestMonotonicFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
+ @kotlinx.coroutines.ExperimentalCoroutinesApi public final class TestMonotonicFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
ctor public TestMonotonicFrameClock(kotlinx.coroutines.CoroutineScope coroutineScope, kotlinx.coroutines.test.DelayController delayController, long frameDelayNanos);
method public long getFrameDelayNanos();
method public boolean getHasAwaiters();
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index 343c76f..61c92645 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -279,7 +279,7 @@
public final class TestContext {
}
- @kotlinx.coroutines.ExperimentalCoroutinesApi public final class TestMonotonicFrameClock implements androidx.compose.runtime.dispatch.MonotonicFrameClock {
+ @kotlinx.coroutines.ExperimentalCoroutinesApi public final class TestMonotonicFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
ctor public TestMonotonicFrameClock(kotlinx.coroutines.CoroutineScope coroutineScope, kotlinx.coroutines.test.DelayController delayController, long frameDelayNanos);
method public long getFrameDelayNanos();
method public boolean getHasAwaiters();
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/TestMonotonicFrameClockTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/TestMonotonicFrameClockTest.kt
index adbf9ab..75bfc07 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/TestMonotonicFrameClockTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/TestMonotonicFrameClockTest.kt
@@ -16,7 +16,7 @@
package androidx.compose.ui.test
-import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.withFrameNanos
import androidx.test.filters.SmallTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt
index b43fcc5..adadb76 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt
@@ -25,7 +25,7 @@
import android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT
import android.view.MotionEvent.ACTION_POINTER_UP
import android.view.MotionEvent.ACTION_UP
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher
+import androidx.compose.ui.platform.AndroidUiDispatcher
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.node.RootForTest
import androidx.compose.ui.platform.ViewRootForTest
diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.kt
index 289a08d..f68fbfe 100644
--- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.kt
@@ -16,7 +16,7 @@
package androidx.compose.ui.test
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index aea0509..b39e99c 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2185,6 +2185,32 @@
public final class AndroidComposeViewKt {
}
+ public final class AndroidUiDispatcher extends kotlinx.coroutines.CoroutineDispatcher {
+ method public void dispatch(kotlin.coroutines.CoroutineContext context, Runnable block);
+ method public android.view.Choreographer getChoreographer();
+ method public androidx.compose.runtime.MonotonicFrameClock getFrameClock();
+ property public final android.view.Choreographer choreographer;
+ property public final androidx.compose.runtime.MonotonicFrameClock frameClock;
+ field public static final androidx.compose.ui.platform.AndroidUiDispatcher.Companion Companion;
+ }
+
+ public static final class AndroidUiDispatcher.Companion {
+ method public kotlin.coroutines.CoroutineContext getCurrentThread();
+ method public kotlin.coroutines.CoroutineContext getMain();
+ property public final kotlin.coroutines.CoroutineContext CurrentThread;
+ property public final kotlin.coroutines.CoroutineContext Main;
+ }
+
+ public final class AndroidUiDispatcherKt {
+ }
+
+ public final class AndroidUiFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public AndroidUiFrameClock(android.view.Choreographer choreographer);
+ method public android.view.Choreographer getChoreographer();
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public final android.view.Choreographer choreographer;
+ }
+
public final class AndroidUriHandler implements androidx.compose.ui.platform.UriHandler {
ctor public AndroidUriHandler(android.content.Context context);
method public void openUri(String uri);
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index aea0509..b39e99c 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2185,6 +2185,32 @@
public final class AndroidComposeViewKt {
}
+ public final class AndroidUiDispatcher extends kotlinx.coroutines.CoroutineDispatcher {
+ method public void dispatch(kotlin.coroutines.CoroutineContext context, Runnable block);
+ method public android.view.Choreographer getChoreographer();
+ method public androidx.compose.runtime.MonotonicFrameClock getFrameClock();
+ property public final android.view.Choreographer choreographer;
+ property public final androidx.compose.runtime.MonotonicFrameClock frameClock;
+ field public static final androidx.compose.ui.platform.AndroidUiDispatcher.Companion Companion;
+ }
+
+ public static final class AndroidUiDispatcher.Companion {
+ method public kotlin.coroutines.CoroutineContext getCurrentThread();
+ method public kotlin.coroutines.CoroutineContext getMain();
+ property public final kotlin.coroutines.CoroutineContext CurrentThread;
+ property public final kotlin.coroutines.CoroutineContext Main;
+ }
+
+ public final class AndroidUiDispatcherKt {
+ }
+
+ public final class AndroidUiFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public AndroidUiFrameClock(android.view.Choreographer choreographer);
+ method public android.view.Choreographer getChoreographer();
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public final android.view.Choreographer choreographer;
+ }
+
public final class AndroidUriHandler implements androidx.compose.ui.platform.UriHandler {
ctor public AndroidUriHandler(android.content.Context context);
method public void openUri(String uri);
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index bdcf71e..3ff9be4b 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2247,6 +2247,32 @@
public final class AndroidComposeViewKt {
}
+ public final class AndroidUiDispatcher extends kotlinx.coroutines.CoroutineDispatcher {
+ method public void dispatch(kotlin.coroutines.CoroutineContext context, Runnable block);
+ method public android.view.Choreographer getChoreographer();
+ method public androidx.compose.runtime.MonotonicFrameClock getFrameClock();
+ property public final android.view.Choreographer choreographer;
+ property public final androidx.compose.runtime.MonotonicFrameClock frameClock;
+ field public static final androidx.compose.ui.platform.AndroidUiDispatcher.Companion Companion;
+ }
+
+ public static final class AndroidUiDispatcher.Companion {
+ method public kotlin.coroutines.CoroutineContext getCurrentThread();
+ method public kotlin.coroutines.CoroutineContext getMain();
+ property public final kotlin.coroutines.CoroutineContext CurrentThread;
+ property public final kotlin.coroutines.CoroutineContext Main;
+ }
+
+ public final class AndroidUiDispatcherKt {
+ }
+
+ public final class AndroidUiFrameClock implements androidx.compose.runtime.MonotonicFrameClock {
+ ctor public AndroidUiFrameClock(android.view.Choreographer choreographer);
+ method public android.view.Choreographer getChoreographer();
+ method public suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
+ property public final android.view.Choreographer choreographer;
+ }
+
public final class AndroidUriHandler implements androidx.compose.ui.platform.UriHandler {
ctor public AndroidUriHandler(android.content.Context context);
method public void openUri(String uri);
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index b50126b..9f93aaf 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -72,6 +72,7 @@
testImplementation(ANDROIDX_TEST_RULES)
testImplementation(ANDROIDX_TEST_RUNNER)
+ testImplementation(KOTLIN_COROUTINES_TEST)
testImplementation(JUNIT)
testImplementation(TRUTH)
testImplementation(MOCKITO_CORE)
@@ -87,6 +88,8 @@
androidTestImplementation(ANDROIDX_TEST_UIAUTOMATOR)
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ANDROIDX_TEST_EXT_KTX)
+ androidTestImplementation(KOTLIN_COROUTINES_TEST)
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(JUNIT)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
@@ -104,6 +107,7 @@
androidTestImplementation project(":compose:ui:ui-test-junit4")
androidTestImplementation project(":test-screenshot")
androidTestImplementation "androidx.recyclerview:recyclerview:1.1.0"
+ androidTestImplementation("androidx.core:core-ktx:1.1.0")
lintChecks project(":compose:ui:ui-lint")
lintPublish project(":compose:ui:ui-lint")
@@ -166,6 +170,7 @@
test.dependencies {
implementation(ANDROIDX_TEST_RULES)
implementation(ANDROIDX_TEST_RUNNER)
+ implementation(KOTLIN_COROUTINES_TEST)
implementation(JUNIT)
implementation(TRUTH)
implementation(MOCKITO_CORE)
@@ -183,6 +188,8 @@
implementation(ANDROIDX_TEST_UIAUTOMATOR)
implementation(ANDROIDX_TEST_RULES)
implementation(ANDROIDX_TEST_RUNNER)
+ implementation(ANDROIDX_TEST_EXT_KTX)
+ implementation(KOTLIN_COROUTINES_TEST)
implementation(ESPRESSO_CORE)
implementation(JUNIT)
implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
@@ -200,6 +207,7 @@
implementation project(":compose:ui:ui-test-junit4")
implementation project(":test-screenshot")
implementation "androidx.recyclerview:recyclerview:1.1.0"
+ implementation("androidx.core:core-ktx:1.1.0")
}
desktopTest.dependencies {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt
index c4198df..91f3634 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt
@@ -24,7 +24,7 @@
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher
+import androidx.compose.ui.platform.AndroidUiDispatcher
import androidx.compose.testutils.ComposeTestCase
import androidx.compose.testutils.createAndroidComposeBenchmarkRunner
import androidx.compose.ui.platform.setContent
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/ClickNotPlacedChildTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/ClickNotPlacedChildTest.kt
new file mode 100644
index 0000000..3fbc3d6
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/ClickNotPlacedChildTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.input
+
+import android.os.Build
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ClickNotPlacedChildTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childIsDisplayedWhenItWasNotPlacedOriginallyButPlacedLater() {
+ var firstClickedTimes = 0
+ var secondClickedTimes = 0
+ composeTestRule.setContent {
+ Layout(
+ content = {
+ Box(
+ Modifier.fillMaxSize().clickable {
+ firstClickedTimes++
+ }
+ )
+ Box(
+ Modifier.fillMaxSize().clickable {
+ secondClickedTimes++
+ }
+ )
+ },
+ modifier = Modifier.size(100.dp).testTag("parent")
+ ) { measutables, constraints ->
+ val first = measutables[0].measure(constraints)
+ measutables[1].measure(constraints)
+ layout(first.width, first.height) {
+ first.place(0, 0)
+ }
+ }
+ }
+
+ composeTestRule.onNodeWithTag("parent")
+ .performClick()
+
+ composeTestRule.runOnIdle {
+ assertEquals(0, secondClickedTimes)
+ assertEquals(1, firstClickedTimes)
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index 3ae5bed..26bf47f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.input.pointer
+import androidx.compose.foundation.layout.offset
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.Autofill
@@ -29,9 +30,10 @@
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.node.InternalCoreApi
import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.LayoutNodeWrapper
+import androidx.compose.ui.node.MeasureAndLayoutDelegate
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.node.Owner
import androidx.compose.ui.node.OwnerSnapshotObserver
@@ -51,7 +53,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.spy
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -83,15 +84,20 @@
@RunWith(AndroidJUnit4::class)
class PointerInputEventProcessorTest {
- private lateinit var root: LayoutNode
private lateinit var pointerInputEventProcessor: PointerInputEventProcessor
- private val testOwner: TestOwner = spy()
+ private lateinit var testOwner: TestOwner
@Before
fun setup() {
- root = LayoutNode(0, 0, 500, 500)
- root.attach(testOwner)
- pointerInputEventProcessor = PointerInputEventProcessor(root)
+ testOwner = TestOwner()
+ pointerInputEventProcessor = PointerInputEventProcessor(testOwner.root)
+ }
+
+ private fun addToRoot(vararg layoutNodes: LayoutNode) {
+ layoutNodes.forEachIndexed { index, node ->
+ testOwner.root.insertAt(index, node)
+ }
+ testOwner.measureAndLayout()
}
@Test
@@ -110,7 +116,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val offset = Offset(100f, 200f)
val previousEvents = mutableListOf<PointerInputEventData>()
@@ -173,7 +179,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val offset = Offset(100f, 200f)
val offset2 = Offset(300f, 400f)
@@ -230,7 +236,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val offsets = arrayOf(
Offset(100f, 200f),
@@ -296,7 +302,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val offsets = arrayOf(
Offset(99f, 200f),
@@ -372,7 +378,7 @@
).apply {
insertAt(0, middleLayoutNode)
}
- root.insertAt(0, parentLayoutNode)
+ addToRoot(parentLayoutNode)
val offset = when (numberOfChildrenHit) {
3 -> Offset(250f, 250f)
@@ -466,7 +472,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val down = PointerInputEvent(
0,
@@ -587,7 +593,7 @@
testOwner.position = IntOffset(aOX, aOY)
- root.insertAt(0, parentLayoutNode)
+ addToRoot(parentLayoutNode)
val additionalOffset = IntOffset(aOX, aOY)
@@ -752,10 +758,7 @@
childPointerInputFilter2
)
)
- root.apply {
- insertAt(0, childLayoutNode1)
- insertAt(0, childLayoutNode2)
- }
+ addToRoot(childLayoutNode1, childLayoutNode2)
val offset1 = Offset(25f, 25f)
val offset2 = Offset(75f, 75f)
@@ -904,11 +907,7 @@
)
)
- root.apply {
- insertAt(0, childLayoutNode1)
- insertAt(1, childLayoutNode2)
- insertAt(2, childLayoutNode3)
- }
+ addToRoot(childLayoutNode1, childLayoutNode2, childLayoutNode3)
val offset1 = Offset(25f, 25f)
val offset2 = Offset(75f, 75f)
@@ -1084,10 +1083,7 @@
)
)
- root.apply {
- insertAt(0, childLayoutNode1)
- insertAt(1, childLayoutNode2)
- }
+ addToRoot(childLayoutNode1, childLayoutNode2)
val offset1 = Offset(50f, 25f)
val offset2 = Offset(50f, 75f)
@@ -1204,10 +1200,7 @@
)
)
- root.apply {
- insertAt(0, childLayoutNode1)
- insertAt(1, childLayoutNode2)
- }
+ addToRoot(childLayoutNode1, childLayoutNode2)
val offset1 = Offset(25f, 50f)
val offset2 = Offset(75f, 50f)
@@ -1351,9 +1344,8 @@
insertAt(2, layoutNodeBottomLeft)
insertAt(3, layoutNodeBottomRight)
}
- root.apply {
- insertAt(0, parentLayoutNode)
- }
+ addToRoot(parentLayoutNode)
+
val offsetsTopLeft =
listOf(
Offset(0f, 1f),
@@ -1540,9 +1532,7 @@
singlePointerInputFilter
)
)
- root.apply {
- insertAt(0, layoutNode)
- }
+ addToRoot(layoutNode)
val offsetsThatHit =
listOf(
Offset(2f, 2f),
@@ -1616,9 +1606,7 @@
modifier
)
- root.apply {
- insertAt(0, layoutNode)
- }
+ addToRoot(layoutNode)
val offset1 = Offset(50f, 75f)
@@ -1698,9 +1686,7 @@
val layoutNode4: LayoutNode = LayoutNode(4, 8, 500, 500).apply {
insertAt(0, layoutNode3)
}
- root.apply {
- insertAt(0, layoutNode4)
- }
+ addToRoot(layoutNode4)
val offset1 = Offset(499f, 499f)
@@ -1776,9 +1762,7 @@
val layoutNode5: LayoutNode = LayoutNode(5, 10, 500, 500).apply {
insertAt(0, layoutNode4)
}
- root.apply {
- insertAt(0, layoutNode5)
- }
+ addToRoot(layoutNode5)
val offset1 = Offset(499f, 499f)
@@ -1884,10 +1868,7 @@
)
)
- root.apply {
- insertAt(0, layoutNode1)
- insertAt(1, layoutNode2)
- }
+ addToRoot(layoutNode1, layoutNode2)
val down = PointerInputEvent(
1, 0, Offset(50f, 50f), true
@@ -1912,9 +1893,7 @@
PointerInputModifierImpl2(pointerInputFilter1)
)
- root.apply {
- insertAt(0, layoutNode1)
- }
+ addToRoot(layoutNode1)
val down = PointerInputEvent(
1, 0, Offset(0f, 0f), true
@@ -1946,7 +1925,7 @@
PointerInputModifierImpl2(pointerInputFilter)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val pointerInputEvent =
PointerInputEvent(
@@ -2005,7 +1984,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val pointerInputEvent1 =
PointerInputEvent(
@@ -2123,8 +2102,7 @@
PointerInputModifierImpl2(pointerInputFilter2)
)
- root.insertAt(0, layoutNode1)
- root.insertAt(1, layoutNode2)
+ addToRoot(layoutNode1, layoutNode2)
val pointerInputEventData1 =
PointerInputEventData(
@@ -2217,7 +2195,7 @@
PointerInputModifierImpl2(pointerInputFilter)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val down =
PointerInputEvent(
@@ -2304,7 +2282,7 @@
PointerInputModifierImpl2(pointerInputFilter)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val down =
PointerInputEvent(
@@ -2364,7 +2342,7 @@
)
)
- root.insertAt(0, layoutNode)
+ addToRoot(layoutNode)
val down1 =
PointerInputEvent(
@@ -2460,7 +2438,7 @@
insertAt(0, childLayoutNode)
}
- root.insertAt(0, parentLayoutNode)
+ addToRoot(parentLayoutNode)
val offset = Offset(50f, 50f)
@@ -2575,7 +2553,7 @@
insertAt(0, childLayoutNode)
}
- root.insertAt(0, parentLayoutNode)
+ addToRoot(parentLayoutNode)
val down =
PointerInputEvent(0, 7, Offset(50f, 50f), true)
@@ -2616,7 +2594,7 @@
insertAt(0, childLayoutNode)
}
- root.insertAt(0, parentLayoutNode)
+ addToRoot(parentLayoutNode)
val offset = Offset(50f, 50f)
@@ -2731,7 +2709,7 @@
insertAt(0, childLayoutNode)
}
- root.insertAt(0, parentLayoutNode)
+ addToRoot(parentLayoutNode)
val down =
PointerInputEvent(0, 7, Offset(50f, 50f), true)
@@ -2779,9 +2757,7 @@
)
)
- root.apply {
- insertAt(0, layoutNode)
- }
+ addToRoot(layoutNode)
val offsets =
listOf(
@@ -2824,7 +2800,7 @@
pointerInputFilter
)
)
- root.apply { insertAt(0, layoutNode) }
+ addToRoot(layoutNode)
val pointerInputEvent =
PointerInputEvent(0, 11, Offset(0f, 0f), true)
@@ -2854,14 +2830,14 @@
pointerInputFilter
)
)
- root.apply { insertAt(0, layoutNode) }
+ addToRoot(layoutNode)
val down = PointerInputEvent(0, 11, Offset(0f, 0f), true)
pointerInputEventProcessor.process(down)
val move = PointerInputEvent(0, 11, Offset(1f, 0f), true)
// Act
- root.removeAt(0, 1)
+ testOwner.root.removeAt(0, 1)
val result = pointerInputEventProcessor.process(move)
// Assert
@@ -2886,7 +2862,7 @@
pointerInputFilter
)
)
- root.apply { insertAt(0, layoutNode) }
+ addToRoot(layoutNode)
val down = PointerInputEvent(0, 11, Offset(0f, 0f), true)
pointerInputEventProcessor.process(down)
val move = PointerInputEvent(0, 11, Offset(1f, 0f), true)
@@ -2927,7 +2903,7 @@
pointerInputFilter
)
)
- root.apply { insertAt(0, layoutNode) }
+ addToRoot(layoutNode)
val down = PointerInputEvent(0, 11, Offset(0f, 0f), true)
pointerInputEventProcessor.process(down)
val move = PointerInputEvent(0, 11, Offset(1f, 0f), true)
@@ -2987,59 +2963,45 @@
}
}
-abstract class TestOwner : Owner {
- var position: IntOffset? = null
-
- override val root: LayoutNode
- get() = LayoutNode()
-
- override fun calculatePosition(): IntOffset {
- return position ?: IntOffset.Zero
- }
-}
-
private class PointerInputModifierImpl2(override val pointerInputFilter: PointerInputFilter) :
PointerInputModifier
private fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
LayoutNode().apply {
- this.modifier = modifier
+ this.modifier = Modifier.layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ placeable.place(x, y)
+ }
+ }.then(modifier)
measureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks("not supported") {
override fun measure(
measureScope: MeasureScope,
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult =
- measureScope.layout(x2 - x, y2 - y) {}
+ measureScope.layout(x2 - x, y2 - y) {
+ measurables.forEach { it.measure(constraints).place(0, 0) }
+ }
}
- attach(mockOwner())
- layoutState = LayoutNode.LayoutState.NeedsRemeasure
- remeasure(Constraints())
- var wrapper: LayoutNodeWrapper? = outerLayoutNodeWrapper
- while (wrapper != null) {
- wrapper.measureResult = innerLayoutNodeWrapper.measureResult
- wrapper = (wrapper as? LayoutNodeWrapper)?.wrapped
- }
- place(x, y)
- detach()
}
-private fun mockOwner(
- position: IntOffset = IntOffset.Zero,
- targetRoot: LayoutNode = LayoutNode()
-): Owner = MockOwner(position, targetRoot)
-
@OptIn(ExperimentalComposeUiApi::class, InternalCoreApi::class)
-private class MockOwner(
- private val position: IntOffset,
- private val targetRoot: LayoutNode
-) : Owner {
+private class TestOwner : Owner {
+ var position: IntOffset = IntOffset.Zero
+ override val root = LayoutNode(0, 0, 500, 500)
+
+ private val delegate = MeasureAndLayoutDelegate(root)
+
+ init {
+ root.attach(this)
+ delegate.updateRootConstraints(Constraints(maxWidth = 500, maxHeight = 500))
+ }
+
override fun calculatePosition(): IntOffset = position
override fun calculatePositionInWindow(): IntOffset = position
override fun requestFocus(): Boolean = false
- override val root: LayoutNode
- get() = targetRoot
override val rootForTest: RootForTest
get() = TODO("Not yet implemented")
override val hapticFeedBack: HapticFeedback
@@ -3069,9 +3031,11 @@
set(@Suppress("UNUSED_PARAMETER") value) {}
override fun onRequestMeasure(layoutNode: LayoutNode) {
+ delegate.requestRemeasure(layoutNode)
}
override fun onRequestRelayout(layoutNode: LayoutNode) {
+ delegate.requestRelayout(layoutNode)
}
override fun onAttach(node: LayoutNode) {
@@ -3081,6 +3045,7 @@
}
override fun measureAndLayout() {
+ delegate.measureAndLayout()
}
override fun createLayer(
diff --git a/compose/runtime/runtime-dispatch/src/androidAndroidTest/kotlin/androidx/compose/runtime/dispatch/AndroidUiDispatcherTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt
similarity index 80%
rename from compose/runtime/runtime-dispatch/src/androidAndroidTest/kotlin/androidx/compose/runtime/dispatch/AndroidUiDispatcherTest.kt
rename to compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt
index a46885d..50a283b 100644
--- a/compose/runtime/runtime-dispatch/src/androidAndroidTest/kotlin/androidx/compose/runtime/dispatch/AndroidUiDispatcherTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package androidx.compose.runtime.dispatch
+package androidx.compose.ui.platform
import android.graphics.Rect
import android.view.Choreographer
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup.LayoutParams
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.runtime.withFrameNanos
import androidx.core.view.doOnLayout
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -31,9 +34,10 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.withTimeoutOrNull
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNotNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Rule
@@ -44,7 +48,7 @@
@RunWith(AndroidJUnit4::class)
class AndroidUiDispatcherTest {
@get:Rule
- val rule = activityScenarioRule<TestActivity>()
+ val rule = activityScenarioRule<AppCompatActivity>()
@Test
fun currentThreadIsMainOnMainThread() = runBlocking(Dispatchers.Main) {
@@ -133,28 +137,32 @@
// in the same frame if the resume was triggered by the input event.
val rect = layoutRect.await()
swipe(rect.left + 1, rect.top + 1, rect.right - 1, rect.bottom - 1, 30)
+ waitForIdle()
}
- withTimeout(3_000) {
- val viewTouched = viewTouchedOnFrame.await()
- val inputJob = ranInputJobOnFrame.await()
- assertNotEquals(0, viewTouched)
- assertNotEquals(0, inputJob)
- assertEquals(
- "touch and launched job resume happened on same frame",
- viewTouched,
- inputJob
- )
- assertEquals(
- "withFrame ran on the same frame where it was called",
- inputJob,
- withFrameOnFrame.await()
- )
- assertEquals(
- "second withFrame call was invoked on the very next frame",
- inputJob + 1,
- withFrameSecondCall.await()
- )
- }
+ assertNotNull(
+ "Timeout exceeded waiting for response to input events",
+ withTimeoutOrNull(5_000) {
+ val viewTouched = viewTouchedOnFrame.await()
+ val inputJob = ranInputJobOnFrame.await()
+ assertNotEquals(0, viewTouched)
+ assertNotEquals(0, inputJob)
+ assertEquals(
+ "touch and launched job resume happened on same frame",
+ viewTouched,
+ inputJob
+ )
+ assertEquals(
+ "withFrame ran on the same frame where it was called",
+ inputJob,
+ withFrameOnFrame.await()
+ )
+ assertEquals(
+ "second withFrame call was invoked on the very next frame",
+ inputJob + 1,
+ withFrameSecondCall.await()
+ )
+ }
+ )
}
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
index fd51969..26d6a75 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
@@ -21,7 +21,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Recomposer
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.background
diff --git a/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/AndroidUiDispatcher.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.kt
similarity index 95%
rename from compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/AndroidUiDispatcher.kt
rename to compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.kt
index 2141b1d..e72b4a2 100644
--- a/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/AndroidUiDispatcher.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package androidx.compose.runtime.dispatch
+package androidx.compose.ui.platform
import android.os.Looper
import android.view.Choreographer
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher.Companion.CurrentThread
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher.Companion.Main
+import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.ui.platform.AndroidUiDispatcher.Companion.CurrentThread
+import androidx.compose.ui.platform.AndroidUiDispatcher.Companion.Main
import androidx.core.os.HandlerCompat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
diff --git a/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/AndroidUiFrameClock.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.kt
similarity index 93%
rename from compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/AndroidUiFrameClock.kt
rename to compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.kt
index 71318f4..fbc7b21 100644
--- a/compose/runtime/runtime-dispatch/src/androidMain/kotlin/androidx/compose/runtime/dispatch/AndroidUiFrameClock.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.compose.runtime.dispatch
+package androidx.compose.ui.platform
import android.view.Choreographer
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -23,7 +23,7 @@
class AndroidUiFrameClock(
val choreographer: Choreographer
-) : MonotonicFrameClock {
+) : androidx.compose.runtime.MonotonicFrameClock {
override suspend fun <R> withFrameNanos(
onFrame: (Long) -> R
): R {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.kt
index e869b2a..d702ebf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.kt
@@ -17,7 +17,6 @@
package androidx.compose.ui.platform
import androidx.compose.runtime.ExperimentalComposeApi
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.SnapshotWriteObserver
import androidx.compose.ui.platform.GlobalSnapshotManager.ensureStarted
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt
index b865bd9..c90661e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt
@@ -21,8 +21,7 @@
import androidx.compose.runtime.CompositionReference
import androidx.compose.runtime.PausableMonotonicFrameClock
import androidx.compose.runtime.Recomposer
-import androidx.compose.runtime.dispatch.AndroidUiDispatcher
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.R
import androidx.lifecycle.Lifecycle
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
index 0414d6a..f496339 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
@@ -146,8 +146,12 @@
// not add PointerInputFilters on different paths so we should not even go looking.
val originalSize = hitPointerInputFilters.size
layoutNode.zSortedChildren.reversedAny { child ->
- callHitTest(child, pointerPositionRelativeToScreen, hitPointerInputFilters)
- hitPointerInputFilters.size > originalSize
+ if (child.isPlaced) {
+ callHitTest(child, pointerPositionRelativeToScreen, hitPointerInputFilters)
+ hitPointerInputFilters.size > originalSize
+ } else {
+ false
+ }
}
}
}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 3cdf0e5..772faee 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -18,23 +18,37 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionReference
-import androidx.compose.runtime.EmbeddingContext
+import androidx.compose.runtime.DefaultMonotonicFrameClock
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.Providers
import androidx.compose.runtime.Recomposer
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.node.LayoutNode
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
+import kotlinx.coroutines.swing.Swing
+import javax.swing.SwingUtilities
+
+object SwingEmbeddingContext {
+ fun isMainThread(): Boolean {
+ return SwingUtilities.isEventDispatchThread()
+ }
+
+ fun mainThreadCompositionContext(): CoroutineContext {
+ return Dispatchers.Swing + DefaultMonotonicFrameClock
+ }
+}
// TODO: Replace usages with an appropriately scoped implementation
// Below is a local copy of the old Recomposer.current() implementation.
@OptIn(ExperimentalCoroutinesApi::class)
private val GlobalDefaultRecomposer = run {
- val embeddingContext = EmbeddingContext()
+ val embeddingContext = SwingEmbeddingContext
val mainScope = CoroutineScope(
NonCancellable + embeddingContext.mainThreadCompositionContext()
)
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt
index 679e9e9..417f745 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt
@@ -25,7 +25,7 @@
import androidx.compose.runtime.RecomposeScope
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.currentRecomposeScope
-import androidx.compose.runtime.dispatch.MonotonicFrameClock
+import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.remember
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.channels.Channel
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index 4f34aef..e31400b8 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -32,6 +32,8 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.input.pointer.PointerInputModifier
import androidx.compose.ui.layout.LayoutModifier
@@ -54,7 +56,6 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.zIndex
import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.spy
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -794,7 +795,7 @@
@Test
fun hitTest_pointerInBounds_pointerInputFilterHit() {
- val pointerInputFilter: PointerInputFilter = spy()
+ val pointerInputFilter: PointerInputFilter = mockPointerInputFilter()
val layoutNode =
LayoutNode(
0, 0, 1, 1,
@@ -811,7 +812,7 @@
@Test
fun hitTest_pointerOutOfBounds_nothingHit() {
- val pointerInputFilter: PointerInputFilter = spy()
+ val pointerInputFilter: PointerInputFilter = mockPointerInputFilter()
val layoutNode =
LayoutNode(
0, 0, 1, 1,
@@ -854,9 +855,9 @@
private fun hitTest_nestedOffsetNodes_allHitInCorrectOrder(numberOfChildrenHit: Int) {
// Arrange
- val childPointerInputFilter: PointerInputFilter = spy()
- val middlePointerInputFilter: PointerInputFilter = spy()
- val parentPointerInputFilter: PointerInputFilter = spy()
+ val childPointerInputFilter: PointerInputFilter = mockPointerInputFilter()
+ val middlePointerInputFilter: PointerInputFilter = mockPointerInputFilter()
+ val parentPointerInputFilter: PointerInputFilter = mockPointerInputFilter()
val childLayoutNode =
LayoutNode(
@@ -884,6 +885,8 @@
insertAt(0, middleLayoutNode)
attach(MockOwner())
}
+ middleLayoutNode.onNodePlaced()
+ childLayoutNode.onNodePlaced()
val offset = when (numberOfChildrenHit) {
3 -> Offset(250f, 250f)
@@ -952,8 +955,8 @@
// Arrange
- val childPointerInputFilter1: PointerInputFilter = spy()
- val childPointerInputFilter2: PointerInputFilter = spy()
+ val childPointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val childPointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
val childLayoutNode1 =
LayoutNode(
@@ -976,6 +979,8 @@
insertAt(1, childLayoutNode2)
attach(MockOwner())
}
+ childLayoutNode1.onNodePlaced()
+ childLayoutNode2.onNodePlaced()
val offset1 = Offset(25f, 25f)
val offset2 = Offset(75f, 75f)
@@ -1019,9 +1024,9 @@
@Test
fun hitTest_3DownOnOverlappingPointerInputModifiers_resultIsCorrect() {
- val childPointerInputFilter1: PointerInputFilter = spy()
- val childPointerInputFilter2: PointerInputFilter = spy()
- val childPointerInputFilter3: PointerInputFilter = spy()
+ val childPointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val childPointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
+ val childPointerInputFilter3: PointerInputFilter = mockPointerInputFilter()
val childLayoutNode1 =
LayoutNode(
@@ -1053,6 +1058,9 @@
insertAt(2, childLayoutNode3)
attach(MockOwner())
}
+ childLayoutNode1.onNodePlaced()
+ childLayoutNode2.onNodePlaced()
+ childLayoutNode3.onNodePlaced()
val offset1 = Offset(25f, 25f)
val offset2 = Offset(75f, 75f)
@@ -1094,8 +1102,8 @@
@Test
fun hitTest_3DownOnFloatingPointerInputModifierV_resultIsCorrect() {
- val childPointerInputFilter1: PointerInputFilter = spy()
- val childPointerInputFilter2: PointerInputFilter = spy()
+ val childPointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val childPointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
val childLayoutNode1 = LayoutNode(
0, 0, 100, 150,
@@ -1115,6 +1123,8 @@
insertAt(1, childLayoutNode2)
attach(MockOwner())
}
+ childLayoutNode1.onNodePlaced()
+ childLayoutNode2.onNodePlaced()
val offset1 = Offset(50f, 25f)
val offset2 = Offset(50f, 75f)
@@ -1156,8 +1166,8 @@
@Test
fun hitTest_3DownOnFloatingPointerInputModifierH_resultIsCorrect() {
- val childPointerInputFilter1: PointerInputFilter = spy()
- val childPointerInputFilter2: PointerInputFilter = spy()
+ val childPointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val childPointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
val childLayoutNode1 = LayoutNode(
0, 0, 150, 100,
@@ -1177,6 +1187,8 @@
insertAt(1, childLayoutNode2)
attach(MockOwner())
}
+ childLayoutNode2.onNodePlaced()
+ childLayoutNode1.onNodePlaced()
val offset1 = Offset(25f, 50f)
val offset2 = Offset(75f, 50f)
@@ -1226,10 +1238,10 @@
// Arrange
- val pointerInputFilter1: PointerInputFilter = spy()
- val pointerInputFilter2: PointerInputFilter = spy()
- val pointerInputFilter3: PointerInputFilter = spy()
- val pointerInputFilter4: PointerInputFilter = spy()
+ val pointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter3: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter4: PointerInputFilter = mockPointerInputFilter()
val layoutNode1 = LayoutNode(
-1, -1, 1, 1,
@@ -1263,6 +1275,10 @@
insertAt(3, layoutNode4)
attach(MockOwner())
}
+ layoutNode1.onNodePlaced()
+ layoutNode2.onNodePlaced()
+ layoutNode3.onNodePlaced()
+ layoutNode4.onNodePlaced()
val offsetsThatHit1 =
listOf(
@@ -1346,7 +1362,7 @@
// Arrange
- val pointerInputFilter: PointerInputFilter = spy()
+ val pointerInputFilter: PointerInputFilter = mockPointerInputFilter()
val layoutNode = LayoutNode(
0, 0, 2, 2,
@@ -1396,9 +1412,9 @@
// Arrange.
- val pointerInputFilter1: PointerInputFilter = spy()
- val pointerInputFilter2: PointerInputFilter = spy()
- val pointerInputFilter3: PointerInputFilter = spy()
+ val pointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter3: PointerInputFilter = mockPointerInputFilter()
val modifier =
PointerInputModifierImpl(
@@ -1440,7 +1456,7 @@
// Arrange.
- val pointerInputFilter: PointerInputFilter = spy()
+ val pointerInputFilter: PointerInputFilter = mockPointerInputFilter()
val layoutNode1 =
LayoutNode(
@@ -1460,6 +1476,9 @@
}.apply {
attach(MockOwner())
}
+ layoutNode3.onNodePlaced()
+ layoutNode2.onNodePlaced()
+ layoutNode1.onNodePlaced()
val offset1 = Offset(499f, 499f)
val hit = mutableListOf<PointerInputFilter>()
@@ -1478,10 +1497,10 @@
// Arrange.
- val pointerInputFilter1: PointerInputFilter = spy()
- val pointerInputFilter2: PointerInputFilter = spy()
- val pointerInputFilter3: PointerInputFilter = spy()
- val pointerInputFilter4: PointerInputFilter = spy()
+ val pointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter3: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter4: PointerInputFilter = mockPointerInputFilter()
val layoutNode1 = LayoutNode(
1, 6, 500, 500,
@@ -1514,6 +1533,10 @@
}.apply {
attach(MockOwner())
}
+ layoutNode4.onNodePlaced()
+ layoutNode3.onNodePlaced()
+ layoutNode2.onNodePlaced()
+ layoutNode1.onNodePlaced()
val offset1 = Offset(499f, 499f)
@@ -1538,8 +1561,8 @@
@Test
fun hitTest_pointerOnFullyOverlappingPointerInputModifiers_onlyTopPimIsHit() {
- val pointerInputFilter1: PointerInputFilter = spy()
- val pointerInputFilter2: PointerInputFilter = spy()
+ val pointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
val layoutNode1 = LayoutNode(
0, 0, 100, 100,
@@ -1559,6 +1582,8 @@
insertAt(1, layoutNode2)
attach(MockOwner())
}
+ layoutNode1.onNodePlaced()
+ layoutNode2.onNodePlaced()
val offset = Offset(50f, 50f)
@@ -1576,7 +1601,7 @@
@Test
fun hitTest_pointerOnPointerInputModifierInLayoutNodeWithNoSize_nothingHit() {
- val pointerInputFilter: PointerInputFilter = spy()
+ val pointerInputFilter: PointerInputFilter = mockPointerInputFilter()
val layoutNode = LayoutNode(
0, 0, 0, 0,
@@ -1603,8 +1628,8 @@
@Test
fun hitTest_zIndexIsAccounted() {
- val pointerInputFilter1: PointerInputFilter = spy()
- val pointerInputFilter2: PointerInputFilter = spy()
+ val pointerInputFilter1: PointerInputFilter = mockPointerInputFilter()
+ val pointerInputFilter2: PointerInputFilter = mockPointerInputFilter()
val parent = LayoutNode(
0, 0, 2, 2
@@ -1869,7 +1894,7 @@
get() = TODO("Not yet implemented")
}
-fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
+private fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
LayoutNode().apply {
this.modifier = modifier
measureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks("not supported") {
@@ -1893,3 +1918,15 @@
place(x, y)
detach()
}
+
+private fun mockPointerInputFilter(): PointerInputFilter = object : PointerInputFilter() {
+ override fun onPointerEvent(
+ pointerEvent: PointerEvent,
+ pass: PointerEventPass,
+ bounds: IntSize
+ ) {
+ }
+
+ override fun onCancel() {
+ }
+}
\ No newline at end of file
diff --git a/development/project-creator/create_project.py b/development/project-creator/create_project.py
index dc331f8..8ad4d34 100755
--- a/development/project-creator/create_project.py
+++ b/development/project-creator/create_project.py
@@ -77,7 +77,7 @@
os.remove(path)
def mv_dir(src_path_dir, dst_path_dir):
- """Moves a direcotry from src_path_dir to dst_path_dir.
+ """Moves a directory from src_path_dir to dst_path_dir.
Args:
src_path_dir: the source directory, which must exist
@@ -87,16 +87,15 @@
print_e('rename error: Destination path %s already exists.' % dst_path_dir)
return None
# If moving to a new parent directory, create that directory
- final_dir_length = len("/" + dst_path_dir.split('/')[-1])
- parent_dst_path_dir = dst_path_dir[:-final_dir_length]
+ parent_dst_path_dir = os.path.dirname(dst_path_dir)
if not os.path.exists(parent_dst_path_dir):
os.makedirs(parent_dst_path_dir)
if not os.path.exists(src_path_dir):
print_e('mv error: Source path %s does not exist.' % src_path_dir)
return None
- try :
- os.rename(src_path_dir, dst_path_dir)
- except OSError as error:
+ try:
+ os.rename(src_path_dir, dst_path_dir)
+ except OSError as error:
print_e('FAIL: Unable to copy %s to destination %s' % (src_path_dir, dst_path_dir))
print_e(error)
return None
@@ -234,12 +233,12 @@
sed("<YEAR>", year, full_artifact_path + "/build.gradle")
sed("<YEAR>", year, full_artifact_path + "/src/androidTest/AndroidManifest.xml")
sed("<YEAR>", year, full_artifact_path + "/src/main/AndroidManifest.xml")
- sed("0000", year, full_package_info_path)
+ sed("<YEAR>", year, full_package_info_path)
# Populate the PACKAGE
package = generate_package_name(group_id, artifact_id)
sed("<PACKAGE>", package, full_artifact_path + "/src/androidTest/AndroidManifest.xml")
sed("<PACKAGE>", package, full_artifact_path + "/src/main/AndroidManifest.xml")
- sed("placeholder.package", package, full_package_info_path)
+ sed("<PACKAGE>", package, full_package_info_path)
# Populate the VERSION macro
group_id_version_macro = get_group_id_version_macro(group_id)
sed("<GROUPID>", group_id_version_macro, full_artifact_path + "/build.gradle")
diff --git a/development/project-creator/groupId/artifactId/src/main/groupId/package-info.java b/development/project-creator/groupId/artifactId/src/main/groupId/package-info.java
index 708f84a..a51e68c 100644
--- a/development/project-creator/groupId/artifactId/src/main/groupId/package-info.java
+++ b/development/project-creator/groupId/artifactId/src/main/groupId/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 0000 The Android Open Source Project
+ * Copyright (C) <YEAR> 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.
@@ -17,4 +17,4 @@
/**
* Insert package level documentation here
*/
-package placeholder.package;
+package <PACKAGE>;
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
index 9b1fcb7..fd7b5bb 100644
--- a/docs/api_guidelines.md
+++ b/docs/api_guidelines.md
@@ -60,19 +60,20 @@
scenario:
* `androidx.library:1.0.0`
- * contains classes `androidx.library.A` and `androidx.library.util.B`
+ * contains class `androidx.library.A`
+ * contains class `androidx.library.util.B`
This module is split, moving `androidx.library.util.B` to a new module:
* `androidx.library:1.1.0`
* contains class `androidx.library.A`
- * depends on `androidx.library.util:1.0.0`
-* `androidx.library.util:1.0.0`
- * depends on `androidx.library.util.B`
+ * depends on `androidx.library.util:1.1.0`
+* `androidx.library.util:1.1.0`
+ * contains class `androidx.library.util.B`
-A developer writes an app that depends directly on `androidx.library.util:1.0.0`
-and transitively pulls in `androidx.library:1.0.0`. Their app will no longer
-compile due to class duplication of `androidx.library.util.B`.
+A developer writes an app that depends directly on `androidx.library.util:1.1.0`
+and also transitively pulls in `androidx.library:1.0.0`. Their app will no
+longer compile due to class duplication of `androidx.library.util.B`.
While it is possible for the developer to fix this by manually specifying a
dependency on `androidx.library:1.1.0`, there is no easy way for the developer
@@ -960,9 +961,16 @@
### Kotlin {#dependencies-kotlin}
-Kotlin is _recommended_ for new libraries; however, it's important to consider
-its size impact on clients. Currently, the Kotlin stdlib adds a minimum of 40kB
-post-optimization.
+Kotlin is _strongly recommended_ for new libraries; however, it's important to
+consider its size impact on clients. Currently, the Kotlin stdlib adds a minimum
+of 40kB post-optimization. It may not make sense to use Kotlin for a library
+that targets Java-only clients or space-constrained (ex. Android Go) clients.
+
+Existing Java-based libraries are _strongly discouraged_ from using Kotlin,
+primarily because our documentation system does not currently provide a
+Java-facing version of Kotlin API reference docs. Java-based libraries _may_
+migrate to Kotlin, but they must consider the docs usability and size impacts on
+existing Java-only and space-constrained clients.
### Kotlin coroutines {#dependencies-coroutines}
diff --git a/docs/policies.md b/docs/policies.md
index f42d6dc..0b790f5 100644
--- a/docs/policies.md
+++ b/docs/policies.md
@@ -21,10 +21,10 @@
```
<feature-name>/
<feature-name>-<sub-feature>/ [<feature-name>:<feature-name>-<sub-feature>]
+ samples/ [<feature-name>:<feature-name>-<sub-feature>:samples]
integration-tests/
testapp/ [<feature-name>:testapp]
testlib/ [<feature-name>:testlib]
- samples/ [<feature-name>:samples]
```
For example, the `navigation` library group's directory structure is:
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
index b10b84b..4fa04be 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
@@ -73,12 +73,14 @@
fun Project.registerDexInspectorTask(
variant: BaseVariant,
extension: BaseExtension,
+ jarName: String?,
jar: TaskProvider<out Jar>
): TaskProvider<DexInspectorTask> {
return tasks.register(variant.taskName("dexInspector"), DexInspectorTask::class.java) {
it.setDx(extension.sdkDirectory, extension.buildToolsVersion)
it.jars.from(jar.get().destinationDirectory)
- val out = File(taskWorkingDir(variant, "dexedInspector"), "${project.name}.jar")
+ val name = jarName ?: "${project.name}.jar"
+ val out = File(taskWorkingDir(variant, "dexedInspector"), name)
it.outputFile.set(out)
it.dependsOn(jar)
}
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
index 81df245..9ee34da 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
@@ -27,6 +27,7 @@
import org.gradle.api.tasks.StopExecutionException
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.apply
+import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getPlugin
import java.io.File
@@ -44,6 +45,7 @@
override fun apply(project: Project) {
var foundLibraryPlugin = false
var foundReleaseVariant = false
+ val extension = project.extensions.create<InspectionExtension>(EXTENSION_NAME, project)
project.pluginManager.withPlugin("com.android.library") {
foundLibraryPlugin = true
val libExtension = project.extensions.getByType(LibraryExtension::class.java)
@@ -53,7 +55,9 @@
foundReleaseVariant = true
val unzip = project.registerUnzipTask(variant)
val shadowJar = project.registerShadowDependenciesTask(variant, unzip)
- dexTask = project.registerDexInspectorTask(variant, libExtension, shadowJar)
+ dexTask = project.registerDexInspectorTask(
+ variant, libExtension, extension.name, shadowJar
+ )
}
}
libExtension.sourceSets.findByName("main")!!.resources.srcDirs(
@@ -141,4 +145,13 @@
libExtension.libraryVariants.all { variant ->
libraryProject.registerGenerateProguardDetectionFileTask(variant)
}
+}
+
+const val EXTENSION_NAME = "inspection"
+
+open class InspectionExtension(@Suppress("UNUSED_PARAMETER") project: Project) {
+ /**
+ * Name of built inspector artifact, if not provided it is equal to project's name.
+ */
+ var name: String? = null
}
\ No newline at end of file
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
index 853628b..9f1a0fb 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
@@ -40,6 +40,12 @@
val fileTree = project.fileTree(zipTask.get().destinationDir)
fileTree.include("**/*.jar", "**/*.so")
it.from(fileTree)
+ it.includeEmptyDirs = false
+ it.filesMatching("**/*.so") {
+ if (it.path.startsWith("jni")) {
+ it.path = "lib/${it.path.removePrefix("jni")}"
+ }
+ }
it.destinationDirectory.set(taskWorkingDir(variant, "shadowedJar"))
it.archiveBaseName.set("${project.name}-shadowed")
it.dependsOn(zipTask)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.java
deleted file mode 100644
index 84628e6..0000000
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.java
+++ /dev/null
@@ -1,94 +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.navigation;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.lifecycle.ViewModelStore;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.UUID;
-
-/**
- * NavControllerViewModel is the always up to date view of the NavController's
- * non configuration state
- */
-class NavControllerViewModel extends ViewModel {
-
- private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {
- @NonNull
- @Override
- @SuppressWarnings("unchecked")
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
- NavControllerViewModel viewModel = new NavControllerViewModel();
- return (T) viewModel;
- }
- };
-
- @NonNull
- static NavControllerViewModel getInstance(ViewModelStore viewModelStore) {
- ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore, FACTORY);
- return viewModelProvider.get(NavControllerViewModel.class);
- }
-
- private final HashMap<UUID, ViewModelStore> mViewModelStores = new HashMap<>();
-
- void clear(@NonNull UUID backStackEntryUUID) {
- // Clear and remove the NavGraph's ViewModelStore
- ViewModelStore viewModelStore = mViewModelStores.remove(backStackEntryUUID);
- if (viewModelStore != null) {
- viewModelStore.clear();
- }
- }
-
- @Override
- protected void onCleared() {
- for (ViewModelStore store: mViewModelStores.values()) {
- store.clear();
- }
- mViewModelStores.clear();
- }
-
- @NonNull
- ViewModelStore getViewModelStore(@NonNull UUID backStackEntryUUID) {
- ViewModelStore viewModelStore = mViewModelStores.get(backStackEntryUUID);
- if (viewModelStore == null) {
- viewModelStore = new ViewModelStore();
- mViewModelStores.put(backStackEntryUUID, viewModelStore);
- }
- return viewModelStore;
- }
-
- @NonNull
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("NavControllerViewModel{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append("} ViewModelStores (");
- Iterator<UUID> viewModelStoreIterator = mViewModelStores.keySet().iterator();
- while (viewModelStoreIterator.hasNext()) {
- sb.append(viewModelStoreIterator.next());
- if (viewModelStoreIterator.hasNext()) {
- sb.append(", ");
- }
- }
- sb.append(')');
- return sb.toString();
- }
-}
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
new file mode 100644
index 0000000..01667f6
--- /dev/null
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.navigation
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.get
+import java.util.UUID
+
+/**
+ * NavControllerViewModel is the always up to date view of the NavController's
+ * non configuration state
+ */
+internal class NavControllerViewModel : ViewModel() {
+ private val viewModelStores = mutableMapOf<UUID, ViewModelStore>()
+
+ fun clear(backStackEntryUUID: UUID) {
+ // Clear and remove the NavGraph's ViewModelStore
+ val viewModelStore = viewModelStores.remove(backStackEntryUUID)
+ viewModelStore?.clear()
+ }
+
+ override fun onCleared() {
+ for (store in viewModelStores.values) {
+ store.clear()
+ }
+ viewModelStores.clear()
+ }
+
+ fun getViewModelStore(backStackEntryUUID: UUID): ViewModelStore {
+ var viewModelStore = viewModelStores[backStackEntryUUID]
+ if (viewModelStore == null) {
+ viewModelStore = ViewModelStore()
+ viewModelStores[backStackEntryUUID] = viewModelStore
+ }
+ return viewModelStore
+ }
+
+ override fun toString(): String {
+ val sb = StringBuilder("NavControllerViewModel{")
+ sb.append(Integer.toHexString(System.identityHashCode(this)))
+ sb.append("} ViewModelStores (")
+ val viewModelStoreIterator: Iterator<UUID> = viewModelStores.keys.iterator()
+ while (viewModelStoreIterator.hasNext()) {
+ sb.append(viewModelStoreIterator.next())
+ if (viewModelStoreIterator.hasNext()) {
+ sb.append(", ")
+ }
+ }
+ sb.append(')')
+ return sb.toString()
+ }
+
+ companion object {
+ private val FACTORY: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ return NavControllerViewModel() as T
+ }
+ }
+
+ @JvmStatic
+ fun getInstance(viewModelStore: ViewModelStore): NavControllerViewModel {
+ val viewModelProvider = ViewModelProvider(viewModelStore, FACTORY)
+ return viewModelProvider.get()
+ }
+ }
+}
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/ActionBarOnDestinationChangedListener.java b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/ActionBarOnDestinationChangedListener.java
deleted file mode 100644
index 18fce74..0000000
--- a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/ActionBarOnDestinationChangedListener.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.navigation.ui;
-
-import android.graphics.drawable.Drawable;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.ActionBarDrawerToggle;
-import androidx.appcompat.app.AppCompatActivity;
-
-/**
- * The OnDestinationChangedListener specifically for keeping the ActionBar updated.
- * This handles both updating the title and updating the Up Indicator, transitioning between
- * the drawer icon and up arrow as needed.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-class ActionBarOnDestinationChangedListener extends
- AbstractAppBarOnDestinationChangedListener {
- private final AppCompatActivity mActivity;
-
- ActionBarOnDestinationChangedListener(@NonNull AppCompatActivity activity,
- @NonNull AppBarConfiguration configuration) {
- super(activity.getDrawerToggleDelegate().getActionBarThemedContext(), configuration);
- mActivity = activity;
- }
-
- @Override
- protected void setTitle(CharSequence title) {
- ActionBar actionBar = mActivity.getSupportActionBar();
- actionBar.setTitle(title);
- }
-
- @Override
- protected void setNavigationIcon(Drawable icon,
- @StringRes int contentDescription) {
- ActionBar actionBar = mActivity.getSupportActionBar();
- if (icon == null) {
- actionBar.setDisplayHomeAsUpEnabled(false);
- } else {
- actionBar.setDisplayHomeAsUpEnabled(true);
- ActionBarDrawerToggle.Delegate delegate = mActivity.getDrawerToggleDelegate();
- delegate.setActionBarUpIndicator(icon, contentDescription);
- }
- }
-}
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/ActionBarOnDestinationChangedListener.kt b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/ActionBarOnDestinationChangedListener.kt
new file mode 100644
index 0000000..c23f619
--- /dev/null
+++ b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/ActionBarOnDestinationChangedListener.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.navigation.ui
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.RestrictTo
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * The OnDestinationChangedListener specifically for keeping the ActionBar updated.
+ * This handles both updating the title and updating the Up Indicator, transitioning between
+ * the drawer icon and up arrow as needed.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+internal class ActionBarOnDestinationChangedListener(
+ private val activity: AppCompatActivity,
+ configuration: AppBarConfiguration
+) : AbstractAppBarOnDestinationChangedListener(
+ activity.drawerToggleDelegate!!.actionBarThemedContext,
+ configuration
+) {
+ override fun setTitle(title: CharSequence) {
+ val actionBar = activity.supportActionBar
+ require(actionBar != null) {
+ "Activity $activity does not have an ActionBar set via setSupportActionBar()"
+ }
+ actionBar.setTitle(title)
+ }
+
+ override fun setNavigationIcon(icon: Drawable, @StringRes contentDescription: Int) {
+ val actionBar = activity.supportActionBar
+ require(actionBar != null) {
+ "Activity $activity does not have an ActionBar set via setSupportActionBar()"
+ }
+ actionBar.setDisplayHomeAsUpEnabled(true)
+ val delegate = activity.drawerToggleDelegate
+ require(delegate != null) {
+ "Activity $activity does not have an DrawerToggleDelegate set"
+ }
+ delegate.setActionBarUpIndicator(icon, contentDescription)
+ }
+}
diff --git a/wear/wear-tiles/build.gradle b/wear/wear-tiles/build.gradle
index fc0e747..bcfeda5 100644
--- a/wear/wear-tiles/build.gradle
+++ b/wear/wear-tiles/build.gradle
@@ -38,11 +38,20 @@
api(GUAVA_LISTENABLE_FUTURE)
implementation(PROTOBUF_LITE)
implementation 'androidx.annotation:annotation:1.2.0-alpha01'
+
+ testImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ testImplementation(ANDROIDX_TEST_EXT_TRUTH)
+ testImplementation(ANDROIDX_TEST_CORE)
+ testImplementation(ANDROIDX_TEST_RUNNER)
+ testImplementation(ANDROIDX_TEST_RULES)
+ testImplementation(ROBOLECTRIC)
+ testImplementation(MOCKITO_CORE)
}
android {
defaultConfig {
- minSdkVersion 24
+ minSdkVersion 26
+ targetSdkVersion 28
}
buildFeatures {
aidl = true
diff --git a/wear/wear-tiles/src/main/AndroidManifest.xml b/wear/wear-tiles/src/main/AndroidManifest.xml
index e26eded..262aeeb 100644
--- a/wear/wear-tiles/src/main/AndroidManifest.xml
+++ b/wear/wear-tiles/src/main/AndroidManifest.xml
@@ -1,4 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.wear.tiles">
-</manifest>
+<manifest package="androidx.wear.tiles" />
\ No newline at end of file
diff --git a/wear/wear-tiles/src/main/java/androidx/wear/tiles/ProtoParcelable.java b/wear/wear-tiles/src/main/java/androidx/wear/tiles/ProtoParcelable.java
index 06b437c..a99c172 100644
--- a/wear/wear-tiles/src/main/java/androidx/wear/tiles/ProtoParcelable.java
+++ b/wear/wear-tiles/src/main/java/androidx/wear/tiles/ProtoParcelable.java
@@ -106,6 +106,6 @@
@Override
public int hashCode() {
- return Arrays.hashCode(mContents);
+ return 31 * mVersion + Arrays.hashCode(mContents);
}
}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java
new file mode 100644
index 0000000..d5f1cea
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.wear.tiles.proto.RequestProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument // See http://g/robolectric-users/fTi2FRXgyGA/m/PkB0wYuwBgAJ
+public final class ProtoParcelableTest {
+ public static class Wrapper extends ProtoParcelable {
+ public static final int VERSION = 1;
+ public static final Creator<Wrapper> CREATOR = newCreator(Wrapper.class, Wrapper::new);
+
+ Wrapper(byte[] payload, int version) {
+ super(payload, version);
+ }
+ }
+
+ @Test
+ public void contentsEqualsAndHashCode() {
+ final Wrapper foo1 =
+ new Wrapper(RequestProto.ResourcesRequest.newBuilder().setVersion(
+ "foo").build().toByteArray(), Wrapper.VERSION);
+ final Wrapper foo2 =
+ new Wrapper(RequestProto.ResourcesRequest.newBuilder().setVersion(
+ "foo").build().toByteArray(), Wrapper.VERSION);
+ final Wrapper bar =
+ new Wrapper(RequestProto.ResourcesRequest.newBuilder().setVersion(
+ "bar").build().toByteArray(), Wrapper.VERSION);
+ assertThat(foo1).isEqualTo(foo2);
+ assertThat(foo1).isNotEqualTo(bar);
+ assertThat(foo1.hashCode()).isEqualTo(foo2.hashCode());
+ assertThat(foo1.hashCode()).isNotEqualTo(bar.hashCode());
+ }
+
+ @Test
+ public void versionEqualsAndHashCode() {
+ final Wrapper foo1 =
+ new Wrapper(RequestProto.ResourcesRequest.newBuilder().setVersion(
+ "foo").build().toByteArray(), Wrapper.VERSION);
+ final Wrapper foo2 =
+ new Wrapper(RequestProto.ResourcesRequest.newBuilder().setVersion(
+ "foo").build().toByteArray(), /* version= */2);
+
+ assertThat(foo1).isNotEqualTo(foo2);
+ assertThat(foo1.hashCode()).isNotEqualTo(foo2.hashCode());
+ }
+
+ @Test
+ public void toParcelAndBack() {
+ RequestProto.ResourcesRequest wrappedMessage =
+ RequestProto.ResourcesRequest.newBuilder().setVersion("foobar").build();
+ Wrapper wrapper = new Wrapper(wrappedMessage.toByteArray(), Wrapper.VERSION);
+
+ Parcel parcel = Parcel.obtain();
+ wrapper.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(Wrapper.CREATOR.createFromParcel(parcel)).isEqualTo(wrapper);
+ }
+
+ @Test
+ public void arrayCreator() {
+ assertThat(Wrapper.CREATOR.newArray(123)).hasLength(123);
+ }
+}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java
new file mode 100644
index 0000000..9465918
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.wear.tiles.proto.ResourceProto.Resources;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public final class ResourcesDataTest {
+ @Test
+ public void toParcelAndBack() {
+ Resources resources = Resources.newBuilder().setVersion("v123").build();
+ ResourcesData wrapper =
+ new ResourcesData(resources.toByteArray(), ResourcesData.VERSION_PROTOBUF);
+
+ Parcel parcel = Parcel.obtain();
+ wrapper.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(ResourcesData.CREATOR.createFromParcel(parcel)).isEqualTo(wrapper);
+ }
+}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java
new file mode 100644
index 0000000..9213528
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.wear.tiles.proto.RequestProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public final class ResourcesRequestDataTest {
+ @Test
+ public void toParcelAndBack() {
+ RequestProto.ResourcesRequest request =
+ RequestProto.ResourcesRequest.newBuilder().setVersion("v123").build();
+ ResourcesRequestData wrapper =
+ new ResourcesRequestData(request.toByteArray(),
+ ResourcesRequestData.VERSION_PROTOBUF);
+
+ Parcel parcel = Parcel.obtain();
+ wrapper.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(ResourcesRequestData.CREATOR.createFromParcel(parcel)).isEqualTo(wrapper);
+ }
+}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileDataTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileDataTest.java
new file mode 100644
index 0000000..f1938b6
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileDataTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.wear.tiles.proto.TileProto.Tile;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public final class TileDataTest {
+ @Test
+ public void toParcelAndBack() {
+ Tile tile = Tile.newBuilder().setResourcesVersion("v123").build();
+ TileData wrapper = new TileData(tile.toByteArray(), TileData.VERSION_PROTOBUF);
+
+ Parcel parcel = Parcel.obtain();
+ wrapper.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(TileData.CREATOR.createFromParcel(parcel)).isEqualTo(wrapper);
+ }
+}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
new file mode 100644
index 0000000..7f5f73d
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.wear.tiles.builders.ResourceBuilders;
+import androidx.wear.tiles.builders.TileBuilders;
+import androidx.wear.tiles.proto.RequestProto;
+import androidx.wear.tiles.proto.ResourceProto.Resources;
+import androidx.wear.tiles.proto.TileProto.Tile;
+import androidx.wear.tiles.readers.RequestReaders.ResourcesRequest;
+import androidx.wear.tiles.readers.RequestReaders.TileRequest;
+
+import com.google.common.truth.Expect;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ServiceController;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class TileProviderServiceTest {
+ @Rule public final Expect expect = Expect.create();
+
+ private static final TileBuilders.Tile DUMMY_TILE =
+ TileBuilders.Tile.builder().setResourcesVersion("5").build();
+ private TileProvider mTileProviderService;
+
+ @Mock private TileCallback mMockTileCallback;
+ @Mock private ResourcesCallback mMockResourcesCallback;
+
+ @Before
+ public void setUp() {
+ mMockTileCallback = mock(TileCallback.class);
+ mMockResourcesCallback = mock(ResourcesCallback.class);
+
+ ServiceController<DummyTileProviderService> dummyTileProviderServiceController =
+ Robolectric.buildService(
+ DummyTileProviderService.class);
+
+ Intent i = new Intent(TileProviderService.ACTION_BIND_TILE_PROVIDER);
+ IBinder binder = dummyTileProviderServiceController.get().onBind(i);
+ mTileProviderService = TileProvider.Stub.asInterface(binder);
+ }
+
+ @Test
+ public void tileProvider_tileRequest() throws Exception {
+ mTileProviderService.onTileRequest(
+ 5,
+ new TileRequestData(
+ RequestProto.TileRequest.getDefaultInstance().toByteArray(),
+ TileRequestData.VERSION_PROTOBUF),
+ mMockTileCallback);
+
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<TileData> tileCaptor = ArgumentCaptor.forClass(TileData.class);
+
+ verify(mMockTileCallback).updateTileData(tileCaptor.capture());
+
+ Tile tile =
+ Tile.parseFrom(
+ tileCaptor.getValue().getContents());
+
+ expect.that(tile).isEqualTo(DUMMY_TILE.toProto());
+ }
+
+ @Test
+ public void tileProvider_resourcesRequest() throws Exception {
+ final String resourcesVersion = "HELLO WORLD";
+
+ ResourcesRequestData resourcesRequestData =
+ new ResourcesRequestData(
+ RequestProto.ResourcesRequest.newBuilder()
+ .setVersion(resourcesVersion)
+ .build()
+ .toByteArray(),
+ ResourcesRequestData.VERSION_PROTOBUF);
+
+ mTileProviderService.onResourcesRequest(5, resourcesRequestData, mMockResourcesCallback);
+
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<ResourcesData> resourcesCaptor = ArgumentCaptor.forClass(
+ ResourcesData.class);
+ verify(mMockResourcesCallback).updateResources(resourcesCaptor.capture());
+
+ Resources resources =
+ Resources.parseFrom(
+ resourcesCaptor.getValue().getContents());
+
+ expect.that(resources.getVersion()).isEqualTo(resourcesVersion);
+ }
+
+ public static class DummyTileProviderService extends TileProviderService {
+ @Override
+ @NonNull
+ protected ListenableFuture<TileBuilders.Tile> onTileRequest(
+ @NonNull TileRequest requestParams) {
+ return Futures.immediateFuture(DUMMY_TILE);
+ }
+
+ @Override
+ @NonNull
+ protected ListenableFuture<ResourceBuilders.Resources> onResourcesRequest(
+ @NonNull ResourcesRequest requestParams) {
+ ResourceBuilders.Resources resources =
+ ResourceBuilders.Resources.builder().setVersion(
+ requestParams.getVersion()).build();
+
+ return Futures.immediateFuture(resources);
+ }
+ }
+}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java
new file mode 100644
index 0000000..bf8ad22
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.wear.tiles.proto.RequestProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public final class TileRequestDataTest {
+ @Test
+ public void toParcelAndBack() {
+ RequestProto.TileRequest request = RequestProto.TileRequest.getDefaultInstance();
+ TileRequestData wrapper =
+ new TileRequestData(request.toByteArray(), TileRequestData.VERSION_PROTOBUF);
+
+ Parcel parcel = Parcel.obtain();
+ wrapper.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(TileRequestData.CREATOR.createFromParcel(parcel)).isEqualTo(wrapper);
+ }
+}
diff --git a/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileUpdateRequestDataTest.java b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileUpdateRequestDataTest.java
new file mode 100644
index 0000000..73f336c
--- /dev/null
+++ b/wear/wear-tiles/src/test/java/androidx/wear/tiles/TileUpdateRequestDataTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public final class TileUpdateRequestDataTest {
+ @Test
+ public void toParcelAndBack() {
+ // This payload ends up empty anyway (it's there for future expansion). Just test that it
+ // doesn't error out when being parceled and back again.
+ TileUpdateRequestData wrapper = new TileUpdateRequestData();
+
+ Parcel parcel = Parcel.obtain();
+ wrapper.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(TileUpdateRequestData.CREATOR.createFromParcel(parcel)).isEqualTo(wrapper);
+ }
+}