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.view 18 19 import android.content.Context 20 import android.graphics.drawable.AnimatedStateListDrawable 21 import android.graphics.drawable.AnimatedVectorDrawable 22 import android.util.AttributeSet 23 import android.util.StateSet 24 import android.view.Gravity 25 import android.view.View 26 import android.view.ViewGroup 27 import android.view.accessibility.AccessibilityNodeInfo 28 import android.widget.FrameLayout 29 import android.widget.ImageView 30 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat 31 import com.airbnb.lottie.LottieCompositionFactory 32 import com.airbnb.lottie.LottieDrawable 33 import com.android.systemui.common.ui.view.LongPressHandlingView 34 import com.android.systemui.res.R 35 36 class DeviceEntryIconView 37 @JvmOverloads 38 constructor( 39 context: Context, 40 attrs: AttributeSet?, 41 defStyleAttrs: Int = 0, 42 ) : FrameLayout(context, attrs, defStyleAttrs) { 43 val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) 44 val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } 45 val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } 46 val aodFpDrawable: LottieDrawable = LottieDrawable() 47 var accessibilityHintType: AccessibilityHintType = AccessibilityHintType.NONE 48 49 private var animatedIconDrawable: AnimatedStateListDrawable = AnimatedStateListDrawable() 50 51 init { 52 setupIconStates() 53 setupIconTransitions() 54 setupAccessibilityDelegate() 55 56 // Ordering matters. From background to foreground we want: 57 // bgView, iconView, longpressHandlingView overlay 58 addBgImageView() 59 addIconImageView() 60 addLongpressHandlingView() 61 } 62 63 private fun setupAccessibilityDelegate() { 64 accessibilityDelegate = 65 object : AccessibilityDelegate() { 66 private val accessibilityBouncerHint = 67 AccessibilityNodeInfo.AccessibilityAction( 68 AccessibilityNodeInfoCompat.ACTION_CLICK, 69 resources.getString(R.string.accessibility_bouncer) 70 ) 71 private val accessibilityEnterHint = 72 AccessibilityNodeInfo.AccessibilityAction( 73 AccessibilityNodeInfoCompat.ACTION_CLICK, 74 resources.getString(R.string.accessibility_enter_hint) 75 ) 76 77 override fun onInitializeAccessibilityNodeInfo( 78 v: View, 79 info: AccessibilityNodeInfo 80 ) { 81 super.onInitializeAccessibilityNodeInfo(v, info) 82 when (accessibilityHintType) { 83 AccessibilityHintType.BOUNCER -> info.addAction(accessibilityBouncerHint) 84 AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint) 85 AccessibilityHintType.NONE -> return 86 } 87 } 88 } 89 } 90 91 /** 92 * Setups different icon states. 93 * - All lottie views will require a LottieOnCompositionLoadedListener to update 94 * LottieProperties (like color) of the view. 95 * - Drawable properties can be updated using ImageView properties like imageTintList. 96 */ 97 private fun setupIconStates() { 98 // Lockscreen States 99 // LOCK 100 animatedIconDrawable.addState( 101 getIconState(IconType.LOCK, false), 102 context.getDrawable(R.drawable.ic_lock)!!, 103 R.id.locked, 104 ) 105 // UNLOCK 106 animatedIconDrawable.addState( 107 getIconState(IconType.UNLOCK, false), 108 context.getDrawable(R.drawable.ic_unlocked)!!, 109 R.id.unlocked, 110 ) 111 // FINGERPRINT 112 animatedIconDrawable.addState( 113 getIconState(IconType.FINGERPRINT, false), 114 context.getDrawable(R.drawable.ic_fingerprint)!!, 115 R.id.locked_fp, 116 ) 117 118 // AOD states 119 // LOCK 120 animatedIconDrawable.addState( 121 getIconState(IconType.LOCK, true), 122 context.getDrawable(R.drawable.ic_lock_aod)!!, 123 R.id.locked_aod, 124 ) 125 // UNLOCK 126 animatedIconDrawable.addState( 127 getIconState(IconType.UNLOCK, true), 128 context.getDrawable(R.drawable.ic_unlocked_aod)!!, 129 R.id.unlocked_aod, 130 ) 131 // FINGERPRINT 132 LottieCompositionFactory.fromRawRes(mContext, R.raw.udfps_aod_fp).addListener { result -> 133 aodFpDrawable.setComposition(result) 134 } 135 animatedIconDrawable.addState( 136 getIconState(IconType.FINGERPRINT, true), 137 aodFpDrawable, 138 R.id.udfps_aod_fp, 139 ) 140 141 // WILDCARD: should always be the last state added since any states will match with this 142 // and therefore won't get matched with subsequent states. 143 animatedIconDrawable.addState( 144 StateSet.WILD_CARD, 145 context.getDrawable(R.color.transparent)!!, 146 R.id.no_icon, 147 ) 148 } 149 150 private fun setupIconTransitions() { 151 // LockscreenFp <=> LockscreenUnlocked 152 animatedIconDrawable.addTransition( 153 R.id.locked_fp, 154 R.id.unlocked, 155 context.getDrawable(R.drawable.fp_to_unlock) as AnimatedVectorDrawable, 156 /* reversible */ false, 157 ) 158 animatedIconDrawable.addTransition( 159 R.id.unlocked, 160 R.id.locked_fp, 161 context.getDrawable(R.drawable.unlock_to_fp) as AnimatedVectorDrawable, 162 /* reversible */ false, 163 ) 164 165 // LockscreenLocked <=> AodLocked 166 animatedIconDrawable.addTransition( 167 R.id.locked_aod, 168 R.id.locked, 169 context.getDrawable(R.drawable.lock_aod_to_ls) as AnimatedVectorDrawable, 170 /* reversible */ false, 171 ) 172 animatedIconDrawable.addTransition( 173 R.id.locked, 174 R.id.locked_aod, 175 context.getDrawable(R.drawable.lock_ls_to_aod) as AnimatedVectorDrawable, 176 /* reversible */ false, 177 ) 178 179 // LockscreenUnlocked <=> AodUnlocked 180 animatedIconDrawable.addTransition( 181 R.id.unlocked_aod, 182 R.id.unlocked, 183 context.getDrawable(R.drawable.unlocked_aod_to_ls) as AnimatedVectorDrawable, 184 /* reversible */ false, 185 ) 186 animatedIconDrawable.addTransition( 187 R.id.unlocked, 188 R.id.unlocked_aod, 189 context.getDrawable(R.drawable.unlocked_ls_to_aod) as AnimatedVectorDrawable, 190 /* reversible */ false, 191 ) 192 193 // LockscreenLocked <=> LockscreenUnlocked 194 animatedIconDrawable.addTransition( 195 R.id.locked, 196 R.id.unlocked, 197 context.getDrawable(R.drawable.lock_to_unlock) as AnimatedVectorDrawable, 198 /* reversible */ false, 199 ) 200 animatedIconDrawable.addTransition( 201 R.id.unlocked, 202 R.id.locked, 203 context.getDrawable(R.drawable.unlocked_to_locked) as AnimatedVectorDrawable, 204 /* reversible */ false, 205 ) 206 207 // LockscreenFingerprint => LockscreenLocked 208 animatedIconDrawable.addTransition( 209 R.id.locked_fp, 210 R.id.locked, 211 context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable, 212 /* reversible */ false, 213 ) 214 215 // LockscreenUnlocked <=> AodLocked 216 animatedIconDrawable.addTransition( 217 R.id.unlocked, 218 R.id.locked_aod, 219 context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable, 220 /* reversible */ false, 221 ) 222 } 223 224 private fun addLongpressHandlingView() { 225 addView(longPressHandlingView) 226 val lp = longPressHandlingView.layoutParams as LayoutParams 227 lp.height = ViewGroup.LayoutParams.MATCH_PARENT 228 lp.width = ViewGroup.LayoutParams.MATCH_PARENT 229 longPressHandlingView.layoutParams = lp 230 } 231 232 private fun addIconImageView() { 233 iconView.scaleType = ImageView.ScaleType.CENTER_CROP 234 iconView.setImageDrawable(animatedIconDrawable) 235 addView(iconView) 236 val lp = iconView.layoutParams as LayoutParams 237 lp.height = ViewGroup.LayoutParams.MATCH_PARENT 238 lp.width = ViewGroup.LayoutParams.MATCH_PARENT 239 lp.gravity = Gravity.CENTER 240 iconView.layoutParams = lp 241 } 242 243 private fun addBgImageView() { 244 bgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg)) 245 addView(bgView) 246 val lp = bgView.layoutParams as LayoutParams 247 lp.height = ViewGroup.LayoutParams.MATCH_PARENT 248 lp.width = ViewGroup.LayoutParams.MATCH_PARENT 249 bgView.layoutParams = lp 250 } 251 252 fun getIconState(icon: IconType, aod: Boolean): IntArray { 253 val lockIconState = IntArray(2) 254 when (icon) { 255 IconType.LOCK -> lockIconState[0] = android.R.attr.state_first 256 IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last 257 IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle 258 IconType.NONE -> return StateSet.NOTHING 259 } 260 if (aod) { 261 lockIconState[1] = android.R.attr.state_single 262 } else { 263 lockIconState[1] = -android.R.attr.state_single 264 } 265 return lockIconState 266 } 267 268 enum class IconType(val contentDescriptionResId: Int) { 269 LOCK(R.string.accessibility_lock_icon), 270 UNLOCK(R.string.accessibility_unlock_button), 271 FINGERPRINT(R.string.accessibility_fingerprint_label), 272 NONE(-1), 273 } 274 275 enum class AccessibilityHintType { 276 NONE, 277 BOUNCER, 278 ENTER, 279 } 280 } 281