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 
18 package com.android.systemui.biometrics.ui.viewmodel
19 
20 import android.annotation.DrawableRes
21 import android.annotation.RawRes
22 import android.content.res.Configuration
23 import android.graphics.Rect
24 import android.hardware.face.Face
25 import android.util.RotationUtils
26 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
27 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
28 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
29 import com.android.systemui.biometrics.shared.model.DisplayRotation
30 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
31 import com.android.systemui.res.R
32 import com.android.systemui.util.kotlin.combine
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.combine
36 import kotlinx.coroutines.flow.distinctUntilChanged
37 import kotlinx.coroutines.flow.flatMapLatest
38 import kotlinx.coroutines.flow.flowOf
39 
40 /**
41  * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
42  */
43 class PromptIconViewModel
44 constructor(
45     promptViewModel: PromptViewModel,
46     private val displayStateInteractor: DisplayStateInteractor,
47     promptSelectorInteractor: PromptSelectorInteractor,
48     udfpsOverlayInteractor: UdfpsOverlayInteractor,
49 ) {
50 
51     /** Auth types for the UI to display. */
52     enum class AuthType {
53         Fingerprint,
54         Face,
55         Coex
56     }
57 
58     /**
59      * Indicates what auth type the UI currently displays. Fingerprint-only auth -> Fingerprint
60      * Face-only auth -> Face Co-ex auth, implicit flow -> Face Co-ex auth, explicit flow -> Coex
61      */
62     val activeAuthType: Flow<AuthType> =
63         combine(
64             promptViewModel.modalities.distinctUntilChanged(),
65             promptViewModel.faceMode.distinctUntilChanged()
66         ) { modalities, faceMode ->
67             if (modalities.hasFaceAndFingerprint && !faceMode) {
68                 AuthType.Coex
69             } else if (modalities.hasFaceOnly || faceMode) {
70                 AuthType.Face
71             } else if (modalities.hasFingerprintOnly) {
72                 AuthType.Fingerprint
73             } else {
74                 // TODO(b/288175072): Remove, currently needed for transition to credential view
75                 AuthType.Fingerprint
76             }
77         }
78 
79     val udfpsSensorBounds: Flow<Rect> =
80         combine(
81                 udfpsOverlayInteractor.udfpsOverlayParams,
82                 displayStateInteractor.currentRotation
83             ) { params, rotation ->
84                 val rotatedBounds = Rect(params.sensorBounds)
85                 RotationUtils.rotateBounds(
86                     rotatedBounds,
87                     params.naturalDisplayWidth,
88                     params.naturalDisplayHeight,
89                     rotation.ordinal
90                 )
91                 Rect(
92                     rotatedBounds.left,
93                     rotatedBounds.top,
94                     params.logicalDisplayWidth - rotatedBounds.right,
95                     params.logicalDisplayHeight - rotatedBounds.bottom
96                 )
97             }
98             .distinctUntilChanged()
99 
100     /** Whether an error message is currently being shown. */
101     val showingError = promptViewModel.showingError
102 
103     /** Whether the previous icon shown displayed an error. */
104     private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
105 
106     /** Whether the previous icon overlay shown displayed an error. */
107     private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
108 
109     fun setPreviousIconWasError(previousIconWasError: Boolean) {
110         _previousIconWasError.value = previousIconWasError
111     }
112 
113     fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
114         _previousIconOverlayWasError.value = previousIconOverlayWasError
115     }
116 
117     val iconSize: Flow<Pair<Int, Int>> =
118         combine(
119             promptViewModel.position,
120             activeAuthType,
121             promptViewModel.legacyFingerprintSensorWidth,
122             promptViewModel.legacyFingerprintSensorHeight,
123         ) { _, activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
124             if (activeAuthType == AuthType.Face) {
125                 Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight)
126             } else {
127                 Pair(fingerprintSensorWidth, fingerprintSensorHeight)
128             }
129         }
130 
131     /** Current BiometricPromptLayout.iconView asset. */
132     val iconAsset: Flow<Int> =
133         activeAuthType.flatMapLatest { activeAuthType: AuthType ->
134             when (activeAuthType) {
135                 AuthType.Fingerprint ->
136                     combine(
137                         displayStateInteractor.currentRotation,
138                         displayStateInteractor.isFolded,
139                         displayStateInteractor.isInRearDisplayMode,
140                         promptSelectorInteractor.sensorType,
141                         promptViewModel.isAuthenticated,
142                         promptViewModel.isAuthenticating,
143                         promptViewModel.showingError
144                     ) {
145                         rotation: DisplayRotation,
146                         isFolded: Boolean,
147                         isInRearDisplayMode: Boolean,
148                         sensorType: FingerprintSensorType,
149                         authState: PromptAuthState,
150                         isAuthenticating: Boolean,
151                         showingError: Boolean ->
152                         when (sensorType) {
153                             FingerprintSensorType.POWER_BUTTON ->
154                                 getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
155                             else ->
156                                 getFingerprintIconViewAsset(
157                                     authState.isAuthenticated,
158                                     isAuthenticating,
159                                     showingError
160                                 )
161                         }
162                     }
163                 AuthType.Face ->
164                     combine(
165                         promptViewModel.isAuthenticated.distinctUntilChanged(),
166                         promptViewModel.isAuthenticating.distinctUntilChanged(),
167                         promptViewModel.isPendingConfirmation.distinctUntilChanged(),
168                         promptViewModel.showingError.distinctUntilChanged()
169                     ) {
170                         authState: PromptAuthState,
171                         isAuthenticating: Boolean,
172                         isPendingConfirmation: Boolean,
173                         showingError: Boolean ->
174                         getFaceIconViewAsset(
175                             authState,
176                             isAuthenticating,
177                             isPendingConfirmation,
178                             showingError
179                         )
180                     }
181                 AuthType.Coex ->
182                     combine(
183                         displayStateInteractor.currentRotation,
184                         displayStateInteractor.isFolded,
185                         displayStateInteractor.isInRearDisplayMode,
186                         promptSelectorInteractor.sensorType,
187                         promptViewModel.isAuthenticated,
188                         promptViewModel.isAuthenticating,
189                         promptViewModel.isPendingConfirmation,
190                         promptViewModel.showingError,
191                     ) {
192                         rotation: DisplayRotation,
193                         isFolded: Boolean,
194                         isInRearDisplayMode: Boolean,
195                         sensorType: FingerprintSensorType,
196                         authState: PromptAuthState,
197                         isAuthenticating: Boolean,
198                         isPendingConfirmation: Boolean,
199                         showingError: Boolean ->
200                         when (sensorType) {
201                             FingerprintSensorType.POWER_BUTTON ->
202                                 getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
203                             else ->
204                                 getCoexIconViewAsset(
205                                     authState,
206                                     isAuthenticating,
207                                     isPendingConfirmation,
208                                     showingError
209                                 )
210                         }
211                     }
212             }
213         }
214 
215     private fun getFingerprintIconViewAsset(
216         isAuthenticated: Boolean,
217         isAuthenticating: Boolean,
218         showingError: Boolean
219     ): Int =
220         if (isAuthenticated) {
221             if (_previousIconWasError.value) {
222                 R.raw.fingerprint_dialogue_error_to_success_lottie
223             } else {
224                 R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
225             }
226         } else if (isAuthenticating) {
227             if (_previousIconWasError.value) {
228                 R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
229             } else {
230                 R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
231             }
232         } else if (showingError) {
233             R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
234         } else {
235             -1
236         }
237 
238     @RawRes
239     private fun getSfpsIconViewAsset(
240         rotation: DisplayRotation,
241         isDeviceFolded: Boolean,
242         isInRearDisplayMode: Boolean,
243     ): Int =
244         when (rotation) {
245             DisplayRotation.ROTATION_90 ->
246                 if (isInRearDisplayMode) {
247                     R.raw.biometricprompt_rear_portrait_reverse_base
248                 } else if (isDeviceFolded) {
249                     R.raw.biometricprompt_folded_base_topleft
250                 } else {
251                     R.raw.biometricprompt_portrait_base_topleft
252                 }
253             DisplayRotation.ROTATION_270 ->
254                 if (isInRearDisplayMode) {
255                     R.raw.biometricprompt_rear_portrait_base
256                 } else if (isDeviceFolded) {
257                     R.raw.biometricprompt_folded_base_bottomright
258                 } else {
259                     R.raw.biometricprompt_portrait_base_bottomright
260                 }
261             else ->
262                 if (isInRearDisplayMode) {
263                     R.raw.biometricprompt_rear_landscape_base
264                 } else if (isDeviceFolded) {
265                     R.raw.biometricprompt_folded_base_default
266                 } else {
267                     R.raw.biometricprompt_landscape_base
268                 }
269         }
270 
271     @DrawableRes
272     private fun getFaceIconViewAsset(
273         authState: PromptAuthState,
274         isAuthenticating: Boolean,
275         isPendingConfirmation: Boolean,
276         showingError: Boolean
277     ): Int =
278         if (authState.isAuthenticated && isPendingConfirmation) {
279             R.drawable.face_dialog_wink_from_dark
280         } else if (authState.isAuthenticated) {
281             R.drawable.face_dialog_dark_to_checkmark
282         } else if (isAuthenticating) {
283             R.raw.face_dialog_authenticating
284         } else if (showingError) {
285             R.drawable.face_dialog_dark_to_error
286         } else if (_previousIconWasError.value) {
287             R.drawable.face_dialog_error_to_idle
288         } else {
289             R.drawable.face_dialog_idle_static
290         }
291 
292     @RawRes
293     private fun getCoexIconViewAsset(
294         authState: PromptAuthState,
295         isAuthenticating: Boolean,
296         isPendingConfirmation: Boolean,
297         showingError: Boolean
298     ): Int =
299         if (authState.isAuthenticatedAndExplicitlyConfirmed) {
300             R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
301         } else if (isPendingConfirmation) {
302             if (_previousIconWasError.value) {
303                 R.raw.fingerprint_dialogue_error_to_unlock_lottie
304             } else {
305                 R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
306             }
307         } else if (authState.isAuthenticated) {
308             if (_previousIconWasError.value) {
309                 R.raw.fingerprint_dialogue_error_to_success_lottie
310             } else {
311                 R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
312             }
313         } else if (isAuthenticating) {
314             if (_previousIconWasError.value) {
315                 R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
316             } else {
317                 R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
318             }
319         } else if (showingError) {
320             R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
321         } else {
322             -1
323         }
324 
325     /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
326     var iconOverlayAsset: Flow<Int> =
327         activeAuthType.flatMapLatest { activeAuthType: AuthType ->
328             when (activeAuthType) {
329                 AuthType.Fingerprint,
330                 AuthType.Coex ->
331                     combine(
332                         displayStateInteractor.currentRotation,
333                         promptSelectorInteractor.sensorType,
334                         promptViewModel.isAuthenticated,
335                         promptViewModel.isAuthenticating,
336                         promptViewModel.showingError
337                     ) {
338                         rotation: DisplayRotation,
339                         sensorType: FingerprintSensorType,
340                         authState: PromptAuthState,
341                         isAuthenticating: Boolean,
342                         showingError: Boolean ->
343                         when (sensorType) {
344                             FingerprintSensorType.POWER_BUTTON ->
345                                 getSfpsIconOverlayAsset(
346                                     rotation,
347                                     authState.isAuthenticated,
348                                     isAuthenticating,
349                                     showingError
350                                 )
351                             else -> -1
352                         }
353                     }
354                 AuthType.Face -> flowOf(-1)
355             }
356         }
357 
358     @RawRes
359     private fun getSfpsIconOverlayAsset(
360         rotation: DisplayRotation,
361         isAuthenticated: Boolean,
362         isAuthenticating: Boolean,
363         showingError: Boolean
364     ): Int =
365         if (isAuthenticated) {
366             if (_previousIconOverlayWasError.value) {
367                 when (rotation) {
368                     DisplayRotation.ROTATION_0 ->
369                         R.raw.biometricprompt_symbol_error_to_success_landscape
370                     DisplayRotation.ROTATION_90 ->
371                         R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
372                     DisplayRotation.ROTATION_180 ->
373                         R.raw.biometricprompt_symbol_error_to_success_landscape
374                     DisplayRotation.ROTATION_270 ->
375                         R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
376                 }
377             } else {
378                 when (rotation) {
379                     DisplayRotation.ROTATION_0 ->
380                         R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
381                     DisplayRotation.ROTATION_90 ->
382                         R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
383                     DisplayRotation.ROTATION_180 ->
384                         R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
385                     DisplayRotation.ROTATION_270 ->
386                         R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
387                 }
388             }
389         } else if (isAuthenticating) {
390             if (_previousIconOverlayWasError.value) {
391                 when (rotation) {
392                     DisplayRotation.ROTATION_0 ->
393                         R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
394                     DisplayRotation.ROTATION_90 ->
395                         R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
396                     DisplayRotation.ROTATION_180 ->
397                         R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
398                     DisplayRotation.ROTATION_270 ->
399                         R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
400                 }
401             } else {
402                 when (rotation) {
403                     DisplayRotation.ROTATION_0 ->
404                         R.raw.biometricprompt_fingerprint_to_error_landscape
405                     DisplayRotation.ROTATION_90 ->
406                         R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
407                     DisplayRotation.ROTATION_180 ->
408                         R.raw.biometricprompt_fingerprint_to_error_landscape
409                     DisplayRotation.ROTATION_270 ->
410                         R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
411                 }
412             }
413         } else if (showingError) {
414             when (rotation) {
415                 DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
416                 DisplayRotation.ROTATION_90 ->
417                     R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
418                 DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
419                 DisplayRotation.ROTATION_270 ->
420                     R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
421             }
422         } else {
423             -1
424         }
425 
426     /** Content description for iconView */
427     val contentDescriptionId: Flow<Int> =
428         activeAuthType.flatMapLatest { activeAuthType: AuthType ->
429             when (activeAuthType) {
430                 AuthType.Fingerprint,
431                 AuthType.Coex ->
432                     combine(
433                         promptSelectorInteractor.sensorType,
434                         promptViewModel.isAuthenticated,
435                         promptViewModel.isAuthenticating,
436                         promptViewModel.isPendingConfirmation,
437                         promptViewModel.showingError
438                     ) {
439                         sensorType: FingerprintSensorType,
440                         authState: PromptAuthState,
441                         isAuthenticating: Boolean,
442                         isPendingConfirmation: Boolean,
443                         showingError: Boolean ->
444                         getFingerprintIconContentDescriptionId(
445                             sensorType,
446                             authState.isAuthenticated,
447                             isAuthenticating,
448                             isPendingConfirmation,
449                             showingError
450                         )
451                     }
452                 AuthType.Face ->
453                     combine(
454                         promptViewModel.isAuthenticated,
455                         promptViewModel.isAuthenticating,
456                         promptViewModel.showingError,
457                     ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
458                         ->
459                         getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
460                     }
461             }
462         }
463 
464     private fun getFingerprintIconContentDescriptionId(
465         sensorType: FingerprintSensorType,
466         isAuthenticated: Boolean,
467         isAuthenticating: Boolean,
468         isPendingConfirmation: Boolean,
469         showingError: Boolean
470     ): Int =
471         if (isPendingConfirmation) {
472             when (sensorType) {
473                 FingerprintSensorType.POWER_BUTTON -> -1
474                 else -> R.string.fingerprint_dialog_authenticated_confirmation
475             }
476         } else if (isAuthenticating || isAuthenticated) {
477             when (sensorType) {
478                 FingerprintSensorType.POWER_BUTTON ->
479                     R.string.security_settings_sfps_enroll_find_sensor_message
480                 else -> R.string.fingerprint_dialog_touch_sensor
481             }
482         } else if (showingError) {
483             R.string.biometric_dialog_try_again
484         } else {
485             -1
486         }
487 
488     private fun getFaceIconContentDescriptionId(
489         authState: PromptAuthState,
490         isAuthenticating: Boolean,
491         showingError: Boolean
492     ): Int =
493         if (authState.isAuthenticatedAndExplicitlyConfirmed) {
494             R.string.biometric_dialog_face_icon_description_confirmed
495         } else if (authState.isAuthenticated) {
496             R.string.biometric_dialog_face_icon_description_authenticated
497         } else if (isAuthenticating) {
498             R.string.biometric_dialog_face_icon_description_authenticating
499         } else if (showingError) {
500             R.string.keyguard_face_failed
501         } else {
502             R.string.biometric_dialog_face_icon_description_idle
503         }
504 
505     /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
506     val shouldAnimateIconView: Flow<Boolean> =
507         activeAuthType.flatMapLatest { activeAuthType: AuthType ->
508             when (activeAuthType) {
509                 AuthType.Fingerprint ->
510                     combine(
511                         promptSelectorInteractor.sensorType,
512                         promptViewModel.isAuthenticated,
513                         promptViewModel.isAuthenticating,
514                         promptViewModel.showingError
515                     ) {
516                         sensorType: FingerprintSensorType,
517                         authState: PromptAuthState,
518                         isAuthenticating: Boolean,
519                         showingError: Boolean ->
520                         when (sensorType) {
521                             FingerprintSensorType.POWER_BUTTON ->
522                                 shouldAnimateSfpsIconView(
523                                     authState.isAuthenticated,
524                                     isAuthenticating,
525                                     showingError
526                                 )
527                             else ->
528                                 shouldAnimateFingerprintIconView(
529                                     authState.isAuthenticated,
530                                     isAuthenticating,
531                                     showingError
532                                 )
533                         }
534                     }
535                 AuthType.Face ->
536                     combine(
537                         promptViewModel.isAuthenticated,
538                         promptViewModel.isAuthenticating,
539                         promptViewModel.showingError
540                     ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
541                         ->
542                         isAuthenticating ||
543                             authState.isAuthenticated ||
544                             showingError ||
545                             _previousIconWasError.value
546                     }
547                 AuthType.Coex ->
548                     combine(
549                         promptSelectorInteractor.sensorType,
550                         promptViewModel.isAuthenticated,
551                         promptViewModel.isAuthenticating,
552                         promptViewModel.isPendingConfirmation,
553                         promptViewModel.showingError,
554                     ) {
555                         sensorType: FingerprintSensorType,
556                         authState: PromptAuthState,
557                         isAuthenticating: Boolean,
558                         isPendingConfirmation: Boolean,
559                         showingError: Boolean ->
560                         when (sensorType) {
561                             FingerprintSensorType.POWER_BUTTON ->
562                                 shouldAnimateSfpsIconView(
563                                     authState.isAuthenticated,
564                                     isAuthenticating,
565                                     showingError
566                                 )
567                             else ->
568                                 shouldAnimateCoexIconView(
569                                     authState.isAuthenticated,
570                                     isAuthenticating,
571                                     isPendingConfirmation,
572                                     showingError
573                                 )
574                         }
575                     }
576             }
577         }
578 
579     private fun shouldAnimateFingerprintIconView(
580         isAuthenticated: Boolean,
581         isAuthenticating: Boolean,
582         showingError: Boolean
583     ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
584 
585     private fun shouldAnimateSfpsIconView(
586         isAuthenticated: Boolean,
587         isAuthenticating: Boolean,
588         showingError: Boolean
589     ) = isAuthenticated || isAuthenticating || showingError
590 
591     private fun shouldAnimateCoexIconView(
592         isAuthenticated: Boolean,
593         isAuthenticating: Boolean,
594         isPendingConfirmation: Boolean,
595         showingError: Boolean
596     ) =
597         (isAuthenticating && _previousIconWasError.value) ||
598             isPendingConfirmation ||
599             isAuthenticated ||
600             showingError
601 
602     /** Whether the current iconOverlayAsset animation should be playing. */
603     val shouldAnimateIconOverlay: Flow<Boolean> =
604         activeAuthType.flatMapLatest { activeAuthType: AuthType ->
605             when (activeAuthType) {
606                 AuthType.Fingerprint,
607                 AuthType.Coex ->
608                     combine(
609                         promptSelectorInteractor.sensorType,
610                         promptViewModel.isAuthenticated,
611                         promptViewModel.isAuthenticating,
612                         promptViewModel.showingError
613                     ) {
614                         sensorType: FingerprintSensorType,
615                         authState: PromptAuthState,
616                         isAuthenticating: Boolean,
617                         showingError: Boolean ->
618                         when (sensorType) {
619                             FingerprintSensorType.POWER_BUTTON ->
620                                 shouldAnimateSfpsIconOverlay(
621                                     authState.isAuthenticated,
622                                     isAuthenticating,
623                                     showingError
624                                 )
625                             else -> false
626                         }
627                     }
628                 AuthType.Face -> flowOf(false)
629             }
630         }
631 
632     private fun shouldAnimateSfpsIconOverlay(
633         isAuthenticated: Boolean,
634         isAuthenticating: Boolean,
635         showingError: Boolean
636     ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
637 
638     /** Whether the iconView should be flipped due to a device using reverse default rotation . */
639     val shouldFlipIconView: Flow<Boolean> =
640         activeAuthType.flatMapLatest { activeAuthType: AuthType ->
641             when (activeAuthType) {
642                 AuthType.Fingerprint,
643                 AuthType.Coex ->
644                     combine(
645                         promptSelectorInteractor.sensorType,
646                         displayStateInteractor.currentRotation
647                     ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
648                         when (sensorType) {
649                             FingerprintSensorType.POWER_BUTTON ->
650                                 (rotation == DisplayRotation.ROTATION_180)
651                             else -> false
652                         }
653                     }
654                 AuthType.Face -> flowOf(false)
655             }
656         }
657 
658     /** Called on configuration changes */
659     fun onConfigurationChanged(newConfig: Configuration) {
660         displayStateInteractor.onConfigurationChanged(newConfig)
661     }
662 
663     /** iconView assets for caching */
664     fun getRawAssets(hasSfps: Boolean): List<Int> {
665         return if (hasSfps) {
666             listOf(
667                 R.raw.biometricprompt_fingerprint_to_error_landscape,
668                 R.raw.biometricprompt_folded_base_bottomright,
669                 R.raw.biometricprompt_folded_base_default,
670                 R.raw.biometricprompt_folded_base_topleft,
671                 R.raw.biometricprompt_landscape_base,
672                 R.raw.biometricprompt_portrait_base_bottomright,
673                 R.raw.biometricprompt_portrait_base_topleft,
674                 R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
675                 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
676                 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
677                 R.raw.biometricprompt_symbol_error_to_success_landscape,
678                 R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
679                 R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
680                 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
681                 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
682                 R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
683                 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
684                 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
685             )
686         } else {
687             listOf(
688                 R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
689                 R.raw.fingerprint_dialogue_error_to_success_lottie,
690                 R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
691                 R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
692             )
693         }
694     }
695 }
696