package rabbitescape.ui.android; import android.app.Activity; import android.app.AlertDialog; import android.app.Instrumentation; import android.content.DialogInterface; import android.test.ActivityInstrumentationTestCase2; import android.view.MotionEvent; import android.view.View; import android.view.ViewParent; import android.widget.Button; import android.widget.ListView; import junit.framework.Assert; import net.artificialworlds.rabbitescape.R; import java.util.HashMap; import java.util.Map; import rabbitescape.engine.Token; import rabbitescape.engine.menu.MenuItem; import static net.artificialworlds.rabbitescape.BuildConfig.APPLICATION_ID; class AndroidUiDriver { private static class Holder<T> { T item; } private static interface RunAndReturn<T> { public T run(); } private final long timeout = 10000L; private final long delay = 100L; private final Map<Class<? extends Activity>, Instrumentation.ActivityMonitor> monitors = new HashMap<Class<? extends Activity>, Instrumentation.ActivityMonitor>(); private Activity currentActivity; public AndroidUiDriver( ActivityInstrumentationTestCase2<AndroidMenuActivity> testCase ) { monitor( testCase, AndroidMenuActivity.class ); monitor( testCase, AndroidGameActivity.class ); monitor( testCase, AndroidAboutActivity.class ); testCase.launchActivity( APPLICATION_ID, AndroidMenuActivity.class, null ); } public void monitor( ActivityInstrumentationTestCase2<AndroidMenuActivity> testCase, Class<? extends Activity> activityClass ) { monitors.put( activityClass, testCase.getInstrumentation().addMonitor( activityClass.getName(), null, false ) ); } public MenuItem clickMenuItem( final int itemIndex, String expectedName ) { AndroidMenuActivity activity = waitActivity( AndroidMenuActivity.class ); Assert.assertNotNull( "Could not find activity", activity ); final ListView listView = (ListView)activity.findViewById( R.id.listView ); Assert.assertNotNull( "Expected to find listView inside the menu activity", listView ); changeActivity(); MenuItem ret = runOnUiThreadAndWait( activity, new RunAndReturn<MenuItem>() { @Override public MenuItem run() { MenuItem item = (MenuItem)listView.getItemAtPosition( itemIndex ); listView.performItemClick( listView, itemIndex, listView.getItemIdAtPosition( itemIndex ) ); return item; } } ); Assert.assertEquals( expectedName, ret.name ); return ret; } public <T extends Activity> void back( Class<T> currentActivityClass ) { final T activity = waitActivity( currentActivityClass ); changeActivity(); runOnUiThreadAndWait( activity, new RunAndReturn<String>() { @Override public String run() { activity.onBackPressed(); return "backPressed"; } } ); } public void explodeAllRabbits() { final AndroidGameActivity activity = waitActivity( AndroidGameActivity.class ); // Wait until we can click explode all for ( int wait = 0; wait < timeout && ( activity.gameSurface == null || activity.gameSurface.game == null || activity.gameSurface.game.gameLaunch.physics.world == null ); wait += delay ) { sleepDelay(); } Assert.assertNotNull( "Game world not found", activity.gameSurface.game.gameLaunch.physics.world ); final View button = activity.findViewById( R.id.explodeAllButton ); Assert.assertNull( activity.currentDialog ); runOnUiThreadAndWait( activity, new RunAndReturn<String>() { @Override public String run() { button.performClick(); return "buttonPressed"; } } ); clickPositive( "Explode!" ); } public void chooseAbility( Token.Type type ) { final AndroidGameActivity activity = waitActivity( AndroidGameActivity.class ); int numAbilities = activity.abilitiesGroup.getChildCount(); for( int i = 0; i < numAbilities; ++i ) { View child = activity.abilitiesGroup.getChildAt( i ); Assert.assertTrue( child instanceof AbilityButton ); final AbilityButton abilityButton = (AbilityButton)child; if ( abilityButton.ability().equals( type.name() ) ) { runOnUiThreadAndWait( activity, new RunAndReturn<Button>() { @Override public Button run() { abilityButton.performClick(); return abilityButton; } } ); return; } } Assert.fail( "Ability button not found (type: " + type.name() + ")" ); } public void placeToken( final int x, final int y ) { final AndroidGameActivity activity = waitActivity( AndroidGameActivity.class ); runOnUiThreadAndWait( activity, new RunAndReturn<String>() { @Override public String run() { float ts = activity.gameSurface.game.gameLaunch.graphics.renderingTileSize; int sx = activity.gameSurface.game.gameLaunch.graphics.scrollX; int sy = activity.gameSurface.game.gameLaunch.graphics.scrollY; MotionEvent motionEvent = MotionEvent.obtain( 50, 50, MotionEvent.ACTION_DOWN, (int)( ts * ( x + 0.5 ) ) - sx, (int)( ts * ( y + 0.5 ) ) - sy, 0 ); activity.gameSurface.dispatchTouchEvent( motionEvent ); activity.gameSurface.performClick(); return "clicked"; } } ); } public AlertDialog currentDialog() { AndroidGameActivity activity = waitActivity( AndroidGameActivity.class ); return activity.currentDialog; } public void clickNeutral( String expectedButtonText ) { Assert.assertEquals( expectedButtonText, clickDialogButton( DialogInterface.BUTTON_NEUTRAL, false ) ); } public void clickPositive( String expectedButtonText ) { clickPositive( expectedButtonText, false ); } public void clickPositive( String expectedButtonText, boolean doChangeActivity ) { Assert.assertEquals( expectedButtonText, clickDialogButton( DialogInterface.BUTTON_POSITIVE, doChangeActivity ) ); } public CharSequence clickDialogButton( final int buttonType, boolean doChangeActivity ) { final AndroidGameActivity activity = waitActivity( AndroidGameActivity.class ); Assert.assertNotNull( "Could not find activity", activity ); // Wait until we can click the button for ( int wait = 0; wait < timeout && ( activity.currentDialog == null || !activity.currentDialog.isShowing() || activity.currentDialog.getButton( buttonType ) == null ); wait += delay ) { sleepDelay(); } Assert.assertNotNull( activity.currentDialog ); if ( doChangeActivity ) { changeActivity(); } // Click it Button button = runOnUiThreadAndWait( activity, new RunAndReturn<Button>() { @Override public Button run() { Button ret = activity.currentDialog.getButton( buttonType ); ret.performClick(); return ret; } } ); // Wait until the dialog has gone for ( int wait = 0; wait < timeout && activity.currentDialog != null; wait += delay ) { sleepDelay(); } return button.getText(); } public void longWaitForDialog() { final AndroidGameActivity activity = waitActivity( AndroidGameActivity.class ); for ( int wait = 0; wait < timeout * 10 && activity.currentDialog == null; wait += delay ) { sleepDelay(); } } public void changeActivity() { currentActivity = null; } // --- @SuppressWarnings( "unchecked" ) private <T> T waitActivity( Class<? extends Activity> activityClass ) { if ( currentActivity != null ) { Assert.assertTrue( activityClass.isInstance( currentActivity ) ); return (T)currentActivity; } Instrumentation.ActivityMonitor monitor = monitors.get( activityClass ); currentActivity = monitor.waitForActivityWithTimeout( timeout ); return (T)currentActivity; } private <T> T runOnUiThreadAndWait( final Activity activity, final RunAndReturn<T> code ) { final Holder<T> holder = new Holder<T>(); activity.runOnUiThread( new Runnable() { @Override public void run() { holder.item = code.run(); } } ); for ( int wait = 0; holder.item == null && wait < timeout; wait += delay ) { sleepDelay(); } Assert.assertNotNull( "runOnUiThreadAndWait never completed", holder.item ); return holder.item; } public void sleepDelay() { try { Thread.sleep( delay ); } catch ( InterruptedException e ) { // Ignore } } }