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