[CODE] Android multitouch tweaks

All code submission.
Locked
dannuic
Posts: 1
Joined: Sun Jul 07, 2013 2:36 pm

[CODE] Android multitouch tweaks

Post by dannuic »

I took a gander at Wagic mainly because I wanted some ideas on where to start programming for an INWO game on Android, and I thought Wagic might have the core for it since it is supposed to be able to do multiple card games. Unfortunately, I don't think it can handle the unusual card layout of that game without serious modifications.

On the plus side, I do love MtG, so Wagic is great to have on my tablet. With that said, I decided to code a few changes to handle multitouch on my tablet. I've put in the framework for n-finger swipes in the SDL code, and then connected it to the android java. It should easily extend to iOS, but I don't have an iOS device to test it on. I also added in a few pinch and twist commands. I tried my best to follow what I thought the paradigms of this project are -- specifically that the majority of the code and logic needs to go into SDL so that it can remain platform independent, and only putting things into the platform dependent parts as necessary to tie in the logic. Also, avoid hacking things in (but that is the paradigm of every project, right?).

The future of this specific project will include things like more natural response in Android regardless of device specs, multi-finger taps (that's pretty close, it won't take much), customizable gestures (dollar gestures), and binding of gestures. There seems to also be a general responsiveness issue, which I think has to do mainly with all the code being handled in SDL instead of in the Android activity (which means it will probably take some tweaking to get it to feel right), so that is also on my list of TODOs.

So here is the patch, patched against the latest rev, 4847. Please give me feedback! I'm experienced as a C/C++ coder, so obviously some of my style made it's way into this patch...

- dannuic

Code: Select all

### Eclipse Workspace Patch 1.0
#P src
Index: JGE/src/JGE.cpp
===================================================================
--- JGE/src/JGE.cpp	(revision 4847)
+++ JGE/src/JGE.cpp	(working copy)
@@ -577,11 +577,11 @@
     mCriticalAssert = true;
 }
 
-void JGE::Scroll(int inXVelocity, int inYVelocity)
+void JGE::Scroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
     if (mApp != NULL)
     {
-        mApp->OnScroll(inXVelocity, inYVelocity);
+        mApp->OnScroll(inNumPointers, inXVelocity, inYVelocity);
     }
 }
 
Index: projects/mtg/include/GameApp.h
===================================================================
--- projects/mtg/include/GameApp.h	(revision 4847)
+++ projects/mtg/include/GameApp.h	(working copy)
@@ -65,7 +65,7 @@
     virtual void Pause();
     virtual void Resume();
 
-    virtual void OnScroll(int inXVelocity, int inYVelocity);
+    virtual void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity);
 
     void LoadGameStates();
     void SetNextState(int state);
Index: JGE/Dependencies/SDL/src/video/android/SDL_androidtouch.h
===================================================================
--- JGE/Dependencies/SDL/src/video/android/SDL_androidtouch.h	(revision 4847)
+++ JGE/Dependencies/SDL/src/video/android/SDL_androidtouch.h	(working copy)
@@ -24,6 +24,6 @@
 
 extern void Android_OnTouch(int index, int action, float x, float y, float p);
 
-extern void Android_OnFlickGesture(float xVelocity, float yVelocity);
+extern void Android_OnFlickGesture(int numPointers, float xVelocity, float yVelocity);
 
 /* vi: set ts=4 sw=4 expandtab: */
Index: projects/mtg/include/GameStateStory.h
===================================================================
--- projects/mtg/include/GameStateStory.h	(revision 4847)
+++ projects/mtg/include/GameStateStory.h	(working copy)
@@ -22,7 +22,7 @@
     void Update(float dt);
     void Render();
     void ButtonPressed(int controllerId, int controlId);
-    void OnScroll(int inXVelocity, int inYVelocity);
+    void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity);
 };
 
 #endif
Index: projects/mtg/src/GameStateStory.cpp
===================================================================
--- projects/mtg/src/GameStateStory.cpp	(revision 4847)
+++ projects/mtg/src/GameStateStory.cpp	(working copy)
@@ -130,7 +130,7 @@
 
 }
 
-void GameStateStory::OnScroll(int, int inYVelocity)
+void GameStateStory::OnScroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
     if (abs(inYVelocity) > 300)
     {
@@ -142,4 +142,4 @@
             velocity -= 100;
         }
     }
