• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.scene.domain.interactor
18 
19 import com.android.app.tracing.coroutines.launchTraced as launch
20 import com.android.compose.animation.scene.ObservableTransitionState
21 import com.android.systemui.CoreStartable
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.keyguard.data.repository.KeyguardRepository
25 import com.android.systemui.keyguard.shared.model.StatusBarState
26 import com.android.systemui.power.domain.interactor.PowerInteractor
27 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
28 import com.android.systemui.scene.shared.flag.SceneContainerFlag
29 import com.android.systemui.scene.shared.model.Overlays
30 import com.android.systemui.scene.shared.model.Scenes
31 import com.android.systemui.statusbar.NotificationPresenter
32 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
33 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
34 import com.android.systemui.statusbar.notification.init.NotificationsController
35 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
36 import javax.inject.Inject
37 import javax.inject.Provider
38 import kotlinx.coroutines.CoroutineScope
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.StateFlow
41 import kotlinx.coroutines.flow.combine
42 import kotlinx.coroutines.flow.distinctUntilChanged
43 import kotlinx.coroutines.flow.flatMapConcat
44 import kotlinx.coroutines.flow.flowOf
45 import kotlinx.coroutines.flow.map
46 import kotlinx.coroutines.flow.stateIn
47 
48 /** Business logic about the visibility of various parts of the window root view. */
49 @SysUISingleton
50 class WindowRootViewVisibilityInteractor
51 @Inject
52 constructor(
53     @Application private val scope: CoroutineScope,
54     private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository,
55     private val keyguardRepository: KeyguardRepository,
56     private val headsUpManager: HeadsUpManager,
57     powerInteractor: PowerInteractor,
58     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
59     sceneInteractorProvider: Provider<SceneInteractor>,
60 ) : CoreStartable {
61 
62     private var notificationPresenter: NotificationPresenter? = null
63     private var notificationsController: NotificationsController? = null
64 
65     private val isNotifPresenterFullyCollapsed: Boolean
66         get() = notificationPresenter?.isPresenterFullyCollapsed ?: true
67 
68     /**
69      * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
70      * false if the bouncer is visible.
71      */
72     val isLockscreenOrShadeVisible: StateFlow<Boolean> =
73         if (!SceneContainerFlag.isEnabled) {
74             windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
75         } else {
76             sceneInteractorProvider
77                 .get()
78                 .transitionState
79                 .flatMapConcat { state ->
80                     when (state) {
81                         is ObservableTransitionState.Idle ->
82                             flowOf(
83                                 state.currentScene == Scenes.Shade ||
84                                     state.currentScene == Scenes.Lockscreen ||
85                                     Overlays.NotificationsShade in state.currentOverlays ||
86                                     Overlays.QuickSettingsShade in state.currentOverlays
87                             )
88                         is ObservableTransitionState.Transition ->
89                             if (
90                                 state.fromContent == Overlays.Bouncer &&
91                                     state.toContent == Scenes.Lockscreen
92                             ) {
93                                 // Lockscreen is not visible during preview stage of predictive back
94                                 state.isInPreviewStage.map { !it }
95                             } else {
96                                 flowOf(
97                                     state.toContent == Scenes.Shade ||
98                                         state.toContent == Overlays.NotificationsShade ||
99                                         state.toContent == Overlays.QuickSettingsShade ||
100                                         state.toContent == Scenes.Lockscreen ||
101                                         state.fromContent == Scenes.Shade ||
102                                         state.fromContent == Overlays.NotificationsShade ||
103                                         state.fromContent == Overlays.QuickSettingsShade ||
104                                         state.fromContent == Scenes.Lockscreen
105                                 )
106                             }
107                     }
108                 }
109                 .distinctUntilChanged()
110                 .stateIn(scope, SharingStarted.Eagerly, false)
111         }
112 
113     /**
114      * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
115      * interacting with the device, false otherwise. Notably, false if the bouncer is visible and
116      * false if the device is asleep.
117      */
118     val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> =
119         combine(isLockscreenOrShadeVisible, powerInteractor.isAwake) {
120                 isKeyguardAodOrShadeVisible,
121                 isAwake ->
122                 isKeyguardAodOrShadeVisible && isAwake
123             }
124             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
125 
126     /**
127      * Sets classes that aren't easily injectable on this class.
128      *
129      * TODO(b/277762009): Inject these directly instead.
130      */
131     fun setUp(
132         presenter: NotificationPresenter?,
133         notificationsController: NotificationsController?,
134     ) {
135         this.notificationPresenter = presenter
136         this.notificationsController = notificationsController
137     }
138 
139     override fun start() {
140         scope.launch {
141             isLockscreenOrShadeVisibleAndInteractive.collect { interactive ->
142                 if (interactive) {
143                     windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive(
144                         getShouldClearNotificationEffects(keyguardRepository.statusBarState.value),
145                         getNotificationLoad(),
146                     )
147                 } else {
148                     windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive()
149                 }
150             }
151         }
152     }
153 
154     fun setIsLockscreenOrShadeVisible(visible: Boolean) {
155         windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible)
156     }
157 
158     private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean {
159         return !isNotifPresenterFullyCollapsed &&
160             (statusBarState == StatusBarState.SHADE ||
161                 statusBarState == StatusBarState.SHADE_LOCKED)
162     }
163 
164     private fun getNotificationLoad(): Int {
165         return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
166             1
167         } else {
168             getActiveNotificationsCount()
169         }
170     }
171 
172     private fun getActiveNotificationsCount(): Int {
173         return if (NotificationsLiveDataStoreRefactor.isEnabled) {
174             activeNotificationsInteractor.allNotificationsCountValue
175         } else {
176             notificationsController?.getActiveNotificationsCount() ?: 0
177         }
178     }
179 }
180