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