-}
\ No newline at end of file
+}
Index: projects/mtg/src/GameStateShop.cpp
===================================================================
--- projects/mtg/src/GameStateShop.cpp	(revision 4847)
+++ projects/mtg/src/GameStateShop.cpp	(working copy)
@@ -866,7 +866,7 @@
     menu->Close();
 }
 
-void GameStateShop::OnScroll(int inXVelocity, int)
+void GameStateShop::OnScroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
     // we ignore magnitude since there isn't any scrolling in the shop
     if (abs(inXVelocity) > 200)
Index: JGE/Dependencies/SDL/src/core/android/SDL_android.cpp
===================================================================
--- JGE/Dependencies/SDL/src/core/android/SDL_android.cpp	(revision 4847)
+++ JGE/Dependencies/SDL/src/core/android/SDL_android.cpp	(working copy)
@@ -130,9 +130,9 @@
 
 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeFlickGesture(
                                     JNIEnv* env, jclass jcls,
-                                    jfloat xVelocity, jfloat yVelocity)
+                                    jint numPointers, jfloat xVelocity, jfloat yVelocity)
 {
-    Android_OnFlickGesture(xVelocity, yVelocity);
+    Android_OnFlickGesture(numPointers, xVelocity, yVelocity);
 }
 
 // Accelerometer
Index: JGE/Dependencies/SDL/src/events/SDL_gesture.c
===================================================================
--- JGE/Dependencies/SDL/src/events/SDL_gesture.c	(revision 4847)
+++ JGE/Dependencies/SDL/src/events/SDL_gesture.c	(working copy)
@@ -469,11 +469,11 @@
   SDL_Event event;
   event.dgesture.type = SDL_DOLLARGESTURE;
   event.dgesture.touchId = touch->id;
-  /*
-    //TODO: Add this to give location of gesture?
-  event.mgesture.x = touch->centroid.x;
-  event.mgesture.y = touch->centroid.y;
-  */
+
+  //TODO: Add this to give location of gesture? -- should be here as per SDL API docs
+  event.dgesture.x = touch->centroid.x;
+  event.dgesture.y = touch->centroid.y;
+
   event.dgesture.gestureId = gestureId;
   event.dgesture.error = error;  
   //A finger came up to trigger this event.
@@ -519,7 +519,7 @@
 
     
     x = ((float)event->tfinger.x)/(float)inTouch->res.x;
-    y = ((float)event->tfinger.y)/(float)inTouch->res.y;   
+    y = ((float)event->tfinger.y)/(float)inTouch->res.y;
 
 
     //Finger Up
Index: JGE/Dependencies/SDL/include/SDL_events.h
===================================================================
--- JGE/Dependencies/SDL/include/SDL_events.h	(revision 4847)
+++ JGE/Dependencies/SDL/include/SDL_events.h	(working copy)
@@ -117,6 +117,7 @@
      *  and should be allocated with SDL_RegisterEvents()
      */
     SDL_USEREVENT    = 0x8000,
+    SDL_FLICKEVENT,
 
     /**
      *  This last event is only for bounding internal arrays
@@ -341,11 +342,9 @@
     SDL_GestureID gestureId;
     Uint32 numFingers;
     float error;
-  /*
-    //TODO: Enable to give location?
+    //TODO: Enable to give location? -- these should be here according to the SDL API docs -- dannuic
     float x;  //currently 0...1. Change to screen coords?
-    float y;  
-  */
+    float y;
 } SDL_DollarGestureEvent;
 
 
@@ -370,6 +369,15 @@
     void *data2;        /**< User defined data pointer */
 } SDL_UserEvent;
 
+/**
+ *  \brief A user-defined data structure for use with SDL_UserEvent and flick motions
+ */
+typedef struct
+{
+	Uint32 numFingers;		/**< the number of fingers down */
+	float xvel;			/**< the velocity in the x direction */
+	float yvel;			/**< the velocity in the y direction */
+} SDL_FlickMotionData;
 
 struct SDL_SysWMmsg;
 typedef struct SDL_SysWMmsg SDL_SysWMmsg;
Index: projects/mtg/src/GameStateDeckViewer.cpp
===================================================================
--- projects/mtg/src/GameStateDeckViewer.cpp	(revision 4847)
+++ projects/mtg/src/GameStateDeckViewer.cpp	(working copy)
@@ -1802,7 +1802,7 @@
  the way a swipe moves a document on the page.  swipe down is to simulate dragging the page down instead of moving down 
  on a page.
  */
