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