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