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.statusbar.pipeline.shared.ui.view
18 
19 import android.content.Context
20 import android.content.res.ColorStateList
21 import android.graphics.Color
22 import android.util.AttributeSet
23 import android.view.LayoutInflater
24 import android.view.View
25 import android.widget.ImageView
26 import androidx.lifecycle.Lifecycle
27 import androidx.lifecycle.LifecycleOwner
28 import androidx.lifecycle.lifecycleScope
29 import androidx.lifecycle.repeatOnLifecycle
30 import com.android.internal.annotations.VisibleForTesting
31 import com.android.systemui.lifecycle.repeatWhenAttached
32 import com.android.systemui.res.R
33 import com.android.systemui.statusbar.StatusBarIconView
34 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
35 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
36 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
37 import kotlinx.coroutines.awaitCancellation
38 import kotlinx.coroutines.flow.MutableStateFlow
39 import kotlinx.coroutines.launch
40 
41 /** Simple single-icon view that is bound to bindable_status_bar_icon.xml */
42 class SingleBindableStatusBarIconView(
43     context: Context,
44     attrs: AttributeSet?,
45 ) : ModernStatusBarView(context, attrs) {
46 
47     internal lateinit var iconView: ImageView
48     internal lateinit var dotView: StatusBarIconView
49 
50     override fun toString(): String {
51         return "SingleBindableStatusBarIcon(" +
52             "slot='$slot', " +
53             "isCollecting=${binding.isCollecting()}, " +
54             "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
55             "viewString=${super.toString()}"
56     }
57 
58     override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
59         super.initView(slot, bindingCreator)
60 
61         iconView = requireViewById(R.id.icon_view)
62         dotView = requireViewById(R.id.status_bar_dot)
63     }
64 
65     companion object {
66         fun createView(
67             context: Context,
68         ): SingleBindableStatusBarIconView {
69             return LayoutInflater.from(context).inflate(R.layout.bindable_status_bar_icon, null)
70                 as SingleBindableStatusBarIconView
71         }
72 
73         /**
74          * Using a given binding [block], create the necessary scaffolding to handle the general
75          * case of a single status bar icon. This includes eliding into a dot view when there is not
76          * enough space, and handling tint.
77          *
78          * [block] should be a simple [launch] call that handles updating the single icon view with
79          * its new view. Currently there is no simple way to e.g., extend to handle multiple tints
80          * for dual-layered icons, and any more complex logic should probably find a way to return
81          * its own version of [ModernStatusBarViewBinding].
82          */
83         fun withDefaultBinding(
84             view: SingleBindableStatusBarIconView,
85             shouldBeVisible: () -> Boolean,
86             block: suspend LifecycleOwner.(View) -> Unit
87         ): SingleBindableStatusBarIconViewBinding {
88             @StatusBarIconView.VisibleState
89             val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
90 
91             val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
92             val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
93 
94             var isCollecting: Boolean = false
95 
96             view.repeatWhenAttached {
97                 // Child binding
98                 block(view)
99 
100                 lifecycleScope.launch {
101                     repeatOnLifecycle(Lifecycle.State.STARTED) {
102                         // isVisible controls the visibility state of the outer group, and thus it
103                         // needs
104                         // to run in the CREATED lifecycle so it can continue to watch while
105                         // invisible
106                         // See (b/291031862) for details
107                         launch {
108                             visibilityState.collect { visibilityState ->
109                                 // for b/296864006, we can not hide all the child views if
110                                 // visibilityState is STATE_HIDDEN. Because hiding all child views
111                                 // would cause the
112                                 // getWidth() of this view return 0, and that would cause the
113                                 // translation
114                                 // calculation fails in StatusIconContainer. Therefore, like class
115                                 // MobileIconBinder, instead of set the child views visibility to
116                                 // View.GONE,
117                                 // we set their visibility to View.INVISIBLE to make them invisible
118                                 // but
119                                 // keep the width.
120                                 ModernStatusBarViewVisibilityHelper.setVisibilityState(
121                                     visibilityState,
122                                     view.iconView,
123                                     view.dotView,
124                                 )
125                             }
126                         }
127 
128                         launch {
129                             iconTint.collect { tint ->
130                                 val tintList = ColorStateList.valueOf(tint)
131                                 view.iconView.imageTintList = tintList
132                                 view.dotView.setDecorColor(tint)
133                             }
134                         }
135 
136                         launch {
137                             decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) }
138                         }
139 
140                         try {
141                             awaitCancellation()
142                         } finally {
143                             isCollecting = false
144                         }
145                     }
146                 }
147             }
148 
149             return object : SingleBindableStatusBarIconViewBinding {
150                 override val decorTint: Int
151                     get() = decorTint.value
152 
153                 override val iconTint: Int
154                     get() = iconTint.value
155 
156                 override fun getShouldIconBeVisible(): Boolean {
157                     return shouldBeVisible()
158                 }
159 
160                 override fun onVisibilityStateChanged(state: Int) {
161                     visibilityState.value = state
162                 }
163 
164                 override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
165                     iconTint.value = newTint
166                 }
167 
168                 override fun onDecorTintChanged(newTint: Int) {
169                     decorTint.value = newTint
170                 }
171 
172                 override fun isCollecting(): Boolean {
173                     return isCollecting
174                 }
175             }
176         }
177     }
178 }
179 
180 @VisibleForTesting
181 interface SingleBindableStatusBarIconViewBinding : ModernStatusBarViewBinding {
182     val iconTint: Int
183     val decorTint: Int
184 }
185