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