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.BiometricOverlayConstants.REASON_AUTH_BP
22 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
23 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
24 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
25 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
26 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
27 import android.hardware.biometrics.BiometricOverlayConstants.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 /**
49  * Used to show and hide the UDFPS overlay with statusbar commands.
50  */
51 @SysUISingleton
52 class UdfpsShell @Inject constructor(
53     commandRegistry: CommandRegistry
54 ) : Command {
55 
56     /**
57      * Set in [UdfpsController.java] constructor, used to show and hide the UDFPS overlay.
58      * TODO: inject after b/229290039 is resolved
59      */
60     var udfpsOverlayController: UdfpsController.UdfpsOverlayController? = null
61     var context: Context? = null
62     var inflater: LayoutInflater? = null
63 
64     init {
65         commandRegistry.registerCommand("udfps") { this }
66     }
67 
68     override fun execute(pw: PrintWriter, args: List<String>) {
69         if (args.size == 1 && args[0] == "hide") {
70             hideOverlay()
71         } else if (args.size == 2 && args[0] == "show") {
72             showOverlay(getEnrollmentReason(args[1]))
73         } else if (args.size == 1 && args[0] == "onUiReady") {
74             onUiReady()
75         } else if (args.size == 1 && args[0] == "simFingerDown") {
76             simFingerDown()
77         } else if (args.size == 1 && args[0] == "simFingerUp") {
78             simFingerUp()
79         } else {
80             invalidCommand(pw)
81         }
82     }
83 
84     override fun help(pw: PrintWriter) {
85         pw.println("Usage: adb shell cmd statusbar udfps <cmd>")
86         pw.println("Supported commands:")
87         pw.println("  - show <reason>")
88         pw.println("    -> supported reasons: [enroll-find-sensor, enroll-enrolling, auth-bp, " +
89                             "auth-keyguard, auth-other, auth-settings]")
90         pw.println("    -> reason otherwise defaults to unknown")
91         pw.println("  - hide")
92         pw.println("  - onUiReady")
93         pw.println("  - simFingerDown")
94         pw.println("    -> Simulates onFingerDown on sensor")
95         pw.println("  - simFingerUp")
96         pw.println("    -> Simulates onFingerUp on sensor")
97     }
98 
99     private fun invalidCommand(pw: PrintWriter) {
100         pw.println("invalid command")
101         help(pw)
102     }
103 
104     private fun getEnrollmentReason(reason: String): Int {
105         return when (reason) {
106             "enroll-find-sensor" -> REASON_ENROLL_FIND_SENSOR
107             "enroll-enrolling" -> REASON_ENROLL_ENROLLING
108             "auth-bp" -> REASON_AUTH_BP
109             "auth-keyguard" -> REASON_AUTH_KEYGUARD
110             "auth-other" -> REASON_AUTH_OTHER
111             "auth-settings" -> REASON_AUTH_SETTINGS
112             else -> REASON_UNKNOWN
113         }
114     }
115 
116     private fun showOverlay(reason: Int) {
117         udfpsOverlayController?.showUdfpsOverlay(
118                 REQUEST_ID,
119                 SENSOR_ID,
120                 reason,
121                 object : IUdfpsOverlayControllerCallback.Stub() {
122                     override fun onUserCanceled() {
123                         Log.e(TAG, "User cancelled")
124                     }
125                 }
126         )
127     }
128 
129     private fun hideOverlay() {
130         udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
131     }
132 
133 
134     @VisibleForTesting
135     fun onUiReady() {
136         udfpsOverlayController?.debugOnUiReady(SENSOR_ID)
137     }
138 
139     @VisibleForTesting
140     fun simFingerDown() {
141         val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
142 
143         val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(),
144                 sensorBounds.exactCenterY(), MINOR, MAJOR)
145         udfpsOverlayController?.debugOnTouch(downEvent)
146 
147         val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(),
148                 sensorBounds.exactCenterY(), MINOR, MAJOR)
149         udfpsOverlayController?.debugOnTouch(moveEvent)
150 
151         downEvent?.recycle()
152         moveEvent?.recycle()
153     }
154 
155     @VisibleForTesting
156     fun simFingerUp() {
157         val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
158 
159         val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(),
160                 sensorBounds.exactCenterY(), MINOR, MAJOR)
161         udfpsOverlayController?.debugOnTouch(upEvent)
162         upEvent?.recycle()
163     }
164 
165     private fun obtainMotionEvent(
166             action: Int,
167             x: Float,
168             y: Float,
169             minor: Float,
170             major: Float
171     ): MotionEvent? {
172         val pp = MotionEvent.PointerProperties()
173         pp.id = 1
174         val pc = MotionEvent.PointerCoords()
175         pc.x = x
176         pc.y = y
177         pc.touchMinor = minor
178         pc.touchMajor = major
179         return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc),
180                 0, 0, 1f, 1f, 0, 0, 0, 0)
181     }
182 }
183