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.content.Context
20 import android.graphics.Rect
21 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
22 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
23 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
24 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
25 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
26 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
27 import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN
28 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
29 import android.util.Log
30 import android.view.LayoutInflater
31 import android.view.MotionEvent
32 import android.view.MotionEvent.ACTION_DOWN
33 import android.view.MotionEvent.ACTION_MOVE
34 import android.view.MotionEvent.ACTION_UP
35 import com.android.internal.annotations.VisibleForTesting
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.statusbar.commandline.Command
38 import com.android.systemui.statusbar.commandline.CommandRegistry
39 import java.io.PrintWriter
40 import javax.inject.Inject
41 
42 private const val TAG = "UdfpsShell"
43 private const val REQUEST_ID = 2L
44 private const val SENSOR_ID = 0
45 private const val MINOR = 10F
46 private const val MAJOR = 10F
47 
48 /** Used to show and hide the UDFPS overlay with statusbar commands. */
49 @SysUISingleton
50 class UdfpsShell @Inject constructor(commandRegistry: CommandRegistry) : Command {
51 
52     /**
53      * Set in [UdfpsController.java] constructor, used to show and hide the UDFPS overlay.
54      *
55      * TODO: inject after b/229290039 is resolved
56      */
57     var udfpsOverlayController: UdfpsController.UdfpsOverlayController? = null
58     var context: Context? = null
59     var inflater: LayoutInflater? = null
60 
61     init {
<lambda>null62         commandRegistry.registerCommand("udfps") { this }
63     }
64 
executenull65     override fun execute(pw: PrintWriter, args: List<String>) {
66         if (args.size == 1 && args[0] == "hide") {
67             hideOverlay()
68         } else if (args.size == 2 && args[0] == "show") {
69             showOverlay(getEnrollmentReason(args[1]))
70         } else if (args.size == 1 && args[0] == "onUiReady") {
71             onUiReady()
72         } else if (args.size == 1 && args[0] == "simFingerDown") {
73             simFingerDown()
74         } else if (args.size == 1 && args[0] == "simFingerUp") {
75             simFingerUp()
76         } else if (args.size == 1 && args[0] == "biometricPrompt") {
77             launchBiometricPrompt()
78         } else if (args.size == 2 && args[0] == "setIgnoreDisplayTouches") {
79             setIgnoreDisplayTouches(args[1].toBoolean())
80         } else {
81             invalidCommand(pw)
82         }
83     }
84 
helpnull85     override fun help(pw: PrintWriter) {
86         pw.println("Usage: adb shell cmd statusbar udfps <cmd>")
87         pw.println("Supported commands:")
88         pw.println("  - show <reason>")
89         pw.println(
90             "    -> supported reasons: [enroll-find-sensor, enroll-enrolling, auth-bp, " +
91                 "auth-keyguard, auth-other, auth-settings]"
92         )
93         pw.println("    -> reason otherwise defaults to unknown")
94         pw.println("  - hide")
95         pw.println("  - onUiReady")
96         pw.println("  - simFingerDown")
97         pw.println("    -> Simulates onFingerDown on sensor")
98         pw.println("  - simFingerUp")
99         pw.println("    -> Simulates onFingerUp on sensor")
100         pw.println("  - biometricPrompt")
101         pw.println("    -> Shows Biometric Prompt")
102     }
103 
invalidCommandnull104     private fun invalidCommand(pw: PrintWriter) {
105         pw.println("invalid command")
106         help(pw)
107     }
108 
getEnrollmentReasonnull109     private fun getEnrollmentReason(reason: String): Int {
110         return when (reason) {
111             "enroll-find-sensor" -> REASON_ENROLL_FIND_SENSOR
112             "enroll-enrolling" -> REASON_ENROLL_ENROLLING
113             "auth-bp" -> REASON_AUTH_BP
114             "auth-keyguard" -> REASON_AUTH_KEYGUARD
115             "auth-other" -> REASON_AUTH_OTHER
116             "auth-settings" -> REASON_AUTH_SETTINGS
117             else -> REASON_UNKNOWN
118         }
119     }
120 
showOverlaynull121     private fun showOverlay(reason: Int) {
122         udfpsOverlayController?.showUdfpsOverlay(
123             REQUEST_ID,
124             SENSOR_ID,
125             reason,
126             object : IUdfpsOverlayControllerCallback.Stub() {
127                 override fun onUserCanceled() {
128                     Log.e(TAG, "User cancelled")
129                 }
130             }
131         )
132     }
133 
hideOverlaynull134     private fun hideOverlay() {
135         udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
136     }
137 
launchBiometricPromptnull138     private fun launchBiometricPrompt() {
139         udfpsOverlayController?.debugBiometricPrompt()
140     }
141 
142     @VisibleForTesting
onUiReadynull143     fun onUiReady() {
144         udfpsOverlayController?.debugOnUiReady(SENSOR_ID)
145     }
146 
147     @VisibleForTesting
simFingerDownnull148     fun simFingerDown() {
149         val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
150 
151         val downEvent: MotionEvent? =
152             obtainMotionEvent(
153                 ACTION_DOWN,
154                 sensorBounds.exactCenterX(),
155                 sensorBounds.exactCenterY(),
156                 MINOR,
157                 MAJOR
158             )
159         udfpsOverlayController?.debugOnTouch(downEvent)
160 
161         val moveEvent: MotionEvent? =
162             obtainMotionEvent(
163                 ACTION_MOVE,
164                 sensorBounds.exactCenterX(),
165                 sensorBounds.exactCenterY(),
166                 MINOR,
167                 MAJOR
168             )
169         udfpsOverlayController?.debugOnTouch(moveEvent)
170 
171         downEvent?.recycle()
172         moveEvent?.recycle()
173     }
174 
175     @VisibleForTesting
simFingerUpnull176     fun simFingerUp() {
177         val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
178 
179         val upEvent: MotionEvent? =
180             obtainMotionEvent(
181                 ACTION_UP,
182                 sensorBounds.exactCenterX(),
183                 sensorBounds.exactCenterY(),
184                 MINOR,
185                 MAJOR
186             )
187         udfpsOverlayController?.debugOnTouch(upEvent)
188         upEvent?.recycle()
189     }
190 
191     @VisibleForTesting
setIgnoreDisplayTouchesnull192     fun setIgnoreDisplayTouches(ignoreTouches: Boolean) {
193         udfpsOverlayController?.debugSetIgnoreDisplayTouches(ignoreTouches)
194     }
195 
obtainMotionEventnull196     private fun obtainMotionEvent(
197         action: Int,
198         x: Float,
199         y: Float,
200         minor: Float,
201         major: Float
202     ): MotionEvent? {
203         val pp = MotionEvent.PointerProperties()
204         pp.id = 1
205         val pc = MotionEvent.PointerCoords()
206         pc.x = x
207         pc.y = y
208         pc.touchMinor = minor
209         pc.touchMajor = major
210         return MotionEvent.obtain(
211             0,
212             0,
213             action,
214             1,
215             arrayOf(pp),
216             arrayOf(pc),
217             0,
218             0,
219             1f,
220             1f,
221             0,
222             0,
223             0,
224             0
225         )
226     }
227 }
228