• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 com.android.intentresolver.util
18 
19 import android.os.SystemClock
20 import kotlinx.coroutines.CoroutineStart
21 import kotlinx.coroutines.Job
22 import kotlinx.coroutines.coroutineScope
23 import kotlinx.coroutines.delay
24 import kotlinx.coroutines.flow.Flow
25 import kotlinx.coroutines.flow.channelFlow
26 import kotlinx.coroutines.launch
27 
28 /**
29  * Returns a flow that mirrors the original flow, but delays values following emitted values for the
30  * given [periodMs]. If the original flow emits more than one value during this period, only the
31  * latest value is emitted.
32  *
33  * Example:
34  *
35  * ```kotlin
36  * flow {
37  *     emit(1)     // t=0ms
38  *     delay(90)
39  *     emit(2)     // t=90ms
40  *     delay(90)
41  *     emit(3)     // t=180ms
42  *     delay(1010)
43  *     emit(4)     // t=1190ms
44  *     delay(1010)
45  *     emit(5)     // t=2200ms
46  * }.throttle(1000)
47  * ```
48  *
49  * produces the following emissions at the following times
50  *
51  * ```text
52  * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
53  * ```
54  */
55 // A SystemUI com.android.systemui.util.kotlin.throttle copy.
<lambda>null56 fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = channelFlow {
57     coroutineScope {
58         var previousEmitTimeMs = 0L
59         var delayJob: Job? = null
60         var sendJob: Job? = null
61         val outerScope = this
62 
63         collect {
64             delayJob?.cancel()
65             sendJob?.join()
66             val currentTimeMs = SystemClock.elapsedRealtime()
67             val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
68             val timeUntilNextEmit = maxOf(0L, periodMs - timeSinceLastEmit)
69             if (timeUntilNextEmit > 0L) {
70                 // We create delayJob to allow cancellation during the delay period
71                 delayJob = launch {
72                     delay(timeUntilNextEmit)
73                     sendJob = outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
74                         send(it)
75                         previousEmitTimeMs = SystemClock.elapsedRealtime()
76                     }
77                 }
78             } else {
79                 send(it)
80                 previousEmitTimeMs = currentTimeMs
81             }
82         }
83     }
84 }
85