• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.PointF
20 import android.util.RotationUtils
21 import android.view.MotionEvent
22 import android.view.MotionEvent.INVALID_POINTER_ID
23 import android.view.Surface
24 import com.android.systemui.biometrics.UdfpsOverlayParams
25 import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure
26 import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
27 import com.android.systemui.dagger.SysUISingleton
28 import javax.inject.Inject
29 
30 private val SUPPORTED_ROTATIONS = setOf(Surface.ROTATION_90, Surface.ROTATION_270)
31 
32 /**
33  * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations.
34  */
35 @SysUISingleton
36 class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: OverlapDetector) :
37     TouchProcessor {
38 
processTouchnull39     override fun processTouch(
40         event: MotionEvent,
41         previousPointerOnSensorId: Int,
42         overlayParams: UdfpsOverlayParams,
43     ): TouchProcessorResult {
44 
45         fun preprocess(): PreprocessedTouch {
46             val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) }
47             val pointersOnSensor =
48                 touchData
49                     .filter { overlapDetector.isGoodOverlap(it, overlayParams.nativeSensorBounds) }
50                     .map { it.pointerId }
51             return PreprocessedTouch(touchData, previousPointerOnSensorId, pointersOnSensor)
52         }
53 
54         return when (event.actionMasked) {
55             MotionEvent.ACTION_DOWN,
56             MotionEvent.ACTION_POINTER_DOWN,
57             MotionEvent.ACTION_MOVE,
58             MotionEvent.ACTION_HOVER_ENTER,
59             MotionEvent.ACTION_HOVER_MOVE -> processActionMove(preprocess())
60             MotionEvent.ACTION_UP,
61             MotionEvent.ACTION_POINTER_UP,
62             MotionEvent.ACTION_HOVER_EXIT ->
63                 processActionUp(preprocess(), event.getPointerId(event.actionIndex))
64             MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData())
65             else ->
66                 Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked))
67         }
68     }
69 }
70 
71 /**
72  * [data] contains a list of NormalizedTouchData for pointers in the motionEvent ordered by
73  * pointerIndex
74  *
75  * [previousPointerOnSensorId] the pointerId of the previous pointer on the sensor,
76  * [MotionEvent.INVALID_POINTER_ID] if none
77  *
78  * [pointersOnSensor] contains a list of ids of pointers on the sensor
79  */
80 private data class PreprocessedTouch(
81     val data: List<NormalizedTouchData>,
82     val previousPointerOnSensorId: Int,
83     val pointersOnSensor: List<Int>,
84 )
85 
processActionMovenull86 private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult {
87     val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID
88     val hasPointerOnSensor = touch.pointersOnSensor.isNotEmpty()
89     val pointerOnSensorId = touch.pointersOnSensor.firstOrNull() ?: INVALID_POINTER_ID
90 
91     return if (!hadPointerOnSensor && hasPointerOnSensor) {
92         val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
93         ProcessedTouch(InteractionEvent.DOWN, data.pointerId, data)
94     } else if (hadPointerOnSensor && !hasPointerOnSensor) {
95         ProcessedTouch(InteractionEvent.UP, INVALID_POINTER_ID, NormalizedTouchData())
96     } else {
97         val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
98         ProcessedTouch(InteractionEvent.UNCHANGED, pointerOnSensorId, data)
99     }
100 }
101 
processActionUpnull102 private fun processActionUp(touch: PreprocessedTouch, actionId: Int): TouchProcessorResult {
103     // Finger lifted and it was the only finger on the sensor
104     return if (touch.pointersOnSensor.size == 1 && touch.pointersOnSensor.contains(actionId)) {
105         ProcessedTouch(
106             InteractionEvent.UP,
107             pointerOnSensorId = INVALID_POINTER_ID,
108             NormalizedTouchData()
109         )
110     } else {
111         // Pick new pointerOnSensor that's not the finger that was lifted
112         val pointerOnSensorId = touch.pointersOnSensor.find { it != actionId } ?: INVALID_POINTER_ID
113         val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
114         ProcessedTouch(InteractionEvent.UNCHANGED, data.pointerId, data)
115     }
116 }
117 
processActionCancelnull118 private fun processActionCancel(data: NormalizedTouchData): TouchProcessorResult {
119     return ProcessedTouch(InteractionEvent.CANCEL, pointerOnSensorId = INVALID_POINTER_ID, data)
120 }
121 
122 /**
123  * Returns the touch information from the given [MotionEvent] with the relevant fields mapped to
124  * natural orientation and native resolution.
125  */
normalizenull126 private fun MotionEvent.normalize(
127     pointerIndex: Int,
128     overlayParams: UdfpsOverlayParams
129 ): NormalizedTouchData {
130     val naturalTouch: PointF = rotateToNaturalOrientation(pointerIndex, overlayParams)
131     val nativeX = naturalTouch.x / overlayParams.scaleFactor
132     val nativeY = naturalTouch.y / overlayParams.scaleFactor
133     val nativeMinor: Float = getTouchMinor(pointerIndex) / overlayParams.scaleFactor
134     val nativeMajor: Float = getTouchMajor(pointerIndex) / overlayParams.scaleFactor
135     var nativeOrientation: Float = getOrientation(pointerIndex)
136     if (SUPPORTED_ROTATIONS.contains(overlayParams.rotation)) {
137         nativeOrientation = toRadVerticalFromRotated(nativeOrientation.toDouble()).toFloat()
138     }
139     return NormalizedTouchData(
140         pointerId = getPointerId(pointerIndex),
141         x = nativeX,
142         y = nativeY,
143         minor = nativeMinor,
144         major = nativeMajor,
145         orientation = nativeOrientation,
146         time = eventTime,
147         gestureStart = downTime,
148     )
149 }
150 
toRadVerticalFromRotatednull151 private fun toRadVerticalFromRotated(rad: Double): Double {
152     val piBound = ((rad % Math.PI) + Math.PI / 2) % Math.PI
153     return if (piBound < Math.PI / 2.0) piBound else piBound - Math.PI
154 }
155 
156 /**
157  * Returns the [MotionEvent.getRawX] and [MotionEvent.getRawY] of the given pointer as if the device
158  * is in the [Surface.ROTATION_0] orientation.
159  */
rotateToNaturalOrientationnull160 private fun MotionEvent.rotateToNaturalOrientation(
161     pointerIndex: Int,
162     overlayParams: UdfpsOverlayParams
163 ): PointF {
164     val touchPoint = PointF(getRawX(pointerIndex), getRawY(pointerIndex))
165     val rot = overlayParams.rotation
166     if (SUPPORTED_ROTATIONS.contains(rot)) {
167         RotationUtils.rotatePointF(
168             touchPoint,
169             RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
170             overlayParams.logicalDisplayWidth.toFloat(),
171             overlayParams.logicalDisplayHeight.toFloat()
172         )
173     }
174     return touchPoint
175 }
176