package com.google.android.apps.common.testing.ui.espresso.base; import com.google.android.apps.common.testing.testrunner.ActivityLifecycleCallback; import com.google.android.apps.common.testing.testrunner.ActivityLifecycleMonitorRegistry; import com.google.android.apps.common.testing.testrunner.Stage; import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException; import com.google.android.apps.common.testing.ui.testapp.R; import com.google.android.apps.common.testing.ui.testapp.SendActivity; import android.app.Activity; import android.os.Build; import android.os.SystemClock; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Tests for {@link EventInjector}. */ public class EventInjectorTest extends ActivityInstrumentationTestCase2<SendActivity> { private static final String TAG = EventInjectorTest.class.getSimpleName(); private Activity sendActivity; private EventInjector injector; final AtomicBoolean injectEventWorked = new AtomicBoolean(false); final AtomicBoolean injectEventThrewSecurityException = new AtomicBoolean(false); final CountDownLatch latch = new CountDownLatch(1); @SuppressWarnings("deprecation") public EventInjectorTest() { // Supporting froyo. super("com.google.android.apps.common.testing.ui.testapp", SendActivity.class); } @Override public void setUp() throws Exception { super.setUp(); if (Build.VERSION.SDK_INT > 15) { InputManagerEventInjectionStrategy strat = new InputManagerEventInjectionStrategy(); strat.initialize(); injector = new EventInjector(strat); } else { WindowManagerEventInjectionStrategy strat = new WindowManagerEventInjectionStrategy(); strat.initialize(); injector = new EventInjector(strat); } } @Override public void tearDown() throws Exception { super.tearDown(); } @LargeTest public void testInjectKeyEventUpWithNoDown() throws Exception { sendActivity = getActivity(); getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { View view = sendActivity.findViewById(R.id.send_data_edit_text); assertTrue(view.requestFocus()); latch.countDown(); } }); assertTrue("Timed out!", latch.await(10, TimeUnit.SECONDS)); KeyCharacterMap keyCharacterMap = UiControllerImpl.getKeyCharacterMap(); KeyEvent[] events = keyCharacterMap.getEvents("a".toCharArray()); assertTrue(injector.injectKeyEvent(events[1])); } @LargeTest public void testInjectStaleKeyEvent() throws Exception { sendActivity = getActivity(); getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { View view = sendActivity.findViewById(R.id.send_data_edit_text); assertTrue(view.requestFocus()); latch.countDown(); } }); assertTrue("Timed out!", latch.await(10, TimeUnit.SECONDS)); assertFalse("SecurityException exception was thrown.", injectEventThrewSecurityException.get()); KeyCharacterMap keyCharacterMap = UiControllerImpl.getKeyCharacterMap(); KeyEvent[] events = keyCharacterMap.getEvents("a".toCharArray()); KeyEvent event = KeyEvent.changeTimeRepeat(events[0], 1, 0); // Stale event does not fail for API < 13. if (Build.VERSION.SDK_INT < 13) { assertTrue(injector.injectKeyEvent(event)); } else { assertFalse(injector.injectKeyEvent(event)); } } @LargeTest public void testInjectKeyEvent_securityException() { KeyCharacterMap keyCharacterMap = UiControllerImpl.getKeyCharacterMap(); KeyEvent[] events = keyCharacterMap.getEvents("a".toCharArray()); try { injector.injectKeyEvent(events[0]); fail("Should have thrown a security exception!"); } catch (InjectEventSecurityException expected) { } } @LargeTest public void testInjectMotionEvent_securityException() throws Exception { getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0); try { injector.injectMotionEvent(down); } catch (InjectEventSecurityException expected) { injectEventThrewSecurityException.set(true); } latch.countDown(); } }); latch.await(10, TimeUnit.SECONDS); assertTrue(injectEventThrewSecurityException.get()); } @LargeTest public void testInjectMotionEvent_upEventFailure() throws InterruptedException { final CountDownLatch activityStarted = new CountDownLatch(1); ActivityLifecycleCallback callback = new ActivityLifecycleCallback() { @Override public void onActivityLifecycleChanged(Activity activity, Stage stage) { if (Stage.RESUMED == stage && activity instanceof SendActivity) { activityStarted.countDown(); } } }; ActivityLifecycleMonitorRegistry .getInstance() .addLifecycleCallback(callback); try { getActivity(); assertTrue(activityStarted.await(20, TimeUnit.SECONDS)); final int[] xy = UiControllerImplIntegrationTest.getCoordinatesInMiddleOfSendButton( getActivity(), getInstrumentation()); getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, xy[0], xy[1], 0); try { injectEventWorked.set(injector.injectMotionEvent(up)); } catch (InjectEventSecurityException e) { Log.e(TAG, "injectEvent threw a SecurityException"); } up.recycle(); latch.countDown(); } }); latch.await(10, TimeUnit.SECONDS); assertFalse(injectEventWorked.get()); } finally { ActivityLifecycleMonitorRegistry .getInstance() .removeLifecycleCallback(callback); } } }