• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.statusbar.notification.stack.ui.viewbinder
18 
19 import android.util.Log
20 import com.android.app.tracing.coroutines.flow.collectTraced
21 import com.android.app.tracing.coroutines.launchTraced as launch
22 import com.android.systemui.common.ui.ConfigurationState
23 import com.android.systemui.common.ui.view.onLayoutChanged
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.dump.DumpManager
27 import com.android.systemui.lifecycle.WindowLifecycleState
28 import com.android.systemui.lifecycle.repeatWhenAttached
29 import com.android.systemui.lifecycle.viewModel
30 import com.android.systemui.res.R
31 import com.android.systemui.shade.ShadeDisplayAware
32 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
33 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
34 import com.android.systemui.util.kotlin.FlowDumperImpl
35 import com.android.systemui.util.kotlin.launchAndDispose
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineDispatcher
38 import kotlinx.coroutines.DisposableHandle
39 import kotlinx.coroutines.flow.Flow
40 import kotlinx.coroutines.flow.MutableStateFlow
41 import kotlinx.coroutines.flow.filter
42 
43 /** Binds the [NotificationScrollView]. */
44 @SysUISingleton
45 class NotificationScrollViewBinder
46 @Inject
47 constructor(
48     dumpManager: DumpManager,
49     @Main private val mainImmediateDispatcher: CoroutineDispatcher,
50     private val view: NotificationScrollView,
51     private val viewModelFactory: NotificationScrollViewModel.Factory,
52     @ShadeDisplayAware private val configuration: ConfigurationState,
53 ) : FlowDumperImpl(dumpManager) {
54 
55     private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset")
56 
57     private fun updateViewPosition() {
58         val trueView = view.asView()
59         if (trueView.top != 0) {
60             Log.w("NSSL", "Expected $trueView to have top==0")
61         }
62         viewLeftOffset.value = trueView.left
63     }
64 
65     fun bindWhileAttached(): DisposableHandle {
66         return view.asView().repeatWhenAttached(mainImmediateDispatcher) { bind() }
67     }
68 
69     suspend fun bind(): Nothing =
70         view.asView().viewModel(
71             traceName = "NotificationScrollViewBinder",
72             minWindowLifecycleState = WindowLifecycleState.ATTACHED,
73             factory = viewModelFactory::create,
74         ) { viewModel ->
75             launchAndDispose {
76                 updateViewPosition()
77                 view.asView().onLayoutChanged { updateViewPosition() }
78             }
79 
80             launch {
81                 viewModel
82                     .notificationScrimShape(
83                         cornerRadius = scrimRadius,
84                         viewLeftOffset = viewLeftOffset,
85                     )
86                     .collectTraced { view.setClippingShape(it) }
87             }
88 
89             launch { viewModel.isOccluded.collectTraced { view.setOccluded(it) } }
90 
91             launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
92             launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } }
93             launch {
94                 viewModel.expandFraction.collectTraced {
95                     view.setExpandFraction(it.coerceIn(0f, 1f))
96                 }
97             }
98             launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } }
99             launch { viewModel.blurRadius(maxBlurRadius).collect(view::setBlurRadius) }
100             launch {
101                 viewModel.isShowingStackOnLockscreen.collectTraced {
102                     view.setShowingStackOnLockscreen(it)
103                 }
104             }
105             launch {
106                 viewModel.alphaForLockscreenFadeIn.collectTraced {
107                     view.setAlphaForLockscreenFadeIn(it)
108                 }
109             }
110             launch { viewModel.isScrollable.collectTraced { view.setScrollingEnabled(it) } }
111             launch { viewModel.isDozing.collectTraced { isDozing -> view.setDozing(isDozing) } }
112             launch {
113                 viewModel.isPulsing.collectTraced { isPulsing ->
114                     view.setPulsing(isPulsing, viewModel.shouldAnimatePulse.value)
115                 }
116             }
117             launch {
118                 viewModel.shouldCloseGuts
119                     .filter { it }
120                     .collectTraced { view.closeGutsOnSceneTouch() }
121             }
122             launch {
123                 viewModel.suppressHeightUpdates.collectTraced { view.suppressHeightUpdates(it) }
124             }
125 
126             launchAndDispose {
127                 view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
128                 view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
129                 view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer)
130                 view.setRemoteInputRowBottomBoundConsumer(
131                     viewModel.remoteInputRowBottomBoundConsumer
132                 )
133                 view.setAccessibilityScrollEventConsumer(viewModel.accessibilityScrollEventConsumer)
134                 viewModel.setQsScrimShapeConsumer { shape ->
135                     view.setNegativeClippingShape(
136                         shape?.let {
137                             it.copy(bounds = it.bounds.minus(leftOffset = view.asView().left))
138                         }
139                     )
140                 }
141                 DisposableHandle {
142                     view.setSyntheticScrollConsumer(null)
143                     view.setCurrentGestureOverscrollConsumer(null)
144                     view.setCurrentGestureInGutsConsumer(null)
145                     view.setRemoteInputRowBottomBoundConsumer(null)
146                     view.setAccessibilityScrollEventConsumer(null)
147                     viewModel.setQsScrimShapeConsumer(null)
148                 }
149             }
150         }
151 
152     /** blur radius to be applied when the QS panel is fully expanded */
153     private val maxBlurRadius: Flow<Int> =
154         configuration.getDimensionPixelSize(R.dimen.max_shade_content_blur_radius)
155 
156     /** flow of the scrim clipping radius */
157     private val scrimRadius: Flow<Int>
158         get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
159 }
160