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