• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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