-void GameStateDeckViewer::OnScroll(int inXVelocity, int inYVelocity)
+void GameStateDeckViewer::OnScroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
     int magnitude = static_cast<int>( sqrtf( (float )( (inXVelocity * inXVelocity) + (inYVelocity * inYVelocity))));
     
Index: projects/mtg/Android/res/layout/main.xml
===================================================================
--- projects/mtg/Android/res/layout/main.xml	(revision 4847)
+++ projects/mtg/Android/res/layout/main.xml	(working copy)
@@ -8,7 +8,7 @@
 <TextView  
     android:layout_width="fill_parent" 
     android:layout_height="wrap_content" 
-    android:text="Wagic"
+    android:text="@string/app_name"
     />
 </LinearLayout>
 
Index: projects/mtg/include/GameStateDuel.h
===================================================================
--- projects/mtg/include/GameStateDuel.h	(revision 4847)
+++ projects/mtg/include/GameStateDuel.h	(working copy)
@@ -245,7 +245,7 @@
     virtual void Render();
     void initRand(unsigned seed = 0);
 
-    void OnScroll(int inXVelocity, int inYVelocity);
+    void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity);
 
     enum ENUM_DUEL_STATE_MENU_ITEM
     {
Index: JGE/include/JGE.h
===================================================================
--- JGE/include/JGE.h	(revision 4847)
+++ JGE/include/JGE.h	(working copy)
@@ -336,7 +336,7 @@
 
 
   // Scroll events - currently triggered by SDL JOYBALL events
-  void Scroll(int inXVelocity, int inYVelocity);
+  void Scroll(int inNumPointers, int inXVelocity, int inYVelocity);
 
   //////////////////////////////////////////////////////////////////////////
   /// Get if the system is ended/paused or not.
Index: projects/mtg/include/GameStateDeckViewer.h
===================================================================
--- projects/mtg/include/GameStateDeckViewer.h	(revision 4847)
+++ projects/mtg/include/GameStateDeckViewer.h	(working copy)
@@ -151,7 +151,7 @@
     int loadDeck(int deckid);
     void LoadDeckStatistics(int deckId);
 
-    void OnScroll(int inXVelocity, int inYVelocity);
+    void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity);
 
     void buildEditorMenu();
     virtual void ButtonPressed(int controllerId, int controlId);
Index: projects/mtg/src/GameStateDuel.cpp
===================================================================
--- projects/mtg/src/GameStateDuel.cpp	(revision 4847)
+++ projects/mtg/src/GameStateDuel.cpp	(working copy)
@@ -1703,23 +1703,34 @@
 }
 
 
-void GameStateDuel::OnScroll(int inXVelocity, int inYVelocity)
+void GameStateDuel::OnScroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
-    // ignore magnitude for now, since no action requires scrolling
-    if (abs(inYVelocity) > 300)
-    {
-        /*bool flickUpwards = (inYVelocity < 0);*/
-        mEngine->HoldKey_NoRepeat(/*flickUpwards ? JGE_BTN_PREV : */JGE_BTN_SEC);
-        //removing the activation of "left trigger" or "advance phase" because on high end tablets this gesture
-        //is picked up by simply looking at the cards in your hand if the resolution of tablet exceed 1800 X ANY.
-    }
+	// ignore magnitude for now, since no action requires scrolling
+	if (abs(inYVelocity) > 300)
+	{
+		bool flickUpwards = (inYVelocity < 0);
+		if (inNumPointers == 1)
+		{
+			mEngine->HoldKey_NoRepeat(/*flickUpwards ? JGE_BTN_PREV : */JGE_BTN_SEC);
+			//removing the activation of "left trigger" or "advance phase" because on high end tablets this gesture
+			//is picked up by simply looking at the cards in your hand if the resolution of tablet exceed 1800 X ANY.
+			DebugTrace("Flick: Single finger (Y)");
+		}
+		else
+		{
+			mEngine->HoldKey_NoRepeat(flickUpwards ? JGE_BTN_CANCEL : JGE_BTN_PRI);
+			DebugTrace("Flick: Multi-finger");
+		}
+	}
+
     if (abs(inXVelocity) > 300)
     {
-        bool flickLeft = (inYVelocity > 0);
+        bool flickLeft = (inXVelocity > 0);
         if(flickLeft && OptionClosedHand::INVISIBLE == options[Options::CLOSEDHAND].number)
         {
             JButton trigger = (options[Options::REVERSETRIGGERS].number ? JGE_BTN_PREV : JGE_BTN_NEXT);
             mEngine->HoldKey_NoRepeat(trigger);
+			DebugTrace("Flick: Single finger (X)");
         }
     }
 }
