diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 6e65350cb193..de9bbb6ef9fa 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -19,6 +19,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.rules", "services.core.unboosted", + "testables", "truth-prebuilt", "ub-uiautomator", ], diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 3eeba7d4f415..1d65cc35c3bc 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -19,7 +19,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.filters.MediumTest +import android.app.ActivityManager +import android.app.ApplicationExitInfo import android.graphics.Rect +import android.os.Build +import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.SystemClock import android.provider.Settings import android.provider.Settings.Global.HIDE_ERROR_DIALOGS @@ -27,10 +31,13 @@ import android.support.test.uiautomator.By import android.support.test.uiautomator.UiDevice import android.support.test.uiautomator.UiObject2 import android.support.test.uiautomator.Until +import android.testing.PollingCheck import android.view.InputDevice import android.view.MotionEvent import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before import org.junit.Test @@ -51,22 +58,28 @@ import org.junit.runner.RunWith class AnrTest { companion object { private const val TAG = "AnrTest" + private const val ALL_PIDS = 0 + private const val NO_MAX = 0 } - val mInstrumentation = InstrumentationRegistry.getInstrumentation() - var mHideErrorDialogs = 0 + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private var hideErrorDialogs = 0 + private lateinit var PACKAGE_NAME: String + private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * + Build.HW_TIMEOUT_MULTIPLIER) @Before fun setUp() { - val contentResolver = mInstrumentation.targetContext.contentResolver - mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + val contentResolver = instrumentation.targetContext.contentResolver + hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName() } @After fun tearDown() { - val contentResolver = mInstrumentation.targetContext.contentResolver - Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs) + val contentResolver = instrumentation.targetContext.contentResolver + Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs) } @Test @@ -86,19 +99,28 @@ class AnrTest { private fun clickCloseAppOnAnrDialog() { // Find anr dialog and kill app - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val closeAppButton: UiObject2? = uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000) if (closeAppButton == null) { fail("Could not find anr dialog") return } + val initialReasons = getExitReasons() closeAppButton.click() + /** + * We must wait for the app to be fully closed before exiting this test. This is because + * another test may again invoke 'am start' for the same activity. + * If the 1st process that got ANRd isn't killed by the time second 'am start' runs, + * the killing logic will apply to the newly launched 'am start' instance, and the second + * test will fail because the unresponsive activity will never be launched. + */ + waitForNewExitReason(initialReasons[0].timestamp) } private fun clickWaitOnAnrDialog() { // Find anr dialog and tap on wait - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val waitButton: UiObject2? = uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000) if (waitButton == null) { @@ -108,9 +130,27 @@ class AnrTest { waitButton.click() } + private fun getExitReasons(): List { + lateinit var infos: List + instrumentation.runOnMainSync { + val am = instrumentation.getContext().getSystemService(ActivityManager::class.java) + infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX) + } + return infos + } + + private fun waitForNewExitReason(previousExitTimestamp: Long) { + PollingCheck.waitFor { + getExitReasons()[0].timestamp > previousExitTimestamp + } + val reasons = getExitReasons() + assertTrue(reasons[0].timestamp > previousExitTimestamp) + assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason) + } + private fun triggerAnr() { startUnresponsiveActivity() - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val obj: UiObject2? = uiDevice.wait(Until.findObject( By.text("Unresponsive gesture monitor")), 10000) @@ -125,15 +165,14 @@ class AnrTest { MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */) downEvent.source = InputDevice.SOURCE_TOUCHSCREEN - mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) + instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) - // Todo: replace using timeout from android.hardware.input.IInputManager - SystemClock.sleep(5000) // default ANR timeout for gesture monitors + SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors } private fun startUnresponsiveActivity() { val flags = " -W -n " - val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity" - mInstrumentation.uiAutomation.executeShellCommand(startCmd) + val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity" + instrumentation.uiAutomation.executeShellCommand(startCmd) } -} \ No newline at end of file +}