• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.systemui.statusbar.chips.ui.viewmodel
18 
19 import android.annotation.SuppressLint
20 import com.android.app.tracing.coroutines.launchTraced as launch
21 import com.android.systemui.dagger.qualifiers.Application
22 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
23 import kotlinx.coroutines.CoroutineScope
24 import kotlinx.coroutines.delay
25 import kotlinx.coroutines.flow.Flow
26 import kotlinx.coroutines.flow.MutableSharedFlow
27 import kotlinx.coroutines.flow.SharingStarted
28 import kotlinx.coroutines.flow.StateFlow
29 import kotlinx.coroutines.flow.combine
30 import kotlinx.coroutines.flow.stateIn
31 import kotlinx.coroutines.flow.transformLatest
32 
33 /**
34  * A class that can help [OngoingActivityChipViewModel] instances with various transition states.
35  *
36  * For now, this class's only functionality is immediately hiding the chip if the user has tapped an
37  * activity chip and then clicked "Stop" on the resulting dialog. There's a bit of a delay between
38  * when the user clicks "Stop" and when the system services notify SysUI that the activity has
39  * indeed stopped. We don't want the chip to briefly show for a few frames during that delay, so
40  * this class helps us immediately hide the chip as soon as the user clicks "Stop" in the dialog.
41  * See b/353249803#comment4.
42  */
43 class ChipTransitionHelper(@Application private val scope: CoroutineScope) {
44     /** A flow that emits each time the user has clicked "Stop" on the dialog. */
45     @SuppressLint("SharedFlowCreation")
46     private val activityStoppedFromDialogEvent = MutableSharedFlow<Unit>()
47 
48     /** True if the user recently stopped the activity from the dialog. */
49     private val wasActivityRecentlyStoppedFromDialog: Flow<Boolean> =
50         activityStoppedFromDialogEvent
51             .transformLatest {
52                 // Give system services 500ms to stop the activity and notify SysUI. Once more than
53                 // 500ms has elapsed, we should go back to using the current system service
54                 // information as the source of truth.
55                 emit(true)
56                 delay(500)
57                 emit(false)
58             }
59             // Use stateIn so that the flow created in [createChipFlow] is guaranteed to
60             // emit. (`combine`s require that all input flows have emitted.)
61             .stateIn(scope, SharingStarted.Lazily, false)
62 
63     /**
64      * Notifies this class that the user just clicked "Stop" on the stop dialog that's shown when
65      * the chip is tapped.
66      *
67      * Call this method in order to immediately hide the chip.
68      */
69     fun onActivityStoppedFromDialog() {
70         // Because this event causes UI changes, make sure it's launched on the main thread scope.
71         scope.launch { activityStoppedFromDialogEvent.emit(Unit) }
72     }
73 
74     /**
75      * Creates a flow that will forcibly hide the chip if the user recently stopped the activity
76      * (see [onActivityStoppedFromDialog]). In general, this flow just uses value in [chip].
77      */
78     fun createChipFlow(chip: Flow<OngoingActivityChipModel>): StateFlow<OngoingActivityChipModel> {
79         return combine(chip, wasActivityRecentlyStoppedFromDialog) {
80                 chipModel,
81                 activityRecentlyStopped ->
82                 if (activityRecentlyStopped) {
83                     // There's a bit of a delay between when the user stops an activity via
84                     // SysUI and when the system services notify SysUI that the activity has
85                     // indeed stopped. Prevent the chip from showing during this delay by
86                     // immediately hiding it without any animation.
87                     OngoingActivityChipModel.Inactive(shouldAnimate = false)
88                 } else {
89                     chipModel
90                 }
91             }
92             .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Inactive())
93     }
94 }
95