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