1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.ui.platform
18 
19 import androidx.compose.runtime.withFrameNanos
20 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
21 import kotlin.coroutines.CoroutineContext
22 import kotlin.coroutines.coroutineContext
23 
24 /**
25  * Provides a policy that will be applied to animations that get their frame time from
26  * [withInfiniteAnimationFrameNanos][androidx.compose.animation.core.withInfiniteAnimationFrameNanos]
27  * or
28  * [withInfiniteAnimationFrameMillis][androidx.compose.animation.core.withInfiniteAnimationFrameMillis]
29  * This can be used to intervene in infinite animations to make them finite, for example by
30  * cancelling such coroutines.
31  *
32  * By default no policy is installed, except in instrumented tests that use
33  * [androidx.compose.ui.test.junit4.ComposeTestRule].
34  */
35 @JvmDefaultWithCompatibility
36 interface InfiniteAnimationPolicy : CoroutineContext.Element {
37     /**
38      * Call this to apply the policy on the given suspending [block]. Execution of the block is
39      * determined by the policy implementation. For example, a test policy could decide not to run
40      * the block, or trace its execution.
41      *
42      * The block is intended to be part of and will therefore be treated as an infinite animation,
43      * one that after returning from [onInfiniteOperation] will call it again. If the block is not
44      * part of an infinite animation, the policy will still be applied.
45      */
onInfiniteOperationnull46     suspend fun <R> onInfiniteOperation(block: suspend () -> R): R
47 
48     override val key: CoroutineContext.Key<*>
49         get() = Key
50 
51     companion object Key : CoroutineContext.Key<InfiniteAnimationPolicy>
52 }
53 
54 /**
55  * Like [withFrameNanos], but applies the [InfiniteAnimationPolicy] from the calling
56  * [CoroutineContext] if there is one.
57  *
58  * Note that this is an exact copy of the implementation in the `animation-core` module. We need
59  * access to it in this module, but other changes are being considered to this API so we don't want
60  * to go moving APIs around now if we might change them anyway. b/230369229 tracks cleaning up this
61  * clipboard inheritance.
62  */
63 internal suspend fun <R> withInfiniteAnimationFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R =
64     when (val policy = coroutineContext[InfiniteAnimationPolicy]) {
65         null -> withFrameNanos(onFrame)
66         else -> policy.onInfiniteOperation { withFrameNanos(onFrame) }
67     }
68