1 /*
2  * Copyright (C) 2022 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.biometrics
18 
19 import android.annotation.DrawableRes
20 import android.content.Context
21 import android.graphics.drawable.Animatable2
22 import android.graphics.drawable.AnimatedVectorDrawable
23 import android.graphics.drawable.Drawable
24 import android.util.Log
25 import com.airbnb.lottie.LottieAnimationView
26 import com.airbnb.lottie.LottieCompositionFactory
27 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
28 
29 private const val TAG = "AuthIconController"
30 
31 /** Controller for animating the BiometricPrompt icon/affordance. */
32 abstract class AuthIconController(
33     protected val context: Context,
34     protected val iconView: LottieAnimationView
35 ) : Animatable2.AnimationCallback() {
36 
37     /** If this controller should ignore events and pause. */
38     var deactivated: Boolean = false
39 
40     /** If the icon view should be treated as an alternate "confirm" button. */
41     open val actsAsConfirmButton: Boolean = false
42 
43     final override fun onAnimationStart(drawable: Drawable) {
44         super.onAnimationStart(drawable)
45     }
46 
47     final override fun onAnimationEnd(drawable: Drawable) {
48         super.onAnimationEnd(drawable)
49 
50         if (!deactivated) {
51             handleAnimationEnd(drawable)
52         }
53     }
54 
55     /** Set the icon to a static image. */
56     protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
57         iconView.setImageDrawable(context.getDrawable(iconRes))
58     }
59 
60     /** Animate a resource. */
61     protected fun animateIconOnce(@DrawableRes iconRes: Int) {
62         animateIcon(iconRes, false)
63     }
64 
65     /** Animate a resource. */
66     protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
67         if (!deactivated) {
68             val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
69             iconView.setImageDrawable(icon)
70             icon.forceAnimationOnUI()
71             if (repeat) {
72                 icon.registerAnimationCallback(this)
73             }
74             icon.start()
75         }
76     }
77 
78     /** Update the icon to reflect the [newState]. */
79     fun updateState(@BiometricState lastState: Int, @BiometricState newState: Int) {
80         if (deactivated) {
81             Log.w(TAG, "Ignoring updateState when deactivated: $newState")
82         } else {
83             updateIcon(lastState, newState)
84         }
85     }
86 
87     /** Call during [updateState] if the controller is not [deactivated]. */
88     abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int)
89 
90     /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
91     open fun handleAnimationEnd(drawable: Drawable) {}
92 
93     // TODO(b/251476085): Migrate this to an extension at the appropriate level?
94     /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
95     protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
96         for (res in rawResources) {
97             LottieCompositionFactory.fromRawRes(context, res)
98         }
99     }
100 }
101