Index: JGE/src/SDLmain.cpp
===================================================================
--- JGE/src/SDLmain.cpp	(revision 4847)
+++ JGE/src/SDLmain.cpp	(working copy)
@@ -44,8 +44,8 @@
 
 // tick value equates to ms
 const int kTapEventTimeout = 250;
+const int kMultiTouchEventDelay = 500;
 
-
 uint64_t	lastTickCount;
 JGE* g_engine = NULL;
 JApp* g_app = NULL;
@@ -92,6 +92,7 @@
     SDL_Rect        viewPort;
     Uint32          lastMouseUpTime;
     Uint32          lastFingerDownTime;
+    Uint32			lastMultiTouchTime;
     int             windowed_w;
     int             windowed_h;
     int             windowed_pos_x;
@@ -101,7 +102,7 @@
     int mMouseDownY;
 
 public:
-    SdlApp() : Surf_Display(NULL), window(NULL), lastMouseUpTime(0), lastFingerDownTime(0), Running(true), mMouseDownX(0), mMouseDownY(0)
+    SdlApp() : Surf_Display(NULL), window(NULL), lastMouseUpTime(0), lastFingerDownTime(0), lastMultiTouchTime(0), Running(true), mMouseDownX(0), mMouseDownY(0)
     {
     }
 
@@ -185,6 +186,7 @@
     void OnMouseWheel(int x, int y);
 
     void OnTouchEvent(const SDL_TouchFingerEvent& event);
+    void OnMultiTouchEvent(const SDL_MultiGestureEvent& event);
 
     void OnEvent(SDL_Event* Event)
     {
@@ -250,12 +252,29 @@
                     << ", dTheta " << Event->mgesture.dTheta
                     << ", dDist " << Event->mgesture.dDist
                     << ", numFingers " << Event->mgesture.numFingers);
+            OnMultiTouchEvent(Event->mgesture);
             break;
 
+        case SDL_DOLLARGESTURE:
+        	DebugTrace("Dollargesture : touchId " << Event->dgesture.touchId
+        			<< ", x " << Event->dgesture.x
+        			<< ", y " << Event->dgesture.y
+        			<< ", gestureId " << Event->dgesture.gestureId
+        			<< ", error " << Event->dgesture.error
+        			<< ", numFingers " << Event->dgesture.numFingers);
+        	break;
+
         case SDL_JOYBALLMOTION:
-            DebugTrace("Flick gesture detected, x: " << Event->jball.xrel << ", y: " << Event->jball.yrel);
-            g_engine->Scroll(Event->jball.xrel, Event->jball.yrel);
+//            DebugTrace("Flick gesture detected, x: " << Event->jball.xrel << ", y: " << Event->jball.yrel);
+//            g_engine->Scroll(Event->jball.xrel, Event->jball.yrel);
             break;
+
+        case SDL_FLICKEVENT:
+        	SDL_FlickMotionData *flickData = static_cast<SDL_FlickMotionData*>(Event->user.data1);
+            DebugTrace("Flick event detected, num: " << flickData->numFingers << ", x: " << flickData->xvel << ", y: " << flickData->yvel);
+            g_engine->Scroll(flickData->numFingers, flickData->xvel, flickData->yvel);
+            free(flickData);
+            break;
         }
     }
 
@@ -600,12 +619,12 @@
                     // treat an up finger within 50 pixels of the down finger coords as a double click event
                     if (abs(mMouseDownX - event.x) < kHitzonePliancy && abs(mMouseDownY - event.y) < kHitzonePliancy)
                     {
-                    	DebugTrace("Pressing OK BUtton");
+                    	DebugTrace("Pressing OK Button");
                         g_engine->HoldKey_NoRepeat(JGE_BTN_OK);
                     }
                 }
             }
-      		else      
+      		else
 #endif
 			g_engine->LeftClicked(
                 ((event.x - viewPort.x) * SCREEN_WIDTH) / actualWidth,
@@ -615,6 +634,37 @@
     }
 }
 
