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 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.graphics.Rect
25 import android.os.Build
26 import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
27 import android.os.SystemClock
28 import android.provider.Settings
29 import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
30 import android.testing.PollingCheck
31 import android.view.InputDevice
32 import android.view.MotionEvent
33 
34 import androidx.test.uiautomator.By
35 import androidx.test.uiautomator.UiDevice
36 import androidx.test.uiautomator.UiObject2
37 import androidx.test.uiautomator.Until
38 
39 import org.junit.After
40 import org.junit.Assert.assertEquals
41 import org.junit.Assert.assertTrue
42 import org.junit.Assert.fail
43 import org.junit.Before
44 import org.junit.Test
45 import org.junit.runner.RunWith
46 
47 /**
48  * This test makes sure that an unresponsive gesture monitor gets an ANR.
49  *
50  * The gesture monitor must be registered from a different process than the instrumented process.
51  * Otherwise, when the test runs, you will get:
52  * Test failed to run to completion.
53  * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''.
54  * Check device logcat for details
55  * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
56  */
57 @MediumTest
58 @RunWith(AndroidJUnit4::class)
59 class AnrTest {
60     companion object {
61         private const val TAG = "AnrTest"
62         private const val ALL_PIDS = 0
63         private const val NO_MAX = 0
64     }
65 
66     private val instrumentation = InstrumentationRegistry.getInstrumentation()
67     private var hideErrorDialogs = 0
68     private lateinit var PACKAGE_NAME: String
69     private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
70             Build.HW_TIMEOUT_MULTIPLIER)
71 
72     @Before
73     fun setUp() {
74         val contentResolver = instrumentation.targetContext.contentResolver
75         hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
76         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
77         PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
78     }
79 
80     @After
81     fun tearDown() {
82         val contentResolver = instrumentation.targetContext.contentResolver
83         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs)
84     }
85 
86     @Test
87     fun testGestureMonitorAnr_Close() {
88         triggerAnr()
89         clickCloseAppOnAnrDialog()
90     }
91 
92     @Test
93     fun testGestureMonitorAnr_Wait() {
94         triggerAnr()
95         clickWaitOnAnrDialog()
96         SystemClock.sleep(500) // Wait at least 500ms after tapping on wait
97         // ANR dialog should reappear after a delay - find the close button on it to verify
98         clickCloseAppOnAnrDialog()
99     }
100 
101     private fun clickCloseAppOnAnrDialog() {
102         // Find anr dialog and kill app
103         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
104         val closeAppButton: UiObject2? =
105                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
106         if (closeAppButton == null) {
107             fail("Could not find anr dialog")
108             return
109         }
110         val initialReasons = getExitReasons()
111         closeAppButton.click()
112         /**
113          * We must wait for the app to be fully closed before exiting this test. This is because
114          * another test may again invoke 'am start' for the same activity.
115          * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
116          * the killing logic will apply to the newly launched 'am start' instance, and the second
117          * test will fail because the unresponsive activity will never be launched.
118          */
119         waitForNewExitReason(initialReasons[0].timestamp)
120     }
121 
122     private fun clickWaitOnAnrDialog() {
123         // Find anr dialog and tap on wait
124         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
125         val waitButton: UiObject2? =
126                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
127         if (waitButton == null) {
128             fail("Could not find anr dialog/wait button")
129             return
130         }
131         waitButton.click()
132     }
133 
134     private fun getExitReasons(): List<ApplicationExitInfo> {
135         lateinit var infos: List<ApplicationExitInfo>
136         instrumentation.runOnMainSync {
137             val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)
138             infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
139         }
140         return infos
141     }
142 
143     private fun waitForNewExitReason(previousExitTimestamp: Long) {
144         PollingCheck.waitFor {
145             getExitReasons()[0].timestamp > previousExitTimestamp
146         }
147         val reasons = getExitReasons()
148         assertTrue(reasons[0].timestamp > previousExitTimestamp)
149         assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
150     }
151 
152     private fun triggerAnr() {
153         startUnresponsiveActivity()
154         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
155         val obj: UiObject2? = uiDevice.wait(Until.findObject(
156                 By.text("Unresponsive gesture monitor")), 10000)
157 
158         if (obj == null) {
159             fail("Could not find unresponsive activity")
160             return
161         }
162 
163         val rect: Rect = obj.visibleBounds
164         val downTime = SystemClock.uptimeMillis()
165         val downEvent = MotionEvent.obtain(downTime, downTime,
166                 MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
167         downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
168 
169         instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
170 
171         SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
172     }
173 
174     private fun startUnresponsiveActivity() {
175         val flags = " -W -n "
176         val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
177         instrumentation.uiAutomation.executeShellCommand(startCmd)
178     }
179 }
180