content: 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
          }
      }
  }
  