+void SdlApp::OnMultiTouchEvent(const SDL_MultiGestureEvent& event) {
+	if (event.numFingers < 2) return; // This should generate a single touch event
+
+	/** TODO: make the motion selection and the sensitivity configurable
+	 *   in the button mapping menu. Probably best to have a finite
+	 *   number of statically defined motions that the user can select
+	 *   from. -- dannuic
+	 */
+
+    Uint32 eventTime = SDL_GetTicks();
+	if (eventTime - lastMultiTouchTime > kMultiTouchEventDelay)
+	{
+		lastMultiTouchTime = eventTime;
+		if (fabs(event.dTheta) > 0.01f) // twist for SEC (cross)
+		{
+			DebugTrace("Pressing SEC Button");
+			g_engine->HoldKey_NoRepeat(JGE_BTN_SEC);
+		}
+		else if (event.dDist < -0.5f) // pinch for PRI (square)
+		{
+			DebugTrace("Pressing PRI Button");
+			g_engine->HoldKey_NoRepeat(JGE_BTN_PRI);
+		}
+		else if (event.dDist > 0.5f) // reverse pinch for CANCEL (triangle)
+		{
+			DebugTrace("Pressing CANCEL Button");
+			g_engine->HoldKey_NoRepeat(JGE_BTN_CANCEL);
+		}
+	}
+}
+
 bool SdlApp::OnInit()
 {
 	int window_w, window_h;
Index: projects/mtg/Android/AndroidManifest.xml
===================================================================
--- projects/mtg/Android/AndroidManifest.xml	(revision 4847)
+++ projects/mtg/Android/AndroidManifest.xml	(working copy)
@@ -1,10 +1,12 @@
 <?xml version="1.0" encoding="utf-8" standalone="no"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="0190" android:versionName="@string/app_version" package="net.wagic.app">
+<!-- <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="0190" android:versionName="@string/app_version" package="net.wagic.app"> -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="0190" android:versionName="0.19.0" package="net.wagic.app">
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <application android:debuggable="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17"/>
+    <application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:debuggable="true">
         <activity android:configChanges="keyboard|keyboardHidden|orientation" android:label="@string/app_name" android:name="org.libsdl.app.SDLActivity" android:screenOrientation="sensorLandscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -14,5 +16,4 @@
         
         <activity android:configChanges="keyboard|keyboardHidden|orientation" android:name="com.google.ads.AdActivity" android:screenOrientation="sensorLandscape"/>
     </application>
-    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="9"/>
 </manifest>
Index: projects/mtg/Android/src/org/libsdl/app/SDLActivity.java
===================================================================
--- projects/mtg/Android/src/org/libsdl/app/SDLActivity.java	(revision 4847)
+++ projects/mtg/Android/src/org/libsdl/app/SDLActivity.java	(working copy)
@@ -5,6 +5,7 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.ref.WeakReference;
 import java.net.URL;
 import java.net.URLConnection;
 
@@ -40,6 +41,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.Menu;
@@ -83,11 +85,11 @@
     private AlertDialog        mErrorDialog;
     public String              mErrorMessage                         = "";
     public Boolean             mErrorHappened                        = false;
-    public final static String RES_FOLDER                            = "/sdcard/Wagic/Res/";
+    public final static String RES_FOLDER                            = Environment.getExternalStorageDirectory().getPath() + "/Wagic/Res/";
     public static String       RES_FILENAME                          = "core_0184.zip";
     public static final String RES_URL                               = "http://wagic.googlecode.com/files/";
 
-    public String              systemFolder                          = "/sdcard/Wagic/Res/";
+    public String              systemFolder                          = Environment.getExternalStorageDirectory().getPath() + "/Wagic/Res/";
     private String             userFolder;
 
     // path to the onboard sd card that is not removable (typically /mnt/sdcard )
@@ -117,6 +119,11 @@
     {
         return userFolder;
     }
+    
+    public static SDLActivity getInstance()
+    {
+    	return mSingleton;
+    }
 
     // setters
     public void updateStorageLocations()
@@ -403,18 +410,19 @@
         mSurface = new SDLSurface(getApplication());
 
         // setContentView(mSurface);
-        SurfaceHolder holder = mSurface.getHolder();
-        holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
+        // this is ignored now, automatically set
+        //SurfaceHolder holder = mSurface.getHolder();
+        //holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
 
         // Create the adView
         mAdView = new AdView(this, AdSize.BANNER, "a14e9009f88864f"); // "a14dc0ab7b27413" <-- for the alpha);
 
-        _videoLayout.addView(mSurface, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+        _videoLayout.addView(mSurface, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
         _videoLayout.addView(mAdView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.BOTTOM + Gravity.CENTER_HORIZONTAL));
         // mGLView.setFocusableInTouchMode(true);
         // mGLView.setFocusable(true);
         // adView.requestFreshAd();
-        setContentView(_videoLayout, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+        setContentView(_videoLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
         mSurface.requestFocus();
 
         AdRequest request = new AdRequest();
@@ -457,7 +465,7 @@
         } else
         {
             FrameLayout _videoLayout = new FrameLayout(this);
-            setContentView(_videoLayout, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+            setContentView(_videoLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
             startDownload();
         }
     }
@@ -509,21 +517,29 @@
     // Messages from the SDLMain thread
     static int COMMAND_CHANGE_TITLE = 1;
     static int COMMAND_JGE_MSG      = 2;
-
+    
     // Handler for the messages
-    Handler    commandHandler       = new Handler()
-                                    {
-                                        public void handleMessage(Message msg)
-                                        {
-                                            if (msg.arg1 == COMMAND_CHANGE_TITLE)
-                                            {
-                                                setTitle((String) msg.obj);
-                                            } else if (msg.arg1 == COMMAND_JGE_MSG)
-                                            {
-                                                processJGEMsg((String) msg.obj);
-                                            }
-                                        }
-                                    };
+    static class CommandHandler extends Handler
+    {
+    	private final WeakReference<SDLActivity> mActivity;
+    	
+    	CommandHandler(SDLActivity a)
+    	{
+    		mActivity = new WeakReference<SDLActivity>(a);
+    	}
+    	
+    	@Override
+    	public void handleMessage(Message msg)
+    	{
+    		SDLActivity activity = mActivity.get();
+    		if (msg.arg1 == SDLActivity.COMMAND_CHANGE_TITLE)
+    			activity.setTitle((String) msg.obj);
+    		else if (msg.arg1 == SDLActivity.COMMAND_JGE_MSG)
+    			activity.processJGEMsg((String) msg.obj);
+    	}
+    }
+    
+    CommandHandler commandHandler = new CommandHandler(this);
 
     // Send a message from the SDLMain thread
     void sendCommand(int command, Object data)
@@ -551,7 +567,7 @@
 
     public static native void onNativeTouch(int index, int action, float x, float y, float p);
 
-    public static native void onNativeFlickGesture(float xVelocity, float yVelocity);
+    public static native void onNativeFlickGesture(int numPointers, float xVelocity, float yVelocity);
 
     public static native void onNativeAccel(float x, float y, float z);
 
@@ -595,7 +611,7 @@
 
     public static Object audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames)
     {
-        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        int channelConfig = isStereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO;
         int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
         int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
 
@@ -720,7 +736,8 @@
         protected void onPreExecute()
         {
             super.onPreExecute();
-            showDialog(DIALOG_DOWNLOAD_PROGRESS);
+            //showDialog(DIALOG_DOWNLOAD_PROGRESS);
+            mProgressDialog.show();
         }
 
         @Override
@@ -781,7 +798,7 @@
                 Log.e(TAG1, e.getMessage());
             }
 
-            return new Long(totalBytes);
+            return Long.valueOf(totalBytes);
         }
 
         protected void onProgressUpdate(Integer... progress)
@@ -798,8 +815,10 @@
         {
             if (mErrorHappened)
             {
-                dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
-                showDialog(DIALOG_DOWNLOAD_ERROR);
+                //dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
+            	mProgressDialog.dismiss();
+                //showDialog(DIALOG_DOWNLOAD_ERROR);
+            	mErrorDialog.show();
                 return;
             }
             // rename the temporary file into the final filename
@@ -811,7 +830,8 @@
             if (preFile.exists())
                 preFile.renameTo(postFile);
 
-            dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
+            //dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
+            mProgressDialog.dismiss();
             // Start game;
             mSingleton.mainDisplay();
         }
@@ -875,6 +895,7 @@
     private static SensorManager   mSensorManager;
 
     private static VelocityTracker mVelocityTracker;
+    private SparseIntArray		   mFingerTracker;
 
     final private Object           mSemSurface;
     private Boolean                mSurfaceValid;
@@ -920,6 +941,8 @@
         requestFocus();
         setOnKeyListener(this);
         setOnTouchListener(this);
+        
+        mFingerTracker = new SparseIntArray();
 
         mSensorManager = (SensorManager) context.getSystemService("sensor");
     }
@@ -1191,7 +1214,6 @@
     // Touch events
     public boolean onTouch(View v, MotionEvent event)
     {
-
         for (int index = 0; index < event.getPointerCount(); ++index)
         {
             int action = event.getActionMasked();
@@ -1202,35 +1224,67 @@
             // TODO: Anything else we need to pass?
             SDLActivity.onNativeTouch(index, action, x, y, p);
         }
+        
+        // account for 'flick' type gestures by monitoring velocity
+        int action = event.getAction();
+    	switch (action & MotionEvent.ACTION_MASK)
+    	{
+    	case MotionEvent.ACTION_DOWN:
+            mVelocityTracker = VelocityTracker.obtain();
+            mVelocityTracker.clear();
+            mVelocityTracker.addMovement(event);
+            mFingerTracker.clear();
+            mFingerTracker.put(event.getPointerCount(), mFingerTracker.get(event.getPointerCount()) + 1);
+    		break;
+    	case MotionEvent.ACTION_MOVE:
+    	case MotionEvent.ACTION_POINTER_DOWN:
+    	case MotionEvent.ACTION_POINTER_UP:
+            mVelocityTracker.addMovement(event);
+            mFingerTracker.put(event.getPointerCount(), mFingerTracker.get(event.getPointerCount()) + 1);
+            break;
+    	case MotionEvent.ACTION_UP:
+            mVelocityTracker.addMovement(event);
 
-        // account for 'flick' type gestures by monitoring velocity
-        if (event.getActionIndex() == 0)
-        {
-            if (event.getAction() == MotionEvent.ACTION_DOWN)
+            // calc velocity
+            // TODO: the y-direction seems to be more sensitive than x, need more testing on other devices (due to the bar at the bottom?)
+            // TODO: there seems to be a bug where the velocity will calculate as 0
+            mVelocityTracker.computeCurrentVelocity(1000);
+            float xVelocity = mVelocityTracker.getXVelocity(0);
+            float yVelocity = mVelocityTracker.getYVelocity(0);
+            
+            /**
+             * TODO: can normalize to avoid resolution concerns, so now the velocity is in units of screens/second 
+             *       and all the velocities in the code will need to be redone -- dannuic 
+             * float xVelocity = mVelocityTracker.getXVelocity(0) / (float)getContext().getResources().getDisplayMetrics().widthPixels;
+             * float yVelocity = mVelocityTracker.getYVelocity(0) / (float)getContext().getResources().getDisplayMetrics().heightPixels;
+             */
+            
+            if (Math.abs(xVelocity) > 300 || Math.abs(yVelocity) > 300)
             {
-                mVelocityTracker = VelocityTracker.obtain();
-                mVelocityTracker.clear();
-                mVelocityTracker.addMovement(event);
-            } else if (event.getAction() == MotionEvent.ACTION_MOVE)
-            {
-                mVelocityTracker.addMovement(event);
-            } else if (event.getAction() == MotionEvent.ACTION_UP)
-            {
-                mVelocityTracker.addMovement(event);
+            	int pointerCountMode = mFingerTracker.keyAt(0);
+            	for (int i = 1; i < mFingerTracker.size(); ++i)
+            	{
+            		int j = mFingerTracker.valueAt(i);
+            		if (mFingerTracker.get(pointerCountMode) < j)
+            			pointerCountMode = mFingerTracker.keyAt(i);
+            	}
+            	
+                SDLActivity.onNativeFlickGesture(pointerCountMode, xVelocity, yVelocity);
+            }
 
-                // calc velocity
-                mVelocityTracker.computeCurrentVelocity(1000);
-                float xVelocity = mVelocityTracker.getXVelocity(0);
-                float yVelocity = mVelocityTracker.getYVelocity(0);
-                if (Math.abs(xVelocity) > 300 || Math.abs(yVelocity) > 300)
-                    SDLActivity.onNativeFlickGesture(xVelocity, yVelocity);
+            mVelocityTracker.recycle();
+    		break;
+    	}
 
-                mVelocityTracker.recycle();
-            }
-        }
-
         return true;
     }
