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