• 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.communal
18 
19 import android.os.UserHandle
20 import android.provider.Settings
21 import com.android.app.tracing.coroutines.launchTraced as launch
22 import com.android.internal.logging.UiEventLogger
23 import com.android.systemui.CoreStartable
24 import com.android.systemui.communal.domain.interactor.CommunalInteractor
25 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
26 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
27 import com.android.systemui.communal.shared.log.CommunalUiEvent
28 import com.android.systemui.communal.shared.model.CommunalScenes
29 import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal
30 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dagger.qualifiers.Application
33 import com.android.systemui.dagger.qualifiers.Background
34 import com.android.systemui.dagger.qualifiers.Main
35 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
36 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
37 import com.android.systemui.keyguard.shared.model.Edge
38 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
39 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
40 import com.android.systemui.scene.shared.model.Scenes
41 import com.android.systemui.statusbar.NotificationShadeWindowController
42 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
43 import com.android.systemui.util.kotlin.emitOnStart
44 import com.android.systemui.util.kotlin.sample
45 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
46 import com.android.systemui.util.settings.SystemSettings
47 import javax.inject.Inject
48 import kotlin.time.Duration.Companion.milliseconds
49 import kotlinx.coroutines.CoroutineDispatcher
50 import kotlinx.coroutines.CoroutineScope
51 import kotlinx.coroutines.Job
52 import kotlinx.coroutines.delay
53 import kotlinx.coroutines.flow.collectLatest
54 import kotlinx.coroutines.flow.combine
55 import kotlinx.coroutines.flow.distinctUntilChanged
56 import kotlinx.coroutines.flow.launchIn
57 import kotlinx.coroutines.flow.onEach
58 import kotlinx.coroutines.withContext
59 
60 /**
61  * A [CoreStartable] responsible for automatically navigating between communal scenes when certain
62  * conditions are met.
63  */
64 @SysUISingleton
65 class CommunalSceneStartable
66 @Inject
67 constructor(
68     private val communalInteractor: CommunalInteractor,
69     private val communalSettingsInteractor: CommunalSettingsInteractor,
70     private val communalSceneInteractor: CommunalSceneInteractor,
71     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
72     private val keyguardInteractor: KeyguardInteractor,
73     private val systemSettings: SystemSettings,
74     private val notificationShadeWindowController: NotificationShadeWindowController,
75     @Background private val bgScope: CoroutineScope,
76     @Application private val applicationScope: CoroutineScope,
77     @Main private val mainDispatcher: CoroutineDispatcher,
78     private val uiEventLogger: UiEventLogger,
79 ) : CoreStartable {
80     private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
81 
82     private var timeoutJob: Job? = null
83 
84     private var isDreaming: Boolean = false
85 
86     override fun start() {
87         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
88             return
89         }
90 
91         bgScope.launch {
92             communalSceneInteractor.isIdleOnCommunal.collectLatest {
93                 withContext(mainDispatcher) {
94                     notificationShadeWindowController.setGlanceableHubShowing(it)
95                 }
96             }
97         }
98 
99         // In V2, the timeout is handled by PowerManagerService since we no longer keep the dream
100         // active underneath the hub.
101         if (!communalSettingsInteractor.isV2FlagEnabled()) {
102             systemSettings
103                 .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
104                 // Read the setting value on start.
105                 .emitOnStart()
106                 .onEach {
107                     screenTimeout =
108                         systemSettings.getIntForUser(
109                             Settings.System.SCREEN_OFF_TIMEOUT,
110                             DEFAULT_SCREEN_TIMEOUT,
111                             UserHandle.USER_CURRENT,
112                         )
113                 }
114                 .launchIn(bgScope)
115 
116             // The hub mode timeout should start as soon as the user enters hub mode. At the end of
117             // the
118             // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
119             // dream is not running, nothing will happen. However if the dream starts again
120             // underneath
121             // hub mode after the initial timeout expires, such as if the device is docked or the
122             // dream
123             // app is updated by the Play store, a new timeout should be started.
124             bgScope.launch {
125                 combine(
126                         communalSceneInteractor.currentScene,
127                         // Emit a value on start so the combine starts.
128                         communalInteractor.userActivity.emitOnStart(),
129                     ) { scene, _ ->
130                         // Only timeout if we're on the hub is open.
131                         scene.isCommunal()
132                     }
133                     .collectLatest { shouldTimeout ->
134                         cancelHubTimeout()
135                         if (shouldTimeout) {
136                             startHubTimeout()
137                         }
138                     }
139             }
140 
141             bgScope.launch {
142                 keyguardInteractor.isDreaming
143                     .sample(communalSceneInteractor.currentScene, ::Pair)
144                     .collectLatest { (isDreaming, scene) ->
145                         this@CommunalSceneStartable.isDreaming = isDreaming
146                         if (scene.isCommunal() && isDreaming && timeoutJob == null) {
147                             // If dreaming starts after timeout has expired, ex. if dream restarts
148                             // under
149                             // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub.
150                             // The
151                             // delay is necessary so the KeyguardInteractor.isAbleToDream flow
152                             // passes
153                             // through that same amount of delay and publishes a new value which is
154                             // then
155                             // picked up by the HomeSceneFamilyResolver such that the next call to
156                             // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream".
157                             delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
158                             communalSceneInteractor.changeScene(
159                                 CommunalScenes.Blank,
160                                 "dream started after timeout",
161                             )
162                             uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
163                         }
164                     }
165             }
166         }
167 
168         if (communalSettingsInteractor.isV2FlagEnabled()) {
169             applicationScope.launch(context = mainDispatcher) {
170                 anyOf(
171                         communalSceneInteractor.isTransitioningToOrIdleOnCommunal,
172                         // when transitioning from hub to dream, allow hub to stay at the current
173                         // orientation, as keyguard doesn't allow rotation by default.
174                         keyguardTransitionInteractor.isInTransition(
175                             edge = Edge.create(from = Scenes.Communal, to = DREAMING),
176                             edgeWithoutSceneContainer =
177                                 Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
178                         ),
179                     )
180                     .distinctUntilChanged()
181                     .collectLatest {
182                         notificationShadeWindowController.setGlanceableHubOrientationAware(it)
183                     }
184             }
185         }
186     }
187 
188     private fun cancelHubTimeout() {
189         timeoutJob?.cancel()
190         timeoutJob = null
191     }
192 
193     private fun startHubTimeout() {
194         if (timeoutJob == null) {
195             timeoutJob =
196                 bgScope.launch {
197                     delay(screenTimeout.milliseconds)
198                     if (isDreaming) {
199                         communalSceneInteractor.changeScene(
200                             newScene = CommunalScenes.Blank,
201                             loggingReason = "hub timeout",
202                             transitionKey =
203                                 if (communalSettingsInteractor.isV2FlagEnabled())
204                                     CommunalTransitionKeys.SimpleFade
205                                 else null,
206                         )
207                         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
208                     }
209                     timeoutJob = null
210                 }
211         }
212     }
213 
214     companion object {
215         val DEFAULT_SCREEN_TIMEOUT = 15000
216     }
217 }
218