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