1 /*
2 * Copyright (C) 2020 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 android.input.cts
17
18 import android.content.BroadcastReceiver
19 import android.content.Context
20 import android.content.Intent
21 import android.content.IntentFilter
22 import android.os.Handler
23 import android.os.HandlerThread
24 import android.os.Looper
25 import android.os.SystemClock
26 import android.view.InputDevice
27 import android.view.MotionEvent
28 import android.view.MotionEvent.ACTION_DOWN
29 import android.view.MotionEvent.ACTION_MOVE
30 import android.view.View
31 import androidx.test.ext.junit.rules.ActivityScenarioRule
32 import androidx.test.ext.junit.runners.AndroidJUnit4
33 import androidx.test.filters.MediumTest
34 import androidx.test.platform.app.InstrumentationRegistry
35 import com.android.compatibility.common.util.PollingCheck
36 import org.junit.Before
37 import org.junit.Rule
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import java.util.concurrent.CompletableFuture
41 import java.util.concurrent.atomic.AtomicBoolean
42 import kotlin.concurrent.thread
43
44 private const val OVERLAY_ACTIVITY_FOCUSED = "android.input.cts.action.OVERLAY_ACTIVITY_FOCUSED"
45
getViewCenterOnScreennull46 private fun getViewCenterOnScreen(v: View): Pair<Float, Float> {
47 val location = IntArray(2)
48 v.getLocationOnScreen(location)
49 val x = location[0].toFloat() + v.width / 2
50 val y = location[1].toFloat() + v.height / 2
51 return Pair(x, y)
52 }
53
54 /**
55 * When OverlayActivity receives focus, it will send out the OVERLAY_ACTIVITY_FOCUSED broadcast.
56 */
57 class OverlayFocusedBroadcastReceiver : BroadcastReceiver() {
58 private val isFocused = AtomicBoolean(false)
onReceivenull59 override fun onReceive(context: Context, intent: Intent) {
60 isFocused.set(true)
61 }
62
overlayActivityIsFocusednull63 fun overlayActivityIsFocused(): Boolean {
64 return isFocused.get()
65 }
66 }
67
68 /**
69 * This test injects an incomplete event stream and makes sure that the app processes it correctly.
70 * If it does not process it correctly, it can get ANRd.
71 *
72 * This test reproduces a bug where there was incorrect consumption logic in the InputEventReceiver
73 * jni code. If the system has this bug, this test ANRs.
74 * The bug occurs when the app consumes a focus event right after a batched MOVE event.
75 * In this test, we take care to write a batched MOVE event and a focus event prior to unblocking
76 * the UI thread to let the app process these events.
77 */
78 @MediumTest
79 @RunWith(AndroidJUnit4::class)
80 class IncompleteMotionTest {
81 @get:Rule
82 val activityRule = ActivityScenarioRule(IncompleteMotionActivity::class.java)
83 private lateinit var activity: IncompleteMotionActivity
84 private val instrumentation = InstrumentationRegistry.getInstrumentation()
85
86 @Before
setUpnull87 fun setUp() {
88 activityRule.getScenario().onActivity {
89 activity = it
90 }
91 PollingCheck.waitFor { activity.hasWindowFocus() }
92 }
93
94 /**
95 * Check that MOVE event is received by the activity, even if it's coupled with a FOCUS event.
96 */
97 @Test
testIncompleteMotionnull98 fun testIncompleteMotion() {
99 val downTime = SystemClock.uptimeMillis()
100 val (x, y) = getViewCenterOnScreen(activity.window.decorView)
101
102 // Start a valid touch stream
103 sendEvent(downTime, ACTION_DOWN, x, y, true /*sync*/)
104 val resultFuture = CompletableFuture<Void>()
105 // Lock up the UI thread. This ensures that the motion event that we will write will
106 // not get processed by the app right away.
107 activity.runOnUiThread {
108 val sendMoveAndFocus = thread(start = true) {
109 try {
110 sendEvent(downTime, ACTION_MOVE, x, y + 10, false /*sync*/)
111 // The MOVE event is sent async because the UI thread is blocked.
112 // Give dispatcher some time to send it to the app
113 SystemClock.sleep(700)
114
115 val handlerThread = HandlerThread("Receive broadcast from overlay activity")
116 handlerThread.start()
117 val looper: Looper = handlerThread.looper
118 val handler = Handler(looper)
119 val receiver = OverlayFocusedBroadcastReceiver()
120 val intentFilter = IntentFilter(OVERLAY_ACTIVITY_FOCUSED)
121 activity.registerReceiver(receiver, intentFilter, null, handler)
122
123 // Now send hasFocus=false event to the app by launching a new focusable window
124 startOverlayActivity()
125 PollingCheck.waitFor { receiver.overlayActivityIsFocused() }
126 activity.unregisterReceiver(receiver)
127 handlerThread.quit()
128 // We need to ensure that the focus event has been written to the app's socket
129 // before unblocking the UI thread. Having the overlay activity receive
130 // hasFocus=true event is a good proxy for that. However, it does not guarantee
131 // that dispatcher has written the hasFocus=false event to the current activity.
132 // For safety, add another small sleep here
133 SystemClock.sleep(300)
134 resultFuture.complete(null)
135 } catch (e: Throwable) {
136 // Catch potential throwable as to not crash UI thread, rethrow and validate
137 // outside.
138 resultFuture.completeExceptionally(e)
139 }
140 }
141 sendMoveAndFocus.join()
142 }
143 PollingCheck.waitFor { !activity.hasWindowFocus() }
144 // If the platform implementation has a bug, it would consume both MOVE and FOCUS events,
145 // but will only call 'finish' for the focus event.
146 // The MOVE event would not be propagated to the app, because the Choreographer
147 // callback never gets scheduled
148 // If we wait too long here, we will cause ANR (if the platform has a bug).
149 // If the MOVE event is received, however, we can stop the test.
150 PollingCheck.waitFor { activity.receivedMove() }
151 // Before finishing the test, check that no exceptions occurred while running the
152 // instructions in the 'sendMoveAndFocus' thread.
153 resultFuture.get()
154 }
155
sendEventnull156 private fun sendEvent(downTime: Long, action: Int, x: Float, y: Float, sync: Boolean) {
157 val eventTime = when (action) {
158 ACTION_DOWN -> downTime
159 else -> SystemClock.uptimeMillis()
160 }
161 val event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0 /*metaState*/)
162 event.source = InputDevice.SOURCE_TOUCHSCREEN
163 instrumentation.uiAutomation.injectInputEvent(event, sync)
164 }
165
166 /**
167 * Start an activity that overlays the main activity. This is needed in order to move the focus
168 * to the newly launched activity, thus causing the bottom activity to lose focus.
169 * This activity is not full-screen, in order to prevent the bottom activity from receiving an
170 * onStop call. In the previous platform implementation, the ANR behaviour was incorrectly
171 * fixed by consuming events from the onStop event.
172 * Because the bottom activity's UI thread is locked, use 'am start' to start the new activity
173 */
startOverlayActivitynull174 private fun startOverlayActivity() {
175 val flags = " -W -n "
176 val startCmd = "am start $flags android.input.cts/.OverlayActivity"
177 instrumentation.uiAutomation.executeShellCommand(startCmd)
178 }
179 }
180