Loading tests/Input/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ android_test { "modules-utils-testable-device-config-defaults", ], srcs: [ "src/**/*.aidl", "src/**/*.java", "src/**/*.kt", ], Loading tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +3 −3 Original line number Diff line number Diff line Loading @@ -76,7 +76,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.`when` import org.mockito.stubbing.OngoingStubbing Loading Loading @@ -209,7 +209,7 @@ class InputManagerServiceTests { @Test fun testStart() { verifyZeroInteractions(native) verifyNoInteractions(native) service.start() verify(native).start() Loading @@ -217,7 +217,7 @@ class InputManagerServiceTests { @Test fun testInputSettingsUpdatedOnSystemRunning() { verifyZeroInteractions(native) verifyNoInteractions(native) runWithShellPermissionIdentity { service.systemRunning() Loading tests/Input/src/com/android/test/input/AnrTest.kt +93 −59 Original line number Diff line number Diff line Loading @@ -15,32 +15,37 @@ */ package com.android.test.input 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.content.Context import android.graphics.Rect import android.app.Instrumentation import android.content.Intent import android.hardware.display.DisplayManager import android.os.Build import android.os.Bundle import android.os.IBinder import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.SystemClock import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry import android.server.wm.CtsWindowInfoUtils.getWindowCenter import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop import android.testing.PollingCheck import android.view.InputEvent import android.view.MotionEvent import android.view.MotionEvent.ACTION_DOWN import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.cts.input.BlockingQueueEventVerifier import com.android.cts.input.DebugInputRule import com.android.cts.input.ShowErrorDialogsRule import com.android.cts.input.UinputTouchScreen import com.android.cts.input.inputeventmatchers.withMotionAction import java.time.Duration import java.util.concurrent.LinkedBlockingQueue import java.util.function.Supplier import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue Loading @@ -50,14 +55,35 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith /** * Click on the center of the window identified by the provided window token. * The click is performed using "UinputTouchScreen" device. * If the touchscreen device is closed too soon, it may cause the click to be dropped. Therefore, * the provided runnable can ensure that the click is delivered before the device is closed, thus * avoiding this race. */ private fun clickOnWindow( token: IBinder, displayId: Int, instrumentation: Instrumentation, waitForEvent: Runnable, ) { val displayManager = instrumentation.context.getSystemService(DisplayManager::class.java) val display = displayManager.getDisplay(displayId) val point = getWindowCenter({ token }, display.displayId) UinputTouchScreen(instrumentation, display).use { touchScreen -> touchScreen.touchDown(point.x, point.y).lift() // If the device is allowed to close without waiting here, the injected click may be dropped waitForEvent.run() } } /** * This test makes sure that an unresponsive gesture monitor gets an ANR. * * The gesture monitor must be registered from a different process than the instrumented process. * Otherwise, when the test runs, you will get: * Test failed to run to completion. * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''. * Check device logcat for details * Otherwise, when the test runs, you will get: Test failed to run to completion. Reason: * 'Instrumentation run failed due to 'keyDispatchingTimedOut''. Check device logcat for details * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut' */ @MediumTest Loading @@ -65,30 +91,43 @@ 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 } 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) private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER) private var remoteWindowToken: IBinder? = null private var remoteDisplayId: Int? = null private var remotePid: Int? = null private val remoteInputEvents = LinkedBlockingQueue<InputEvent>() private val verifier = BlockingQueueEventVerifier(remoteInputEvents) @get:Rule val debugInputRule = DebugInputRule() val binder = object : IAnrTestService.Stub() { override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) { remoteWindowToken = token remoteDisplayId = displayId remotePid = pid } override fun notifyMotion(event: MotionEvent) { remoteInputEvents.add(event) } } @get:Rule val showErrorDialogs = ShowErrorDialogsRule() @get:Rule val showErrorDialogs = ShowErrorDialogsRule() @get:Rule val debugInputRule = DebugInputRule() @Before fun setUp() { startUnresponsiveActivity() PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName() } @After fun tearDown() { } @After fun tearDown() {} @Test @DebugInputRule.DebugInput(bug = 339924248) Loading Loading @@ -120,10 +159,10 @@ class AnrTest { 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. * 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. */ waitForNewExitReasonAfter(timestamp) } Loading @@ -144,7 +183,7 @@ class AnrTest { lateinit var infos: List<ApplicationExitInfo> instrumentation.runOnMainSync { val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!! infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX) infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, remotePid!!, NO_MAX) } return infos } Loading @@ -159,37 +198,32 @@ class AnrTest { assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason) } private fun clickOnObject(obj: UiObject2) { val displayManager = instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val display = displayManager.getDisplay(obj.getDisplayId()) val rect: Rect = obj.visibleBounds UinputTouchScreen(instrumentation, display).use { touchScreen -> touchScreen .touchDown(rect.centerX(), rect.centerY()) .lift() } } private fun triggerAnr() { startUnresponsiveActivity() val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000) if (obj == null) { fail("Could not find unresponsive activity") return } clickOnObject(obj) clickOnWindow( remoteWindowToken!!, remoteDisplayId!!, instrumentation, ) { verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN)) } SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors } private fun startUnresponsiveActivity() { val flags = " -W -n " val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity" instrumentation.uiAutomation.executeShellCommand(startCmd) waitForStableWindowGeometry(Duration.ofSeconds(5)) val intent = Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT val bundle = Bundle() bundle.putBinder("serviceBinder", binder) intent.putExtra("serviceBundle", bundle) instrumentation.targetContext.startActivity(intent) // first, wait for the token to become valid PollingCheck.check( "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'", Duration.ofSeconds(5).toMillis()) { remoteWindowToken != null } // next, wait for the window of the activity to get on top // we could combine the two checks above, but the current setup makes it easier to detect // errors assertTrue("Remote activity window did not become visible", waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken })) } } tests/Input/src/com/android/test/input/IAnrTestService.aidl 0 → 100644 +17 −0 Original line number Diff line number Diff line package com.android.test.input; import android.view.MotionEvent; interface IAnrTestService { /** * Provide the activity information. This includes: * windowToken: the windowToken of the activity window * displayId: the display id on which the activity is positioned * pid: the pid of the activity */ void provideActivityInfo(IBinder windowToken, int displayId, int pid); /** * Provide the MotionEvent received by the remote activity. */ void notifyMotion(in MotionEvent event); } tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt +21 −4 Original line number Diff line number Diff line Loading @@ -23,20 +23,24 @@ import android.app.Activity import android.hardware.input.InputManager import android.os.Bundle import android.os.Looper import android.os.Process import android.util.Log import android.view.InputChannel import android.view.InputEvent import android.view.InputEventReceiver import android.view.InputMonitor import android.view.MotionEvent class UnresponsiveReceiver(channel: InputChannel, looper: Looper) : class UnresponsiveReceiver(channel: InputChannel, looper: Looper, val service: IAnrTestService) : InputEventReceiver(channel, looper) { companion object { const val TAG = "UnresponsiveReceiver" } override fun onInputEvent(event: InputEvent) { Log.i(TAG, "Received $event") // Not calling 'finishInputEvent' in order to trigger the ANR service.notifyMotion(event as MotionEvent) } } Loading @@ -44,14 +48,27 @@ class UnresponsiveGestureMonitorActivity : Activity() { companion object { const val MONITOR_NAME = "unresponsive gesture monitor" } private lateinit var mInputEventReceiver: InputEventReceiver private lateinit var mInputMonitor: InputMonitor private lateinit var service: IAnrTestService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val bundle = intent.getBundleExtra("serviceBundle")!! service = IAnrTestService.Stub.asInterface(bundle.getBinder("serviceBinder")) val inputManager = checkNotNull(getSystemService(InputManager::class.java)) mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId) mInputEventReceiver = UnresponsiveReceiver( mInputMonitor.getInputChannel(), Looper.myLooper()!!) mInputEventReceiver = UnresponsiveReceiver(mInputMonitor.getInputChannel(), Looper.myLooper()!!, service) } override fun onAttachedToWindow() { super.onAttachedToWindow() service.provideActivityInfo( window.decorView.windowToken, display.displayId, Process.myPid(), ) } } Loading
tests/Input/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ android_test { "modules-utils-testable-device-config-defaults", ], srcs: [ "src/**/*.aidl", "src/**/*.java", "src/**/*.kt", ], Loading
tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +3 −3 Original line number Diff line number Diff line Loading @@ -76,7 +76,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.`when` import org.mockito.stubbing.OngoingStubbing Loading Loading @@ -209,7 +209,7 @@ class InputManagerServiceTests { @Test fun testStart() { verifyZeroInteractions(native) verifyNoInteractions(native) service.start() verify(native).start() Loading @@ -217,7 +217,7 @@ class InputManagerServiceTests { @Test fun testInputSettingsUpdatedOnSystemRunning() { verifyZeroInteractions(native) verifyNoInteractions(native) runWithShellPermissionIdentity { service.systemRunning() Loading
tests/Input/src/com/android/test/input/AnrTest.kt +93 −59 Original line number Diff line number Diff line Loading @@ -15,32 +15,37 @@ */ package com.android.test.input 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.content.Context import android.graphics.Rect import android.app.Instrumentation import android.content.Intent import android.hardware.display.DisplayManager import android.os.Build import android.os.Bundle import android.os.IBinder import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.SystemClock import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry import android.server.wm.CtsWindowInfoUtils.getWindowCenter import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop import android.testing.PollingCheck import android.view.InputEvent import android.view.MotionEvent import android.view.MotionEvent.ACTION_DOWN import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.cts.input.BlockingQueueEventVerifier import com.android.cts.input.DebugInputRule import com.android.cts.input.ShowErrorDialogsRule import com.android.cts.input.UinputTouchScreen import com.android.cts.input.inputeventmatchers.withMotionAction import java.time.Duration import java.util.concurrent.LinkedBlockingQueue import java.util.function.Supplier import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue Loading @@ -50,14 +55,35 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith /** * Click on the center of the window identified by the provided window token. * The click is performed using "UinputTouchScreen" device. * If the touchscreen device is closed too soon, it may cause the click to be dropped. Therefore, * the provided runnable can ensure that the click is delivered before the device is closed, thus * avoiding this race. */ private fun clickOnWindow( token: IBinder, displayId: Int, instrumentation: Instrumentation, waitForEvent: Runnable, ) { val displayManager = instrumentation.context.getSystemService(DisplayManager::class.java) val display = displayManager.getDisplay(displayId) val point = getWindowCenter({ token }, display.displayId) UinputTouchScreen(instrumentation, display).use { touchScreen -> touchScreen.touchDown(point.x, point.y).lift() // If the device is allowed to close without waiting here, the injected click may be dropped waitForEvent.run() } } /** * This test makes sure that an unresponsive gesture monitor gets an ANR. * * The gesture monitor must be registered from a different process than the instrumented process. * Otherwise, when the test runs, you will get: * Test failed to run to completion. * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''. * Check device logcat for details * Otherwise, when the test runs, you will get: Test failed to run to completion. Reason: * 'Instrumentation run failed due to 'keyDispatchingTimedOut''. Check device logcat for details * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut' */ @MediumTest Loading @@ -65,30 +91,43 @@ 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 } 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) private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER) private var remoteWindowToken: IBinder? = null private var remoteDisplayId: Int? = null private var remotePid: Int? = null private val remoteInputEvents = LinkedBlockingQueue<InputEvent>() private val verifier = BlockingQueueEventVerifier(remoteInputEvents) @get:Rule val debugInputRule = DebugInputRule() val binder = object : IAnrTestService.Stub() { override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) { remoteWindowToken = token remoteDisplayId = displayId remotePid = pid } override fun notifyMotion(event: MotionEvent) { remoteInputEvents.add(event) } } @get:Rule val showErrorDialogs = ShowErrorDialogsRule() @get:Rule val showErrorDialogs = ShowErrorDialogsRule() @get:Rule val debugInputRule = DebugInputRule() @Before fun setUp() { startUnresponsiveActivity() PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName() } @After fun tearDown() { } @After fun tearDown() {} @Test @DebugInputRule.DebugInput(bug = 339924248) Loading Loading @@ -120,10 +159,10 @@ class AnrTest { 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. * 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. */ waitForNewExitReasonAfter(timestamp) } Loading @@ -144,7 +183,7 @@ class AnrTest { lateinit var infos: List<ApplicationExitInfo> instrumentation.runOnMainSync { val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!! infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX) infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, remotePid!!, NO_MAX) } return infos } Loading @@ -159,37 +198,32 @@ class AnrTest { assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason) } private fun clickOnObject(obj: UiObject2) { val displayManager = instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val display = displayManager.getDisplay(obj.getDisplayId()) val rect: Rect = obj.visibleBounds UinputTouchScreen(instrumentation, display).use { touchScreen -> touchScreen .touchDown(rect.centerX(), rect.centerY()) .lift() } } private fun triggerAnr() { startUnresponsiveActivity() val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000) if (obj == null) { fail("Could not find unresponsive activity") return } clickOnObject(obj) clickOnWindow( remoteWindowToken!!, remoteDisplayId!!, instrumentation, ) { verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN)) } SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors } private fun startUnresponsiveActivity() { val flags = " -W -n " val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity" instrumentation.uiAutomation.executeShellCommand(startCmd) waitForStableWindowGeometry(Duration.ofSeconds(5)) val intent = Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT val bundle = Bundle() bundle.putBinder("serviceBinder", binder) intent.putExtra("serviceBundle", bundle) instrumentation.targetContext.startActivity(intent) // first, wait for the token to become valid PollingCheck.check( "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'", Duration.ofSeconds(5).toMillis()) { remoteWindowToken != null } // next, wait for the window of the activity to get on top // we could combine the two checks above, but the current setup makes it easier to detect // errors assertTrue("Remote activity window did not become visible", waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken })) } }
tests/Input/src/com/android/test/input/IAnrTestService.aidl 0 → 100644 +17 −0 Original line number Diff line number Diff line package com.android.test.input; import android.view.MotionEvent; interface IAnrTestService { /** * Provide the activity information. This includes: * windowToken: the windowToken of the activity window * displayId: the display id on which the activity is positioned * pid: the pid of the activity */ void provideActivityInfo(IBinder windowToken, int displayId, int pid); /** * Provide the MotionEvent received by the remote activity. */ void notifyMotion(in MotionEvent event); }
tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt +21 −4 Original line number Diff line number Diff line Loading @@ -23,20 +23,24 @@ import android.app.Activity import android.hardware.input.InputManager import android.os.Bundle import android.os.Looper import android.os.Process import android.util.Log import android.view.InputChannel import android.view.InputEvent import android.view.InputEventReceiver import android.view.InputMonitor import android.view.MotionEvent class UnresponsiveReceiver(channel: InputChannel, looper: Looper) : class UnresponsiveReceiver(channel: InputChannel, looper: Looper, val service: IAnrTestService) : InputEventReceiver(channel, looper) { companion object { const val TAG = "UnresponsiveReceiver" } override fun onInputEvent(event: InputEvent) { Log.i(TAG, "Received $event") // Not calling 'finishInputEvent' in order to trigger the ANR service.notifyMotion(event as MotionEvent) } } Loading @@ -44,14 +48,27 @@ class UnresponsiveGestureMonitorActivity : Activity() { companion object { const val MONITOR_NAME = "unresponsive gesture monitor" } private lateinit var mInputEventReceiver: InputEventReceiver private lateinit var mInputMonitor: InputMonitor private lateinit var service: IAnrTestService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val bundle = intent.getBundleExtra("serviceBundle")!! service = IAnrTestService.Stub.asInterface(bundle.getBinder("serviceBinder")) val inputManager = checkNotNull(getSystemService(InputManager::class.java)) mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId) mInputEventReceiver = UnresponsiveReceiver( mInputMonitor.getInputChannel(), Looper.myLooper()!!) mInputEventReceiver = UnresponsiveReceiver(mInputMonitor.getInputChannel(), Looper.myLooper()!!, service) } override fun onAttachedToWindow() { super.onAttachedToWindow() service.provideActivityInfo( window.decorView.windowToken, display.displayId, Process.myPid(), ) } }