+    
+    // Fling events
+    /* onFling does not handle multitouch, manual detection of onTouch motions needed -- dannuic
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+    	return false;
+    }
+    */
 
     // Sensor events
     public void enableSensor(int sensortype, boolean enabled)
Index: projects/mtg/include/GameStateShop.h
===================================================================
--- projects/mtg/include/GameStateShop.h	(revision 4847)
+++ projects/mtg/include/GameStateShop.h	(working copy)
@@ -120,7 +120,7 @@
     virtual void Update(float dt);
     virtual void Render();
     virtual void ButtonPressed(int controllerId, int controlId);
-    virtual void OnScroll(int inXVelocity, int inYVelocity);
+    virtual void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity);
     static float _x1[], _y1[], _x2[], _y2[], _x3[], _y3[], _x4[], _y4[];
 };
 
Index: projects/mtg/src/GameApp.cpp
===================================================================
--- projects/mtg/src/GameApp.cpp	(revision 4847)
+++ projects/mtg/src/GameApp.cpp	(working copy)
@@ -444,11 +444,11 @@
 
 }
 
-void GameApp::OnScroll(int inXVelocity, int inYVelocity)
+void GameApp::OnScroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
     if (mCurrentState != NULL)
     {
-        mCurrentState->OnScroll(inXVelocity, inYVelocity);
+        mCurrentState->OnScroll(inNumPointers, inXVelocity, inYVelocity);
     }
 }
 
