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.udfps 18 19 import android.graphics.Point 20 import android.graphics.Rect 21 import androidx.annotation.VisibleForTesting 22 import com.android.systemui.dagger.SysUISingleton 23 import kotlin.math.cos 24 import kotlin.math.pow 25 import kotlin.math.sin 26 27 /** 28 * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap 29 * with the sensor. 30 */ 31 @SysUISingleton 32 class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector { 33 isGoodOverlapnull34 override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { 35 val points = calculateSensorPoints(nativeSensorBounds) 36 return points.count { checkPoint(it, touchData) } >= neededPoints 37 } 38 checkPointnull39 private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean { 40 // Calculate if sensor point is within ellipse 41 // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE - 42 // yS))^2 / b^2) <= 1 43 val a: Float = cos(touchData.orientation) * (point.x - touchData.x) 44 val b: Float = sin(touchData.orientation) * (point.y - touchData.y) 45 val c: Float = sin(touchData.orientation) * (point.x - touchData.x) 46 val d: Float = cos(touchData.orientation) * (point.y - touchData.y) 47 val result = 48 (a + b).pow(2) / (touchData.minor / 2).pow(2) + 49 (c - d).pow(2) / (touchData.major / 2).pow(2) 50 51 return result <= 1 52 } 53 54 @VisibleForTesting calculateSensorPointsnull55 fun calculateSensorPoints(sensorBounds: Rect): List<Point> { 56 val sensorX = sensorBounds.centerX() 57 val sensorY = sensorBounds.centerY() 58 val cornerOffset: Int = sensorBounds.width() / 4 59 val sideOffset: Int = sensorBounds.width() / 3 60 61 return listOf( 62 Point(sensorX - cornerOffset, sensorY - cornerOffset), 63 Point(sensorX, sensorY - sideOffset), 64 Point(sensorX + cornerOffset, sensorY - cornerOffset), 65 Point(sensorX - sideOffset, sensorY), 66 Point(sensorX, sensorY), 67 Point(sensorX + sideOffset, sensorY), 68 Point(sensorX - cornerOffset, sensorY + cornerOffset), 69 Point(sensorX, sensorY + sideOffset), 70 Point(sensorX + cornerOffset, sensorY + cornerOffset) 71 ) 72 } 73 } 74