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