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.keyguard.ui.binder 18 19 import android.view.View 20 import androidx.constraintlayout.helper.widget.Layer 21 import androidx.constraintlayout.widget.ConstraintLayout 22 import androidx.lifecycle.Lifecycle 23 import androidx.lifecycle.repeatOnLifecycle 24 import com.android.app.tracing.coroutines.launchTraced as launch 25 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor 26 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config 27 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type 28 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel 29 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel 30 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel 31 import com.android.systemui.lifecycle.repeatWhenAttached 32 import com.android.systemui.plugins.clocks.VRectF 33 import com.android.systemui.res.R 34 import com.android.systemui.shared.R as sharedR 35 import kotlinx.coroutines.DisposableHandle 36 import kotlinx.coroutines.flow.combine 37 38 object KeyguardSmartspaceViewBinder { 39 @JvmStatic 40 fun bind( 41 keyguardRootView: ConstraintLayout, 42 keyguardRootViewModel: KeyguardRootViewModel, 43 clockViewModel: KeyguardClockViewModel, 44 smartspaceViewModel: KeyguardSmartspaceViewModel, 45 blueprintInteractor: KeyguardBlueprintInteractor, 46 ): DisposableHandle { 47 return keyguardRootView.repeatWhenAttached { 48 repeatOnLifecycle(Lifecycle.State.CREATED) { 49 launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") { 50 combine( 51 smartspaceViewModel.isWeatherVisible, 52 clockViewModel.hasCustomWeatherDataDisplay, 53 ::Pair, 54 ) 55 .collect { 56 updateDateWeatherToBurnInLayer( 57 keyguardRootView, 58 clockViewModel, 59 smartspaceViewModel, 60 ) 61 blueprintInteractor.refreshBlueprint( 62 Config( 63 Type.SmartspaceVisibility, 64 checkPriority = false, 65 terminatePrevious = false, 66 ) 67 ) 68 } 69 } 70 71 launch("$TAG#smartspaceViewModel.bcSmartspaceVisibility") { 72 smartspaceViewModel.bcSmartspaceVisibility.collect { 73 updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel) 74 blueprintInteractor.refreshBlueprint( 75 Config( 76 Type.SmartspaceVisibility, 77 checkPriority = false, 78 terminatePrevious = false, 79 ) 80 ) 81 } 82 } 83 84 if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { 85 val xBuffer = 86 keyguardRootView.context.resources.getDimensionPixelSize( 87 R.dimen.smartspace_padding_horizontal 88 ) 89 val yBuffer = 90 keyguardRootView.context.resources.getDimensionPixelSize( 91 R.dimen.smartspace_padding_vertical 92 ) 93 94 val smallViewId = sharedR.id.date_smartspace_view 95 96 val largeViewId = sharedR.id.date_smartspace_view_large 97 98 launch("$TAG#smartspaceViewModel.burnInLayerVisibility") { 99 combine( 100 keyguardRootViewModel.burnInLayerVisibility, 101 clockViewModel.isLargeClockVisible, 102 ::Pair, 103 ) 104 .collect { (visibility, isLargeClock) -> 105 if (isLargeClock) { 106 // hide small clock date/weather 107 keyguardRootView.findViewById<View>(smallViewId)?.let { 108 it.visibility = View.GONE 109 } 110 } 111 } 112 } 113 114 launch("$TAG#clockEventController.onClockBoundsChanged") { 115 // Whenever the doze amount changes, the clock may update it's view bounds. 116 // We need to update our layout position as a result. We could do this via 117 // `requestLayout`, but that's quite expensive when enclosed in since this 118 // recomputes the entire ConstraintLayout, so instead we do it manually. We 119 // would use translationX/Y for this, but that's used by burnin. 120 combine( 121 clockViewModel.isLargeClockVisible, 122 clockViewModel.clockEventController.onClockBoundsChanged, 123 ::Pair, 124 ) 125 .collect { (isLargeClock, clockBounds) -> 126 val viewId = if (isLargeClock) smallViewId else largeViewId 127 keyguardRootView.findViewById<View>(viewId)?.let { 128 it.visibility = View.GONE 129 } 130 131 if (clockBounds == VRectF.ZERO) return@collect 132 if (isLargeClock) { 133 val largeDateHeight = 134 keyguardRootView 135 .findViewById<View>( 136 sharedR.id.date_smartspace_view_large 137 ) 138 ?.height ?: 0 139 140 keyguardRootView.findViewById<View>(largeViewId)?.let { view -> 141 val viewHeight = view.height 142 val offset = (largeDateHeight - viewHeight) / 2 143 view.top = (clockBounds.bottom + yBuffer + offset).toInt() 144 view.bottom = view.top + viewHeight 145 } 146 } else if ( 147 !KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock( 148 keyguardRootView.resources.configuration 149 ) 150 ) { 151 keyguardRootView.findViewById<View>(smallViewId)?.let { view -> 152 val viewWidth = view.width 153 if (view.isLayoutRtl()) { 154 view.right = (clockBounds.left - xBuffer).toInt() 155 view.left = view.right - viewWidth 156 } else { 157 view.left = (clockBounds.right + xBuffer).toInt() 158 view.right = view.left + viewWidth 159 } 160 } 161 } 162 } 163 } 164 } 165 } 166 } 167 } 168 169 private fun updateBCSmartspaceInBurnInLayer( 170 keyguardRootView: ConstraintLayout, 171 clockViewModel: KeyguardClockViewModel, 172 ) { 173 // Visibility is controlled by updateTargetVisibility in CardPagerAdapter 174 val burnInLayer = keyguardRootView.requireViewById<Layer>(R.id.burn_in_layer) 175 burnInLayer.apply { 176 val smartspaceView = 177 keyguardRootView.requireViewById<View>(sharedR.id.bc_smartspace_view) 178 if (smartspaceView.visibility == View.VISIBLE) { 179 addView(smartspaceView) 180 } else { 181 removeView(smartspaceView) 182 } 183 } 184 clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView) 185 } 186 187 private fun updateDateWeatherToBurnInLayer( 188 keyguardRootView: ConstraintLayout, 189 clockViewModel: KeyguardClockViewModel, 190 smartspaceViewModel: KeyguardSmartspaceViewModel, 191 ) { 192 if (clockViewModel.hasCustomWeatherDataDisplay.value) { 193 removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel) 194 } else { 195 addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel) 196 } 197 clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView) 198 } 199 200 private fun addDateWeatherToBurnInLayer( 201 constraintLayout: ConstraintLayout, 202 smartspaceViewModel: KeyguardSmartspaceViewModel, 203 ) { 204 val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer) 205 burnInLayer.apply { 206 if ( 207 smartspaceViewModel.isSmartspaceEnabled && 208 smartspaceViewModel.isDateWeatherDecoupled 209 ) { 210 val dateView = 211 constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view) 212 addView(dateView) 213 } 214 } 215 } 216 217 private fun removeDateWeatherFromBurnInLayer( 218 constraintLayout: ConstraintLayout, 219 smartspaceViewModel: KeyguardSmartspaceViewModel, 220 ) { 221 val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer) 222 burnInLayer.apply { 223 if ( 224 smartspaceViewModel.isSmartspaceEnabled && 225 smartspaceViewModel.isDateWeatherDecoupled 226 ) { 227 val dateView = 228 constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view) 229 removeView(dateView) 230 } 231 } 232 } 233 234 private const val TAG = "KeyguardSmartspaceViewBinder" 235 } 236