1 /* 2 * 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 18 package com.android.systemui.keyguard.ui.binder 19 20 import android.view.View 21 import android.view.ViewGroup 22 import android.view.ViewPropertyAnimator 23 import androidx.lifecycle.Lifecycle 24 import androidx.lifecycle.repeatOnLifecycle 25 import com.android.systemui.R 26 import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel 27 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel 28 import com.android.systemui.lifecycle.repeatWhenAttached 29 import kotlinx.coroutines.ExperimentalCoroutinesApi 30 import kotlinx.coroutines.flow.MutableStateFlow 31 import kotlinx.coroutines.flow.flatMapLatest 32 import kotlinx.coroutines.flow.map 33 import kotlinx.coroutines.launch 34 35 object KeyguardAmbientIndicationAreaViewBinder { 36 /** 37 * Defines interface for an object that acts as the binding between the view and its view-model. 38 * 39 * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after 40 * it is bound. 41 */ 42 interface Binding { 43 /** 44 * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the 45 * indication areas. 46 */ 47 fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> 48 49 /** Notifies that device configuration has changed. */ 50 fun onConfigurationChanged() 51 52 /** Destroys this binding, releases resources, and cancels any coroutines. */ 53 fun destroy() 54 } 55 56 @OptIn(ExperimentalCoroutinesApi::class) 57 fun bind( 58 view: ViewGroup, 59 viewModel: KeyguardAmbientIndicationViewModel, 60 keyguardRootViewModel: KeyguardRootViewModel, 61 ): Binding { 62 val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container) 63 val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) 64 65 val disposableHandle = 66 view.repeatWhenAttached { 67 repeatOnLifecycle(Lifecycle.State.STARTED) { 68 launch { 69 keyguardRootViewModel.alpha.collect { alpha -> 70 ambientIndicationArea?.apply { 71 this.importantForAccessibility = 72 if (alpha == 0f) { 73 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 74 } else { 75 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO 76 } 77 this.alpha = alpha 78 } 79 } 80 } 81 82 launch { 83 viewModel.indicationAreaTranslationX.collect { translationX -> 84 ambientIndicationArea?.translationX = translationX 85 } 86 } 87 88 launch { 89 configurationBasedDimensions 90 .map { it.defaultBurnInPreventionYOffsetPx } 91 .flatMapLatest { defaultBurnInOffsetY -> 92 viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) 93 } 94 .collect { translationY -> 95 ambientIndicationArea?.translationY = translationY 96 } 97 } 98 99 } 100 } 101 102 103 return object : Binding { 104 override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> { 105 return listOf(ambientIndicationArea).mapNotNull { it?.animate() } 106 } 107 108 override fun onConfigurationChanged() { 109 configurationBasedDimensions.value = loadFromResources(view) 110 } 111 112 override fun destroy() { 113 disposableHandle.dispose() 114 } 115 } 116 } 117 118 private fun loadFromResources(view: View): ConfigurationBasedDimensions { 119 return ConfigurationBasedDimensions( 120 defaultBurnInPreventionYOffsetPx = 121 view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), 122 ) 123 } 124 125 private data class ConfigurationBasedDimensions( 126 val defaultBurnInPreventionYOffsetPx: Int, 127 ) 128 }