Index: projects/mtg/src/GameStateAwards.cpp
===================================================================
--- projects/mtg/src/GameStateAwards.cpp	(revision 4847)
+++ projects/mtg/src/GameStateAwards.cpp	(working copy)
@@ -389,7 +389,7 @@
     }
 }
 
-void GameStateAwards::OnScroll(int, int inYVelocity)
+void GameStateAwards::OnScroll(int inNumPointers, int inXVelocity, int inYVelocity)
 {
     if (abs(inYVelocity) > 300)
     {
Index: JGE/Dependencies/SDL/src/video/android/SDL_androidtouch.c
===================================================================
--- JGE/Dependencies/SDL/src/video/android/SDL_androidtouch.c	(revision 4847)
+++ JGE/Dependencies/SDL/src/video/android/SDL_androidtouch.c	(working copy)
@@ -119,17 +119,19 @@
     }
 }
 
-void Android_OnFlickGesture(float xVelocity, float yVelocity)
+void Android_OnFlickGesture(int numPointers, float xVelocity, float yVelocity)
 {
-    // cheap hack, translate this to a joystick ball event instead of its own proper event
-    
     SDL_Event event;
-    event.jball.type = SDL_JOYBALLMOTION;
-    event.jball.which = 0;
-    event.jball.ball = 0;
-    event.jball.xrel = (int) xVelocity;;
-    event.jball.yrel = (int) yVelocity;
+    SDL_FlickMotionData *flickData = (SDL_FlickMotionData*)malloc(sizeof(SDL_FlickMotionData));
+    flickData->numFingers = (Uint32) numPointers;
+    flickData->xvel = xVelocity;
+    flickData->yvel = yVelocity;
 
+    event.user.type = SDL_FLICKEVENT; // custom event
+    event.user.code = 0;
+    event.user.data1 = flickData;
+    event.user.data2 = NULL;
+
     SDL_PushEvent(&event);
 }
 
