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.util.TypedValue 20 import android.view.View 21 import android.view.ViewGroup 22 import android.widget.TextView 23 import androidx.lifecycle.Lifecycle 24 import androidx.lifecycle.repeatOnLifecycle 25 import com.android.app.tracing.coroutines.launch 26 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor 27 import com.android.systemui.keyguard.MigrateClocksToBlueprint 28 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel 29 import com.android.systemui.lifecycle.repeatWhenAttached 30 import com.android.systemui.res.R 31 import com.android.systemui.statusbar.KeyguardIndicationController 32 import kotlinx.coroutines.DisposableHandle 33 import kotlinx.coroutines.ExperimentalCoroutinesApi 34 import kotlinx.coroutines.flow.MutableStateFlow 35 import kotlinx.coroutines.flow.combine 36 import kotlinx.coroutines.flow.flatMapLatest 37 import kotlinx.coroutines.flow.map 38 39 /** 40 * Binds a keyguard indication area view to its view-model. 41 * 42 * To use this properly, users should maintain a one-to-one relationship between the [View] and the 43 * view-binding, binding each view only once. It is okay and expected for the same instance of the 44 * view-model to be reused for multiple view/view-binder bindings. 45 */ 46 @OptIn(ExperimentalCoroutinesApi::class) 47 object KeyguardIndicationAreaBinder { 48 49 /** Binds the view to the view-model, continuing to update the former based on the latter. */ 50 @JvmStatic 51 fun bind( 52 view: ViewGroup, 53 viewModel: KeyguardIndicationAreaViewModel, 54 indicationController: KeyguardIndicationController, 55 ): DisposableHandle { 56 indicationController.setIndicationArea(view) 57 58 val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text) 59 val indicationTextBottom: TextView = 60 view.requireViewById(R.id.keyguard_indication_text_bottom) 61 62 view.clipChildren = false 63 view.clipToPadding = false 64 65 val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) 66 val disposableHandle = 67 view.repeatWhenAttached { 68 repeatOnLifecycle(Lifecycle.State.STARTED) { 69 launch("$TAG#viewModel.alpha") { 70 // Do not independently apply alpha, as [KeyguardRootViewModel] should work 71 // for this and all its children 72 if ( 73 !(MigrateClocksToBlueprint.isEnabled || 74 KeyguardBottomAreaRefactor.isEnabled) 75 ) { 76 viewModel.alpha.collect { alpha -> view.alpha = alpha } 77 } 78 } 79 80 launch("$TAG#viewModel.indicationAreaTranslationX") { 81 viewModel.indicationAreaTranslationX.collect { translationX -> 82 view.translationX = translationX 83 } 84 } 85 86 launch("$TAG#viewModel.isIndicationAreaPadded") { 87 combine( 88 viewModel.isIndicationAreaPadded, 89 configurationBasedDimensions.map { it.indicationAreaPaddingPx }, 90 ) { isPadded, paddingIfPaddedPx -> 91 if (isPadded) { 92 paddingIfPaddedPx 93 } else { 94 0 95 } 96 } 97 .collect { paddingPx -> view.setPadding(paddingPx, 0, paddingPx, 0) } 98 } 99 100 launch("$TAG#viewModel.indicationAreaTranslationY") { 101 configurationBasedDimensions 102 .map { it.defaultBurnInPreventionYOffsetPx } 103 .flatMapLatest { defaultBurnInOffsetY -> 104 viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) 105 } 106 .collect { translationY -> view.translationY = translationY } 107 } 108 109 launch("$TAG#indicationText.setTextSize") { 110 configurationBasedDimensions.collect { dimensions -> 111 indicationText.setTextSize( 112 TypedValue.COMPLEX_UNIT_PX, 113 dimensions.indicationTextSizePx.toFloat(), 114 ) 115 indicationTextBottom.setTextSize( 116 TypedValue.COMPLEX_UNIT_PX, 117 dimensions.indicationTextSizePx.toFloat(), 118 ) 119 } 120 } 121 122 launch("$TAG#viewModel.configurationChange") { 123 viewModel.configurationChange.collect { 124 configurationBasedDimensions.value = loadFromResources(view) 125 } 126 } 127 } 128 } 129 return disposableHandle 130 } 131 132 private fun loadFromResources(view: View): ConfigurationBasedDimensions { 133 return ConfigurationBasedDimensions( 134 defaultBurnInPreventionYOffsetPx = 135 view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), 136 indicationAreaPaddingPx = 137 view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding), 138 indicationTextSizePx = 139 view.resources.getDimensionPixelSize( 140 com.android.internal.R.dimen.text_size_small_material, 141 ), 142 ) 143 } 144 145 private data class ConfigurationBasedDimensions( 146 val defaultBurnInPreventionYOffsetPx: Int, 147 val indicationAreaPaddingPx: Int, 148 val indicationTextSizePx: Int, 149 ) 150 151 private const val TAG = "KeyguardIndicationAreaBinder" 152 } 153