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