1 /*
<lambda>null2  * 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.systemui.shade.domain.interactor
18 
19 import com.android.systemui.dagger.SysUISingleton
20 import com.android.systemui.dagger.qualifiers.Application
21 import com.android.systemui.keyguard.data.repository.KeyguardRepository
22 import com.android.systemui.keyguard.shared.model.StatusBarState
23 import com.android.systemui.shade.data.repository.ShadeRepository
24 import com.android.systemui.shade.shared.model.ShadeMode
25 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
26 import javax.inject.Inject
27 import kotlinx.coroutines.CoroutineScope
28 import kotlinx.coroutines.currentCoroutineContext
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.SharingStarted
31 import kotlinx.coroutines.flow.StateFlow
32 import kotlinx.coroutines.flow.combine
33 import kotlinx.coroutines.flow.distinctUntilChanged
34 import kotlinx.coroutines.flow.first
35 import kotlinx.coroutines.flow.flow
36 import kotlinx.coroutines.flow.stateIn
37 import kotlinx.coroutines.isActive
38 
39 /** ShadeInteractor implementation for the legacy codebase, e.g. NPVC. */
40 @SysUISingleton
41 class ShadeInteractorLegacyImpl
42 @Inject
43 constructor(
44     @Application val scope: CoroutineScope,
45     keyguardRepository: KeyguardRepository,
46     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
47     repository: ShadeRepository,
48 ) : BaseShadeInteractor {
49     /**
50      * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
51      * in downstream flows.
52      */
53     override val shadeExpansion: StateFlow<Float> =
54         combine(
55                 repository.lockscreenShadeExpansion,
56                 keyguardRepository.statusBarState,
57                 repository.legacyShadeExpansion,
58                 repository.qsExpansion,
59                 sharedNotificationContainerInteractor.isSplitShadeEnabled
60             ) {
61                 lockscreenShadeExpansion,
62                 statusBarState,
63                 legacyShadeExpansion,
64                 qsExpansion,
65                 splitShadeEnabled ->
66                 when (statusBarState) {
67                     // legacyShadeExpansion is 1 instead of 0 when QS is expanded
68                     StatusBarState.SHADE ->
69                         if (!splitShadeEnabled && qsExpansion > 0f) 1f - qsExpansion
70                         else legacyShadeExpansion
71                     StatusBarState.KEYGUARD -> lockscreenShadeExpansion
72                     // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when
73                     // the pointer is lifted and the lockscreen shade is fully expanded
74                     StatusBarState.SHADE_LOCKED -> 1f
75                 }
76             }
77             .distinctUntilChanged()
78             .stateIn(scope, SharingStarted.Eagerly, 0f)
79 
80     override val qsExpansion: StateFlow<Float> = repository.qsExpansion
81 
82     override val isQsExpanded: StateFlow<Boolean> = repository.legacyIsQsExpanded
83 
84     override val isQsBypassingShade: Flow<Boolean> = repository.legacyExpandImmediate
85     override val isQsFullscreen: Flow<Boolean> = repository.legacyQsFullscreen
86 
87     override val anyExpansion: StateFlow<Float> =
88         createAnyExpansionFlow(scope, shadeExpansion, qsExpansion)
89 
90     override val isAnyExpanded =
91         repository.legacyExpandedOrAwaitingInputTransfer.stateIn(
92             scope,
93             SharingStarted.Eagerly,
94             false
95         )
96 
97     override val isUserInteractingWithShade: Flow<Boolean> =
98         combine(
99             userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion),
100             repository.legacyLockscreenShadeTracking
101         ) { legacyShadeTracking, legacyLockscreenShadeTracking ->
102             legacyShadeTracking || legacyLockscreenShadeTracking
103         }
104 
105     override val isUserInteractingWithQs: Flow<Boolean> =
106         userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
107 
108     override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
109 
110     /**
111      * Return a flow for whether a user is interacting with an expandable shade component using
112      * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
113      * [expansion.first] checks the current value of the flow.
114      */
115     private fun userInteractingFlow(
116         tracking: Flow<Boolean>,
117         expansion: StateFlow<Float>
118     ): Flow<Boolean> {
119         return flow {
120             // initial value is false
121             emit(false)
122             while (currentCoroutineContext().isActive) {
123                 // wait for tracking to become true
124                 tracking.first { it }
125                 emit(true)
126                 // wait for tracking to become false
127                 tracking.first { !it }
128                 // wait for expansion to complete in either direction
129                 expansion.first { it <= 0f || it >= 1f }
130                 // interaction complete
131                 emit(false)
132             }
133         }
134     }
135 }
136