• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 com.android.test.input
17 
18 import android.app.ActivityManager
19 import android.app.ActivityTaskManager
20 import android.app.ApplicationExitInfo
21 import android.app.Instrumentation
22 import android.content.Intent
23 import android.hardware.display.DisplayManager
24 import android.os.Build
25 import android.os.Bundle
26 import android.os.IBinder
27 import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
28 import android.os.SystemClock
29 import android.server.wm.CtsWindowInfoUtils.getWindowCenter
30 import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop
31 import android.testing.PollingCheck
32 import android.util.Log
33 import android.view.InputEvent
34 import android.view.MotionEvent
35 import android.view.MotionEvent.ACTION_DOWN
36 import androidx.test.ext.junit.runners.AndroidJUnit4
37 import androidx.test.filters.MediumTest
38 import androidx.test.platform.app.InstrumentationRegistry
39 import androidx.test.uiautomator.By
40 import androidx.test.uiautomator.UiDevice
41 import androidx.test.uiautomator.UiObject2
42 import androidx.test.uiautomator.Until
43 import com.android.cts.input.BlockingQueueEventVerifier
44 import com.android.cts.input.DebugInputRule
45 import com.android.cts.input.ShowErrorDialogsRule
46 import com.android.cts.input.UinputTouchScreen
47 import com.android.cts.input.inputeventmatchers.withMotionAction
48 import java.time.Duration
49 import java.util.concurrent.LinkedBlockingQueue
50 import java.util.function.Supplier
51 import org.junit.Assert.assertEquals
52 import org.junit.Assert.assertTrue
53 import org.junit.Assert.fail
54 import org.junit.Assume.assumeTrue
55 import org.junit.Rule
56 import org.junit.Test
57 import org.junit.runner.RunWith
58 
59 /**
60  * Click on the center of the window identified by the provided window token. The click is performed
61  * using "UinputTouchScreen" device. If the touchscreen device is closed too soon, it may cause the
62  * click to be dropped. Therefore, the provided runnable can ensure that the click is delivered
63  * before the device is closed, thus avoiding this race.
64  */
65 private fun clickOnWindow(
66     token: IBinder,
67     displayId: Int,
68     instrumentation: Instrumentation,
69     waitForEvent: Runnable,
70 ) {
71     val displayManager = instrumentation.context.getSystemService(DisplayManager::class.java)
72     val display = displayManager.getDisplay(displayId)
73     val point = getWindowCenter({ token }, display.displayId)
74     UinputTouchScreen(instrumentation, display).use { touchScreen ->
75         touchScreen.touchDown(point.x, point.y).lift()
76         // If the device is allowed to close without waiting here, the injected click may be dropped
77         waitForEvent.run()
78     }
79 }
80 
81 /**
82  * This test makes sure that an unresponsive gesture monitor gets an ANR.
83  *
84  * The gesture monitor must be registered from a different process than the instrumented process.
85  * Otherwise, when the test runs, you will get: Test failed to run to completion. Reason:
86  * 'Instrumentation run failed due to 'keyDispatchingTimedOut''. Check device logcat for details
87  * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
88  */
89 @MediumTest
90 @RunWith(AndroidJUnit4::class)
91 class AnrTest {
92     companion object {
93         private const val TAG = "AnrTest"
94         private const val NO_MAX = 0
95     }
96 
97     private val instrumentation = InstrumentationRegistry.getInstrumentation()
98     private lateinit var PACKAGE_NAME: String
99     private val DISPATCHING_TIMEOUT =
100         (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER)
101     private var remoteWindowToken: IBinder? = null
102     private var remoteDisplayId: Int? = null
103     private var remotePid: Int? = null
104     private val remoteInputEvents = LinkedBlockingQueue<InputEvent>()
105     private val verifier = BlockingQueueEventVerifier(remoteInputEvents)
106 
107     // Some devices don't support ANR error dialogs, such as cars, TVs, etc.
108     private val anrDialogsAreSupported =
109         ActivityTaskManager.currentUiModeSupportsErrorDialogs(instrumentation.targetContext)
110 
111     val binder =
112         object : IAnrTestService.Stub() {
provideActivityInfonull113             override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) {
114                 remoteWindowToken = token
115                 remoteDisplayId = displayId
116                 remotePid = pid
117             }
118 
notifyMotionnull119             override fun notifyMotion(event: MotionEvent) {
120                 remoteInputEvents.add(event)
121             }
122         }
123 
124     @get:Rule val showErrorDialogs = ShowErrorDialogsRule()
125 
126     @get:Rule val debugInputRule = DebugInputRule()
127 
128     @Test
129     @DebugInputRule.DebugInput(bug = 339924248)
testGestureMonitorAnr_Closenull130     fun testGestureMonitorAnr_Close() {
131         startUnresponsiveActivity()
132         val timestamp = System.currentTimeMillis()
133         triggerAnr()
134         if (anrDialogsAreSupported) {
135             clickCloseAppOnAnrDialog()
136         } else {
137             Log.i(TAG, "The device does not support ANR dialogs, skipping check for ANR window")
138             // We still want to wait for the app to get killed by the ActivityManager
139         }
140         waitForNewExitReasonAfter(timestamp)
141     }
142 
143     @Test
144     @DebugInputRule.DebugInput(bug = 339924248)
testGestureMonitorAnr_Waitnull145     fun testGestureMonitorAnr_Wait() {
146         assumeTrue(anrDialogsAreSupported)
147         startUnresponsiveActivity()
148         triggerAnr()
149         clickWaitOnAnrDialog()
150         SystemClock.sleep(500) // Wait at least 500ms after tapping on wait
151         // ANR dialog should reappear after a delay - find the close button on it to verify
152         val timestamp = System.currentTimeMillis()
153         clickCloseAppOnAnrDialog()
154         waitForNewExitReasonAfter(timestamp)
155     }
156 
clickCloseAppOnAnrDialognull157     private fun clickCloseAppOnAnrDialog() {
158         // Find anr dialog and kill app
159         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
160         val closeAppButton: UiObject2? =
161             uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
162         if (closeAppButton == null) {
163             fail("Could not find anr dialog/close button")
164             return
165         }
166         closeAppButton.click()
167     }
168 
clickWaitOnAnrDialognull169     private fun clickWaitOnAnrDialog() {
170         // Find anr dialog and tap on wait
171         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
172         val waitButton: UiObject2? =
173             uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
174         if (waitButton == null) {
175             fail("Could not find anr dialog/wait button")
176             return
177         }
178         waitButton.click()
179     }
180 
getExitReasonsnull181     private fun getExitReasons(): List<ApplicationExitInfo> {
182         val packageName = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.name
183         lateinit var infos: List<ApplicationExitInfo>
184         instrumentation.runOnMainSync {
185             val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
186             infos = am.getHistoricalProcessExitReasons(packageName, remotePid!!, NO_MAX)
187         }
188         return infos
189     }
190 
191     /**
192      * We must wait for the app to be fully closed before exiting this test. This is because another
193      * test may again invoke 'am start' for the same activity. If the 1st process that got ANRd
194      * isn't killed by the time second 'am start' runs, the killing logic will apply to the newly
195      * launched 'am start' instance, and the second test will fail because the unresponsive activity
196      * will never be launched.
197      *
198      * Also, we must ensure that we wait until it's killed, so that the next test can launch this
199      * activity again.
200      */
waitForNewExitReasonAfternull201     private fun waitForNewExitReasonAfter(timestamp: Long) {
202         PollingCheck.waitFor(Duration.ofSeconds(20).toMillis() * Build.HW_TIMEOUT_MULTIPLIER) {
203             val reasons = getExitReasons()
204             !reasons.isEmpty() && reasons[0].timestamp >= timestamp
205         }
206         val reasons = getExitReasons()
207         assertTrue(reasons[0].timestamp > timestamp)
208         assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
209     }
210 
triggerAnrnull211     private fun triggerAnr() {
212         clickOnWindow(remoteWindowToken!!, remoteDisplayId!!, instrumentation) {
213             verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN))
214         }
215 
216         SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
217     }
218 
startUnresponsiveActivitynull219     private fun startUnresponsiveActivity() {
220         remoteWindowToken = null
221         val intent =
222             Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java)
223         intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
224         val bundle = Bundle()
225         bundle.putBinder("serviceBinder", binder)
226         intent.putExtra("serviceBundle", bundle)
227         instrumentation.targetContext.startActivity(intent)
228         // first, wait for the token to become valid
229         PollingCheck.check(
230             "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'",
231             Duration.ofSeconds(10).toMillis() * Build.HW_TIMEOUT_MULTIPLIER,
232         ) {
233             remoteWindowToken != null
234         }
235         // next, wait for the window of the activity to get on top
236         // we could combine the two checks above, but the current setup makes it easier to detect
237         // errors
238         assertTrue(
239             "Remote activity window did not become visible",
240             waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken }),
241         )
242     }
243 }
244