xref: /aosp_15_r20/frameworks/base/tests/Input/src/com/android/test/input/AnrTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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 androidx.test.ext.junit.runners.AndroidJUnit4
19 import androidx.test.platform.app.InstrumentationRegistry
20 import androidx.test.filters.MediumTest
21 
22 import android.app.ActivityManager
23 import android.app.ApplicationExitInfo
24 import android.content.Context
25 import android.graphics.Rect
26 import android.hardware.display.DisplayManager
27 import android.os.Build
28 import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
29 import android.os.SystemClock
30 import android.provider.Settings
31 import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
32 import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry
33 import android.testing.PollingCheck
34 
35 import androidx.test.uiautomator.By
36 import androidx.test.uiautomator.UiDevice
37 import androidx.test.uiautomator.UiObject2
38 import androidx.test.uiautomator.Until
39 
40 import com.android.cts.input.DebugInputRule
41 import com.android.cts.input.UinputTouchScreen
42 
43 import java.time.Duration
44 
45 import org.junit.After
46 import org.junit.Assert.assertEquals
47 import org.junit.Assert.assertTrue
48 import org.junit.Assert.fail
49 import org.junit.Before
50 import org.junit.Rule
51 import org.junit.Test
52 import org.junit.runner.RunWith
53 
54 /**
55  * This test makes sure that an unresponsive gesture monitor gets an ANR.
56  *
57  * The gesture monitor must be registered from a different process than the instrumented process.
58  * Otherwise, when the test runs, you will get:
59  * Test failed to run to completion.
60  * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''.
61  * Check device logcat for details
62  * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
63  */
64 @MediumTest
65 @RunWith(AndroidJUnit4::class)
66 class AnrTest {
67     companion object {
68         private const val TAG = "AnrTest"
69         private const val ALL_PIDS = 0
70         private const val NO_MAX = 0
71     }
72 
73     private val instrumentation = InstrumentationRegistry.getInstrumentation()
74     private var hideErrorDialogs = 0
75     private lateinit var PACKAGE_NAME: String
76     private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
77             Build.HW_TIMEOUT_MULTIPLIER)
78 
79     @get:Rule
80     val debugInputRule = DebugInputRule()
81 
82     @Before
83     fun setUp() {
84         val contentResolver = instrumentation.targetContext.contentResolver
85         hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
86         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
87         PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
88     }
89 
90     @After
91     fun tearDown() {
92         val contentResolver = instrumentation.targetContext.contentResolver
93         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs)
94     }
95 
96     @Test
97     @DebugInputRule.DebugInput(bug = 339924248)
98     fun testGestureMonitorAnr_Close() {
99         triggerAnr()
100         clickCloseAppOnAnrDialog()
101     }
102 
103     @Test
104     @DebugInputRule.DebugInput(bug = 339924248)
105     fun testGestureMonitorAnr_Wait() {
106         triggerAnr()
107         clickWaitOnAnrDialog()
108         SystemClock.sleep(500) // Wait at least 500ms after tapping on wait
109         // ANR dialog should reappear after a delay - find the close button on it to verify
110         clickCloseAppOnAnrDialog()
111     }
112 
113     private fun clickCloseAppOnAnrDialog() {
114         // Find anr dialog and kill app
115         val timestamp = System.currentTimeMillis()
116         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
117         val closeAppButton: UiObject2? =
118                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
119         if (closeAppButton == null) {
120             fail("Could not find anr dialog/close button")
121             return
122         }
123         closeAppButton.click()
124         /**
125          * We must wait for the app to be fully closed before exiting this test. This is because
126          * another test may again invoke 'am start' for the same activity.
127          * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
128          * the killing logic will apply to the newly launched 'am start' instance, and the second
129          * test will fail because the unresponsive activity will never be launched.
130          */
131         waitForNewExitReasonAfter(timestamp)
132     }
133 
134     private fun clickWaitOnAnrDialog() {
135         // Find anr dialog and tap on wait
136         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
137         val waitButton: UiObject2? =
138                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
139         if (waitButton == null) {
140             fail("Could not find anr dialog/wait button")
141             return
142         }
143         waitButton.click()
144     }
145 
146     private fun getExitReasons(): List<ApplicationExitInfo> {
147         lateinit var infos: List<ApplicationExitInfo>
148         instrumentation.runOnMainSync {
149             val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
150             infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
151         }
152         return infos
153     }
154 
155     private fun waitForNewExitReasonAfter(timestamp: Long) {
156         PollingCheck.waitFor {
157             val reasons = getExitReasons()
158             !reasons.isEmpty() && reasons[0].timestamp >= timestamp
159         }
160         val reasons = getExitReasons()
161         assertTrue(reasons[0].timestamp > timestamp)
162         assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
163     }
164 
165     private fun clickOnObject(obj: UiObject2) {
166         val displayManager =
167             instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
168         val display = displayManager.getDisplay(obj.getDisplayId())
169         val rect: Rect = obj.visibleBounds
170         UinputTouchScreen(instrumentation, display).use { touchScreen ->
171             touchScreen
172                 .touchDown(rect.centerX(), rect.centerY())
173                 .lift()
174         }
175     }
176 
177     private fun triggerAnr() {
178         startUnresponsiveActivity()
179         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
180         val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000)
181 
182         if (obj == null) {
183             fail("Could not find unresponsive activity")
184             return
185         }
186 
187         clickOnObject(obj)
188 
189         SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
190     }
191 
192     private fun startUnresponsiveActivity() {
193         val flags = " -W -n "
194         val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
195         instrumentation.uiAutomation.executeShellCommand(startCmd)
196         waitForStableWindowGeometry(Duration.ofSeconds(5))
197     }
198 }
199