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