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.shade 18 19 import android.view.MotionEvent 20 import com.android.systemui.assist.AssistManager 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Background 23 import com.android.systemui.dagger.qualifiers.Main 24 import com.android.systemui.scene.domain.interactor.SceneInteractor 25 import com.android.systemui.scene.shared.model.SceneFamilies 26 import com.android.systemui.scene.shared.model.Scenes 27 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse 28 import com.android.systemui.shade.ShadeController.ShadeVisibilityListener 29 import com.android.systemui.shade.domain.interactor.ShadeInteractor 30 import com.android.systemui.statusbar.CommandQueue 31 import com.android.systemui.statusbar.NotificationShadeWindowController 32 import com.android.systemui.statusbar.VibratorHelper 33 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout 34 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 35 import dagger.Lazy 36 import javax.inject.Inject 37 import kotlinx.coroutines.CoroutineDispatcher 38 import kotlinx.coroutines.CoroutineScope 39 import kotlinx.coroutines.ExperimentalCoroutinesApi 40 import kotlinx.coroutines.delay 41 import kotlinx.coroutines.flow.first 42 import kotlinx.coroutines.launch 43 import kotlinx.coroutines.withContext 44 45 /** 46 * Implementation of ShadeController backed by scenes instead of NPVC. 47 * 48 * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods 49 */ 50 @OptIn(ExperimentalCoroutinesApi::class) 51 @SysUISingleton 52 class ShadeControllerSceneImpl 53 @Inject 54 constructor( 55 @Main private val mainDispatcher: CoroutineDispatcher, 56 @Background private val scope: CoroutineScope, 57 private val shadeInteractor: ShadeInteractor, 58 private val sceneInteractor: SceneInteractor, 59 private val notificationStackScrollLayout: NotificationStackScrollLayout, 60 private val vibratorHelper: VibratorHelper, 61 commandQueue: CommandQueue, 62 statusBarKeyguardViewManager: StatusBarKeyguardViewManager, 63 notificationShadeWindowController: NotificationShadeWindowController, 64 assistManagerLazy: Lazy<AssistManager>, 65 ) : 66 BaseShadeControllerImpl( 67 commandQueue, 68 statusBarKeyguardViewManager, 69 notificationShadeWindowController, 70 assistManagerLazy, 71 ) { 72 73 init { 74 scope.launch { 75 shadeInteractor.isAnyExpanded.collect { 76 if (!it) { 77 runPostCollapseActions() 78 } 79 } 80 } 81 } 82 83 override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value 84 85 override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value 86 87 override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value 88 89 override fun instantExpandShade() { 90 // Do nothing 91 } 92 93 override fun instantCollapseShade() { 94 sceneInteractor.snapToScene( 95 SceneFamilies.Home, 96 "hide shade", 97 ) 98 } 99 100 override fun animateCollapseShade( 101 flags: Int, 102 force: Boolean, 103 delayed: Boolean, 104 speedUpFactor: Float 105 ) { 106 if (!force && !shadeInteractor.isAnyExpanded.value) { 107 runPostCollapseActions() 108 return 109 } 110 if ( 111 shadeInteractor.isAnyExpanded.value && 112 flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0 113 ) { 114 // release focus immediately to kick off focus change transition 115 notificationShadeWindowController.setNotificationShadeFocusable(false) 116 notificationStackScrollLayout.cancelExpandHelper() 117 if (delayed) { 118 scope.launch { 119 delay(125) 120 animateCollapseShadeInternal() 121 } 122 } else { 123 animateCollapseShadeInternal() 124 } 125 } 126 } 127 128 override fun collapseWithDuration(animationDuration: Int) { 129 // TODO(b/300258424) inline this. The only caller uses the default duration. 130 animateCollapseShade() 131 } 132 133 private fun animateCollapseShadeInternal() { 134 sceneInteractor.changeScene( 135 SceneFamilies.Home, // TODO(b/336581871): add sceneState? 136 "ShadeController.animateCollapseShade", 137 SlightlyFasterShadeCollapse, 138 ) 139 } 140 141 override fun cancelExpansionAndCollapseShade() { 142 // TODO do we need to actually cancel the touch session? 143 animateCollapseShade() 144 } 145 146 override fun closeShadeIfOpen(): Boolean { 147 if (shadeInteractor.isAnyExpanded.value) { 148 commandQueue.animateCollapsePanels( 149 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 150 true /* force */ 151 ) 152 assistManagerLazy.get().hideAssist() 153 } 154 return false 155 } 156 157 override fun collapseShade() { 158 animateCollapseShadeForcedDelayed() 159 } 160 161 override fun collapseShade(animate: Boolean) { 162 if (animate) { 163 animateCollapseShade() 164 } else { 165 instantCollapseShade() 166 } 167 } 168 169 override fun collapseOnMainThread() { 170 // TODO if this works with delegation alone, we can deprecate and delete 171 collapseShade() 172 } 173 174 override fun expandToNotifications() { 175 sceneInteractor.changeScene( 176 SceneFamilies.NotifShade, 177 "ShadeController.animateExpandShade", 178 ) 179 } 180 181 override fun expandToQs() { 182 sceneInteractor.changeScene(SceneFamilies.QuickSettings, "ShadeController.animateExpandQs") 183 } 184 185 override fun setVisibilityListener(listener: ShadeVisibilityListener) { 186 scope.launch { 187 sceneInteractor.isVisible.collect { isVisible -> 188 withContext(mainDispatcher) { listener.expandedVisibleChanged(isVisible) } 189 } 190 } 191 } 192 193 @ExperimentalCoroutinesApi 194 override fun collapseShadeForActivityStart() { 195 if (shadeInteractor.isAnyExpanded.value) { 196 animateCollapseShadeForcedDelayed() 197 } else { 198 runPostCollapseActions() 199 } 200 } 201 202 override fun postAnimateCollapseShade() { 203 animateCollapseShade() 204 } 205 206 override fun postAnimateForceCollapseShade() { 207 animateCollapseShadeForced() 208 } 209 210 override fun postAnimateExpandQs() { 211 expandToQs() 212 } 213 214 override fun postOnShadeExpanded(action: Runnable) { 215 // TODO verify that clicking "reply" in a work profile notification launches the app 216 // TODO verify that there's not a way to replace and deprecate this method 217 scope.launch { 218 shadeInteractor.isAnyFullyExpanded.first { it } 219 action.run() 220 } 221 } 222 223 override fun makeExpandedInvisible() { 224 // Do nothing 225 } 226 227 override fun makeExpandedVisible(force: Boolean) { 228 // Do nothing 229 } 230 231 override fun isExpandedVisible(): Boolean { 232 return sceneInteractor.currentScene.value != Scenes.Gone 233 } 234 235 override fun onStatusBarTouch(event: MotionEvent) { 236 // The only call to this doesn't happen with MigrateClocksToBlueprint.isEnabled enabled 237 throw UnsupportedOperationException() 238 } 239 240 override fun performHapticFeedback(constant: Int) { 241 vibratorHelper.performHapticFeedback(notificationStackScrollLayout, constant) 242 } 243 } 244