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