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 }