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.transition.TransitionManager 20 import android.transition.TransitionSet 21 import android.view.View.INVISIBLE 22 import android.view.ViewGroup 23 import androidx.annotation.VisibleForTesting 24 import androidx.constraintlayout.helper.widget.Layer 25 import androidx.constraintlayout.widget.ConstraintLayout 26 import androidx.constraintlayout.widget.ConstraintSet 27 import androidx.lifecycle.Lifecycle 28 import androidx.lifecycle.repeatOnLifecycle 29 import com.android.app.tracing.coroutines.launchTraced as launch 30 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor 31 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor 32 import com.android.systemui.keyguard.shared.model.ClockSize 33 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type 34 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection 35 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel 36 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel 37 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel 38 import com.android.systemui.lifecycle.repeatWhenAttached 39 import com.android.systemui.plugins.clocks.AodClockBurnInModel 40 import com.android.systemui.plugins.clocks.ClockController 41 import com.android.systemui.util.kotlin.DisposableHandles 42 import com.android.systemui.util.ui.value 43 import kotlinx.coroutines.DisposableHandle 44 import kotlinx.coroutines.flow.combine 45 import kotlinx.coroutines.flow.distinctUntilChanged 46 import kotlinx.coroutines.flow.map 47 48 object KeyguardClockViewBinder { 49 private val TAG = KeyguardClockViewBinder::class.simpleName!! 50 51 @JvmStatic 52 fun bind( 53 clockSection: ClockSection, 54 keyguardRootView: ConstraintLayout, 55 viewModel: KeyguardClockViewModel, 56 keyguardClockInteractor: KeyguardClockInteractor, 57 blueprintInteractor: KeyguardBlueprintInteractor, 58 rootViewModel: KeyguardRootViewModel, 59 aodBurnInViewModel: AodBurnInViewModel, 60 ): DisposableHandle { 61 val disposables = DisposableHandles() 62 disposables += 63 keyguardRootView.repeatWhenAttached { 64 repeatOnLifecycle(Lifecycle.State.CREATED) { 65 keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView) 66 } 67 } 68 69 disposables += 70 keyguardRootView.repeatWhenAttached { 71 repeatOnLifecycle(Lifecycle.State.CREATED) { 72 // When changing to new clock, we need to remove old views from burnInLayer 73 var lastClock: ClockController? = null 74 launch { 75 viewModel.currentClock.collect { currentClock -> 76 if (lastClock != currentClock) { 77 cleanupClockViews( 78 lastClock, 79 keyguardRootView, 80 viewModel.burnInLayer, 81 ) 82 lastClock = currentClock 83 } 84 85 addClockViews(currentClock, keyguardRootView) 86 updateBurnInLayer( 87 keyguardRootView, 88 viewModel, 89 viewModel.clockSize.value, 90 ) 91 applyConstraints(clockSection, keyguardRootView, true) 92 } 93 } 94 .invokeOnCompletion { 95 cleanupClockViews(lastClock, keyguardRootView, viewModel.burnInLayer) 96 lastClock = null 97 } 98 99 launch { 100 viewModel.clockSize.collect { clockSize -> 101 updateBurnInLayer(keyguardRootView, viewModel, clockSize) 102 blueprintInteractor.refreshBlueprint(Type.ClockSize) 103 } 104 } 105 106 launch { 107 viewModel.clockShouldBeCentered.collect { 108 viewModel.currentClock.value?.let { 109 if (it.largeClock.config.hasCustomPositionUpdatedAnimation) { 110 blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping) 111 } else { 112 blueprintInteractor.refreshBlueprint(Type.DefaultTransition) 113 } 114 } 115 } 116 } 117 118 launch { 119 combine( 120 viewModel.hasAodIcons, 121 rootViewModel.isNotifIconContainerVisible.map { it.value }, 122 ) { hasIcon, isVisible -> 123 hasIcon && isVisible 124 } 125 .distinctUntilChanged() 126 .collect { _ -> 127 viewModel.currentClock.value?.let { 128 if (it.config.useCustomClockScene) { 129 blueprintInteractor.refreshBlueprint(Type.DefaultTransition) 130 } 131 } 132 } 133 } 134 135 launch { 136 aodBurnInViewModel.movement.collect { burnInModel -> 137 viewModel.currentClock.value 138 ?.largeClock 139 ?.layout 140 ?.applyAodBurnIn( 141 AodClockBurnInModel( 142 translationX = burnInModel.translationX.toFloat(), 143 translationY = burnInModel.translationY.toFloat(), 144 scale = burnInModel.scale, 145 ) 146 ) 147 } 148 } 149 150 launch { 151 viewModel.largeClockTextSize.collect { fontSizePx -> 152 viewModel.currentClock.value 153 ?.largeClock 154 ?.events 155 ?.onFontSettingChanged(fontSizePx = fontSizePx.toFloat()) 156 } 157 } 158 } 159 } 160 161 disposables += 162 keyguardRootView.repeatWhenAttached { 163 repeatOnLifecycle(Lifecycle.State.STARTED) { 164 viewModel.currentClock.collect { currentClock -> 165 currentClock?.apply { 166 smallClock.run { events.onThemeChanged(theme) } 167 largeClock.run { events.onThemeChanged(theme) } 168 } 169 } 170 } 171 } 172 173 return disposables 174 } 175 176 @VisibleForTesting 177 fun updateBurnInLayer( 178 keyguardRootView: ConstraintLayout, 179 viewModel: KeyguardClockViewModel, 180 clockSize: ClockSize, 181 ) { 182 val burnInLayer = viewModel.burnInLayer 183 val clockController = viewModel.currentClock.value 184 // Large clocks won't be added to or removed from burn in layer 185 // Weather large clock has customized burn in preventing mechanism 186 // Non-weather large clock will only scale and translate vertically 187 clockController?.let { clock -> 188 when (clockSize) { 189 ClockSize.LARGE -> { 190 clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) } 191 } 192 ClockSize.SMALL -> { 193 clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) } 194 } 195 } 196 } 197 viewModel.burnInLayer?.updatePostLayout(keyguardRootView) 198 } 199 200 fun cleanupClockViews( 201 lastClock: ClockController?, 202 rootView: ConstraintLayout, 203 burnInLayer: Layer?, 204 ) { 205 lastClock?.run { 206 smallClock.layout.views.forEach { 207 burnInLayer?.removeView(it) 208 rootView.removeView(it) 209 } 210 largeClock.layout.views.forEach { rootView.removeView(it) } 211 } 212 } 213 214 @VisibleForTesting 215 fun addClockViews(clockController: ClockController?, rootView: ConstraintLayout) { 216 // We'll collect the same clock when exiting wallpaper picker without changing clock 217 // so we need to remove clock views from parent before addView again 218 clockController?.let { clock -> 219 clock.smallClock.layout.views.forEach { 220 if (it.parent != null) { 221 (it.parent as ViewGroup).removeView(it) 222 } 223 rootView.addView(it).apply { it.visibility = INVISIBLE } 224 } 225 clock.largeClock.layout.views.forEach { 226 if (it.parent != null) { 227 (it.parent as ViewGroup).removeView(it) 228 } 229 rootView.addView(it).apply { it.visibility = INVISIBLE } 230 } 231 } 232 } 233 234 fun applyConstraints( 235 clockSection: ClockSection, 236 rootView: ConstraintLayout, 237 animated: Boolean, 238 set: TransitionSet? = null, 239 ) { 240 val constraintSet = ConstraintSet().apply { clone(rootView) } 241 clockSection.applyConstraints(constraintSet) 242 if (animated) { 243 set?.let { TransitionManager.beginDelayedTransition(rootView, it) } 244 ?: run { TransitionManager.beginDelayedTransition(rootView) } 245 } 246 constraintSet.applyTo(rootView) 247 } 248 } 249