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