/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.android.systemui.util.kotlin import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.repeatWhenAttached import java.util.function.Consumer import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus /** A class allowing Java classes to collect on Kotlin flows. */ @SysUISingleton class JavaAdapter @Inject constructor( @Application private val applicationScope: CoroutineScope, @Background private val backgroundScope: CoroutineScope, ) { /** * Collect information for the given [flow], calling [consumer] for each emitted event. * * Important: This will immediately start collection and *never* stop it. This should only be * used by classes that *need* to always be collecting a value and processing it. Whenever * possible, please use [collectFlow] instead; that method will stop the collection when a view * has disappeared, which will ensure that we don't perform unnecessary work. * * Do *not* call this method in a class's constructor. Instead, call it in * [com.android.systemui.CoreStartable.start] or similar method. */ fun alwaysCollectFlow(flow: Flow, consumer: Consumer): Job { return alwaysCollectFlow(applicationScope, flow, consumer) } /** See [alwaysCollectFlow] */ fun alwaysCollectFlowInBackground(flow: Flow, consumer: Consumer): Job { return alwaysCollectFlow(backgroundScope, flow, consumer) } private fun alwaysCollectFlow( scope: CoroutineScope, flow: Flow, consumer: Consumer, ): Job { return scope.launch { flow.collect { consumer.accept(it) } } } @JvmOverloads fun stateInApp( flow: Flow, initialValue: T, started: SharingStarted = SharingStarted.Eagerly, ): StateFlow { return flow.stateIn(applicationScope, started, initialValue) } /** Call suspend functions from Java */ fun callSuspend( suspendFunction: suspend (T) -> R, arg: T, onSuccess: (R) -> Unit, onCancel: (CancellationException) -> Unit, onFailure: (Throwable) -> Unit, ): Job = applicationScope.launch { val result = try { suspendFunction(arg) } catch (ex: CancellationException) { onCancel(ex) return@launch } catch (ex: Throwable) { onFailure(ex) return@launch } onSuccess(result) } } /** * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to * [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners * during onViewAttached() and removing during onViewRemoved(). * * @return a disposable handle in order to cancel the flow in the future. */ @JvmOverloads fun collectFlow( view: View, flow: Flow, consumer: Consumer, coroutineContext: CoroutineContext = EmptyCoroutineContext, state: Lifecycle.State = Lifecycle.State.CREATED, ): DisposableHandle { return view.repeatWhenAttached(coroutineContext) { repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } } } /** * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to * [LifeCycle.State.CREATED] which is mapped over from the equivalent definition for collecting the * flow on a view. */ @JvmOverloads fun collectFlow( lifecycle: Lifecycle, flow: Flow, consumer: Consumer, state: Lifecycle.State = Lifecycle.State.CREATED, ): Job { return lifecycle.coroutineScope.launch { lifecycle.repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } } } /** * Collect information for the given [flow], calling [consumer] for each emitted event on the * specified [collectContext]. * * Collection will continue until the given [scope] is cancelled. */ @JvmOverloads fun collectFlow( scope: CoroutineScope, collectContext: CoroutineContext = scope.coroutineContext, flow: Flow, consumer: Consumer, ): Job { return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } } } fun combineFlows(flow1: Flow, flow2: Flow, bifunction: (A, B) -> R): Flow { return combine(flow1, flow2, bifunction) } fun combineFlows( flow1: Flow, flow2: Flow, flow3: Flow, trifunction: (A, B, C) -> R, ): Flow { return combine(flow1, flow2, flow3, trifunction) } fun combineFlows( flow: Flow, flow2: Flow, flow3: Flow, flow4: Flow, transform: (T1, T2, T3, T4) -> R, ): Flow { return combine(flow, flow2, flow3, flow4, transform) } fun combineFlows( flow: Flow, flow2: Flow, flow3: Flow, flow4: Flow, flow5: Flow, transform: (T1, T2, T3, T4, T5) -> R, ): Flow { return combine(flow, flow2, flow3, flow4, flow5, transform) } fun combineFlows( flow: Flow, flow2: Flow, flow3: Flow, flow4: Flow, flow5: Flow, flow6: Flow, transform: (T1, T2, T3, T4, T5, T6) -> R, ): Flow { return combine(flow, flow2, flow3, flow4, flow5, flow6, transform) }