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.systemui.keyguard.MigrateClocksToBlueprint 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.KeyguardClockViewModel 36 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel 37 import com.android.systemui.lifecycle.repeatWhenAttached 38 import com.android.systemui.plugins.clocks.AodClockBurnInModel 39 import com.android.systemui.plugins.clocks.ClockController 40 import kotlinx.coroutines.launch 41 42 object KeyguardClockViewBinder { 43 private val TAG = KeyguardClockViewBinder::class.simpleName!! 44 // When changing to new clock, we need to remove old clock views from burnInLayer 45 private var lastClock: ClockController? = null 46 47 @JvmStatic 48 fun bind( 49 clockSection: ClockSection, 50 keyguardRootView: ConstraintLayout, 51 viewModel: KeyguardClockViewModel, 52 keyguardClockInteractor: KeyguardClockInteractor, 53 blueprintInteractor: KeyguardBlueprintInteractor, 54 rootViewModel: KeyguardRootViewModel, 55 ) { 56 keyguardRootView.repeatWhenAttached { 57 repeatOnLifecycle(Lifecycle.State.CREATED) { 58 keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView) 59 } 60 } 61 62 keyguardRootView.repeatWhenAttached { 63 repeatOnLifecycle(Lifecycle.State.CREATED) { 64 launch { 65 if (!MigrateClocksToBlueprint.isEnabled) return@launch 66 viewModel.currentClock.collect { currentClock -> 67 cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer) 68 addClockViews(currentClock, keyguardRootView) 69 updateBurnInLayer(keyguardRootView, viewModel, viewModel.clockSize.value) 70 applyConstraints(clockSection, keyguardRootView, true) 71 } 72 } 73 74 launch { 75 if (!MigrateClocksToBlueprint.isEnabled) return@launch 76 viewModel.clockSize.collect { clockSize -> 77 updateBurnInLayer(keyguardRootView, viewModel, clockSize) 78 blueprintInteractor.refreshBlueprint(Type.ClockSize) 79 } 80 } 81 82 launch { 83 if (!MigrateClocksToBlueprint.isEnabled) return@launch 84 viewModel.clockShouldBeCentered.collect { 85 viewModel.currentClock.value?.let { 86 // TODO(b/301502635): remove "!it.config.useCustomClockScene" when 87 // migrate clocks to blueprint is fully rolled out 88 if ( 89 it.largeClock.config.hasCustomPositionUpdatedAnimation && 90 !it.config.useCustomClockScene 91 ) { 92 blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping) 93 } else { 94 blueprintInteractor.refreshBlueprint(Type.DefaultTransition) 95 } 96 } 97 } 98 } 99 100 launch { 101 if (!MigrateClocksToBlueprint.isEnabled) return@launch 102 viewModel.isAodIconsVisible.collect { 103 viewModel.currentClock.value?.let { 104 if ( 105 viewModel.isLargeClockVisible.value && it.config.useCustomClockScene 106 ) { 107 blueprintInteractor.refreshBlueprint(Type.DefaultTransition) 108 } 109 } 110 } 111 } 112 113 launch { 114 if (!MigrateClocksToBlueprint.isEnabled) return@launch 115 rootViewModel.burnInModel.collect { burnInModel -> 116 viewModel.currentClock.value?.let { 117 it.largeClock.layout.applyAodBurnIn( 118 AodClockBurnInModel( 119 translationX = burnInModel.translationX.toFloat(), 120 translationY = burnInModel.translationY.toFloat(), 121 scale = burnInModel.scale 122 ) 123 ) 124 } 125 } 126 } 127 } 128 } 129 } 130 131 @VisibleForTesting 132 fun updateBurnInLayer( 133 keyguardRootView: ConstraintLayout, 134 viewModel: KeyguardClockViewModel, 135 clockSize: ClockSize, 136 ) { 137 val burnInLayer = viewModel.burnInLayer 138 val clockController = viewModel.currentClock.value 139 // Large clocks won't be added to or removed from burn in layer 140 // Weather large clock has customized burn in preventing mechanism 141 // Non-weather large clock will only scale and translate vertically 142 clockController?.let { clock -> 143 when (clockSize) { 144 ClockSize.LARGE -> { 145 clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) } 146 } 147 ClockSize.SMALL -> { 148 clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) } 149 } 150 } 151 } 152 viewModel.burnInLayer?.updatePostLayout(keyguardRootView) 153 } 154 155 private fun cleanupClockViews( 156 currentClock: ClockController?, 157 rootView: ConstraintLayout, 158 burnInLayer: Layer? 159 ) { 160 if (lastClock == currentClock) { 161 return 162 } 163 164 lastClock?.let { clock -> 165 clock.smallClock.layout.views.forEach { 166 burnInLayer?.removeView(it) 167 rootView.removeView(it) 168 } 169 clock.largeClock.layout.views.forEach { rootView.removeView(it) } 170 } 171 lastClock = currentClock 172 } 173 174 @VisibleForTesting 175 fun addClockViews( 176 clockController: ClockController?, 177 rootView: ConstraintLayout, 178 ) { 179 // We'll collect the same clock when exiting wallpaper picker without changing clock 180 // so we need to remove clock views from parent before addView again 181 clockController?.let { clock -> 182 clock.smallClock.layout.views.forEach { 183 if (it.parent != null) { 184 (it.parent as ViewGroup).removeView(it) 185 } 186 rootView.addView(it).apply { it.visibility = INVISIBLE } 187 } 188 clock.largeClock.layout.views.forEach { 189 if (it.parent != null) { 190 (it.parent as ViewGroup).removeView(it) 191 } 192 rootView.addView(it).apply { it.visibility = INVISIBLE } 193 } 194 } 195 } 196 197 fun applyConstraints( 198 clockSection: ClockSection, 199 rootView: ConstraintLayout, 200 animated: Boolean, 201 set: TransitionSet? = null, 202 ) { 203 val constraintSet = ConstraintSet().apply { clone(rootView) } 204 clockSection.applyConstraints(constraintSet) 205 if (animated) { 206 set?.let { TransitionManager.beginDelayedTransition(rootView, it) } 207 ?: run { TransitionManager.beginDelayedTransition(rootView) } 208 } 209 constraintSet.applyTo(rootView) 210 } 211 } 212