1 /*
2  * Copyright (C) 2020 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 package com.android.systemui.biometrics
17 
18 import android.content.Context
19 import android.graphics.Canvas
20 import android.graphics.Color
21 import android.graphics.Paint
22 import android.graphics.PointF
23 import android.graphics.Rect
24 import android.graphics.RectF
25 import android.util.AttributeSet
26 import android.util.Log
27 import android.view.MotionEvent
28 import android.widget.FrameLayout
29 import com.android.settingslib.udfps.UdfpsOverlayParams
30 import com.android.systemui.R
31 import com.android.systemui.doze.DozeReceiver
32 
33 private const val TAG = "UdfpsView"
34 
35 /**
36  * The main view group containing all UDFPS animations.
37  */
38 class UdfpsView(
39     context: Context,
40     attrs: AttributeSet?
41 ) : FrameLayout(context, attrs), DozeReceiver {
42 
43     // Use expanded overlay when feature flag is true, set by UdfpsViewController
44     var useExpandedOverlay: Boolean = false
45 
46     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
47     // overlayParams.sensorBounds
48     var sensorRect = Rect()
49     private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
50     private val debugTextPaint = Paint().apply {
51         isAntiAlias = true
52         color = Color.BLUE
53         textSize = 32f
54     }
55 
56     private val sensorTouchAreaCoefficient: Float =
57         context.theme.obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 0).use { a ->
58             require(a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
59                 "UdfpsView must contain sensorTouchAreaCoefficient"
60             }
61             a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f)
62         }
63 
64     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
65     var animationViewController: UdfpsAnimationViewController<*>? = null
66 
67     /** Parameters that affect the position and size of the overlay. */
68     var overlayParams = UdfpsOverlayParams()
69 
70     /** Debug message. */
71     var debugMessage: String? = null
72         set(value) {
73             field = value
74             postInvalidate()
75         }
76 
77     /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
78     var isDisplayConfigured: Boolean = false
79         private set
80 
81     fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) {
82         mUdfpsDisplayMode = udfpsDisplayModeProvider
83     }
84 
85     // Don't propagate any touch events to the child views.
86     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
87         return (animationViewController == null || !animationViewController!!.shouldPauseAuth())
88     }
89 
90     override fun dozeTimeTick() {
91         animationViewController?.dozeTimeTick()
92     }
93 
94     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
95         super.onLayout(changed, left, top, right, bottom)
96 
97         val paddingX = animationViewController?.paddingX ?: 0
98         val paddingY = animationViewController?.paddingY ?: 0
99 
100         // Updates sensor rect in relation to the overlay view
101         if (useExpandedOverlay) {
102             animationViewController?.onSensorRectUpdated(RectF(sensorRect))
103         } else {
104             sensorRect.set(
105                     paddingX,
106                     paddingY,
107                     (overlayParams.sensorBounds.width() + paddingX),
108                     (overlayParams.sensorBounds.height() + paddingY)
109             )
110 
111             animationViewController?.onSensorRectUpdated(RectF(sensorRect))
112         }
113     }
114 
115     override fun onAttachedToWindow() {
116         super.onAttachedToWindow()
117         Log.v(TAG, "onAttachedToWindow")
118     }
119 
120     override fun onDetachedFromWindow() {
121         super.onDetachedFromWindow()
122         Log.v(TAG, "onDetachedFromWindow")
123     }
124 
125     override fun onDraw(canvas: Canvas) {
126         super.onDraw(canvas)
127         if (!isDisplayConfigured) {
128             if (!debugMessage.isNullOrEmpty()) {
129                 canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint)
130             }
131         }
132     }
133 
134     fun isWithinSensorArea(x: Float, y: Float): Boolean {
135         // The X and Y coordinates of the sensor's center.
136         val translation = animationViewController?.touchTranslation ?: PointF(0f, 0f)
137         val cx = sensorRect.centerX() + translation.x
138         val cy = sensorRect.centerY() + translation.y
139         // Radii along the X and Y axes.
140         val rx = (sensorRect.right - sensorRect.left) / 2.0f
141         val ry = (sensorRect.bottom - sensorRect.top) / 2.0f
142 
143         return x > cx - rx * sensorTouchAreaCoefficient &&
144             x < cx + rx * sensorTouchAreaCoefficient &&
145             y > cy - ry * sensorTouchAreaCoefficient &&
146             y < cy + ry * sensorTouchAreaCoefficient &&
147             !(animationViewController?.shouldPauseAuth() ?: false)
148     }
149 
150     fun configureDisplay(onDisplayConfigured: Runnable) {
151         isDisplayConfigured = true
152         animationViewController?.onDisplayConfiguring()
153         mUdfpsDisplayMode?.enable(onDisplayConfigured)
154     }
155 
156     fun unconfigureDisplay() {
157         isDisplayConfigured = false
158         animationViewController?.onDisplayUnconfigured()
159         mUdfpsDisplayMode?.disable(null /* onDisabled */)
160     }
161 }
162