1 /*
<lambda>null2  * 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 package com.android.test.inputinjection
17 
18 import android.app.Activity
19 import android.app.Instrumentation
20 import android.content.Intent
21 import android.os.Bundle
22 import android.os.SystemClock
23 import android.view.KeyEvent
24 import android.view.MotionEvent
25 import android.view.View
26 import java.util.concurrent.Executors
27 
28 /**
29  * This app is used for testing the input injection. It's used in 2 ways:
30  * 1) This app tries to use Instrumentation APIs to inject various events. All of these injection
31  *    attempts should fail because it does not have INJECT_EVENTS permission. The results of the
32  *    injection are reported by this app via the IInputInjectionTestCallbacks interface.
33  * 2) The test code tries to inject events into this app. Any keys or motions received by
34  *    this app are reported back to the test via the IInputInjectionTestCallbacks interface.
35  */
36 class InputInjectionActivity : Activity() {
37 
38     companion object {
39         const val INTENT_ACTION_TEST_INJECTION =
40             "com.android.test.inputinjection.action.TEST_INJECTION"
41         const val INTENT_EXTRA_CALLBACK = "com.android.test.inputinjection.extra.CALLBACK"
42 
43         // We don't need to send matching "UP" events for the injected keys and motions because
44         // under normal circumstances, these injections would fail so the gesture would never
45         // actually start.
46         val injectionMethods = listOf<Pair<String, (View) -> Unit>>(
47             Pair("sendPointerSync", { view ->
48                 Instrumentation().sendPointerSync(getMotionDownInView(view))
49             }),
50             Pair("sendTrackballEventSync", { view ->
51                 Instrumentation().sendTrackballEventSync(getMotionDownInView(view))
52             }),
53             Pair("sendKeySync", {
54                 Instrumentation().sendKeySync(getKeyDown())
55             }),
56             Pair("sendKeyUpDownSync", {
57                 Instrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_A)
58             }),
59             Pair("sendCharacterSync", {
60                 Instrumentation().sendCharacterSync(KeyEvent.KEYCODE_A)
61             }),
62             Pair("sendStringSync", {
63                 Instrumentation().sendStringSync("Hello World!")
64             })
65         )
66     }
67 
68     // The binder callbacks that report results back to the test process
69     private lateinit var callbacks: IInputInjectionTestCallbacks
70 
71     private lateinit var view: View
72 
73     public override fun onCreate(savedInstanceState: Bundle?) {
74         super.onCreate(savedInstanceState)
75         setContentView(R.layout.activity_input_injection)
76 
77         callbacks = IInputInjectionTestCallbacks.Stub.asInterface(
78             intent.extras?.getBinder(INTENT_EXTRA_CALLBACK)
79         ) ?: throw IllegalStateException("InputInjectionActivity started without binder callback")
80 
81         view = findViewById(R.id.view)!!
82     }
83 
84     override fun onNewIntent(intent: Intent?) {
85         if (intent!!.action == INTENT_ACTION_TEST_INJECTION) {
86             Executors.newSingleThreadExecutor().execute(this::testInputInjectionFromApp)
87         }
88     }
89 
90     override fun onWindowFocusChanged(hasFocus: Boolean) {
91         super.onWindowFocusChanged(hasFocus)
92         if (hasFocus) {
93             callbacks.onWindowFocused()
94         }
95     }
96 
97     /**
98      * Attempt to inject input events from this application into the system, and report the result
99      * to the test process through the [callbacks]. Since this method synchronously injects events,
100      * it must not be called from the main thread.
101      */
102     private fun testInputInjectionFromApp() {
103         val errors = mutableListOf<String>()
104         for ((name, inject) in injectionMethods) {
105             try {
106                 inject(view)
107                 errors.add(
108                     "Call to $name succeeded without throwing an exception " +
109                             "from an app that does not have INJECT_EVENTS permission."
110                 )
111             } catch (e: RuntimeException) {
112                 // We expect a security exception to be thrown because this app does not have
113                 // the INJECT_EVENTS permission.
114             }
115         }
116         callbacks.onTestInjectionFromApp(errors)
117     }
118 
119     override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
120         callbacks.onKeyEvent(event)
121         return super.dispatchKeyEvent(event)
122     }
123 
124     override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
125         callbacks.onTouchEvent(event)
126         return super.dispatchTouchEvent(event)
127     }
128 }
129 
getMotionDownInViewnull130 private fun getMotionDownInView(view: View): MotionEvent {
131     val now = SystemClock.uptimeMillis()
132     val (x, y) = view.getCenterOnScreen()
133     // Use the default source and allow the injection methods to configure it if needed.
134     return MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, x, y, 0)
135 }
136 
getKeyDownnull137 private fun getKeyDown(): KeyEvent {
138     val now = SystemClock.uptimeMillis()
139     return KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0 /*repeat*/)
140 }
141 
getCenterOnScreennull142 private fun View.getCenterOnScreen(): Pair<Float, Float> {
143     val location = IntArray(2).also { getLocationOnScreen(it) }
144     return location[0].toFloat() + width / 2f to location[1].toFloat() + height / 2f
145 }
146