Index: JGE/include/JApp.h
===================================================================
--- JGE/include/JApp.h	(revision 4847)
+++ JGE/include/JApp.h	(working copy)
@@ -83,7 +83,7 @@
 	//////////////////////////////////////////////////////////////////////////
 	virtual void Resume() = 0;
 
-    virtual void OnScroll(int inXVelocity, int inYVelocity) = 0;
+    virtual void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity) = 0;
 };
 
 
Index: projects/mtg/include/GameStateAwards.h
===================================================================
--- projects/mtg/include/GameStateAwards.h	(revision 4847)
+++ projects/mtg/include/GameStateAwards.h	(working copy)
@@ -35,7 +35,7 @@
     virtual void Update(float dt);
     virtual void Render();
     virtual void ButtonPressed(int controllerId, int controlId);
-    virtual void OnScroll(int inXVelocity, int inYVelocity);
+    virtual void OnScroll(int inNumPointers, int inXVelocity, int inYVelocity);
 };
 
 #endif
Index: projects/mtg/include/GameState.h
===================================================================
--- projects/mtg/include/GameState.h	(revision 4847)
+++ projects/mtg/include/GameState.h	(working copy)
@@ -56,7 +56,7 @@
     virtual void Start(){}
     virtual void End(){}
 
-    virtual void OnScroll(int, int)
+    virtual void OnScroll(int, int, int)
     {
     }
 
Index: projects/mtg/Android
===================================================================
--- projects/mtg/Android	(revision 4847)
+++ projects/mtg/Android	(working copy)

Property changes on: projects/mtg/Android
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,4 ##
+bin
+bin
+gen
+obj

EDIT: Realized I made some code style violations; corrected them in the patch.
Locked