فهرست منبع

implemented mediaplayer for streaming music, implemented file handling code for non asset files (still needs a bit more work to finish)

Tim Newell 12 سال پیش
والد
کامیت
518d391b84

+ 8 - 0
engine/compilers/android/src/com/garagegames/torque2d/FileWalker.java

@@ -6,8 +6,10 @@ import java.util.Hashtable;
 import java.util.Vector;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
+import android.net.Uri;
 import android.util.Log;
 
 public class FileWalker
@@ -113,4 +115,10 @@ public class FileWalker
 		}
 		return ret;
 	}
+	
+	public static void OpenURL(Context context, String url)
+	{
+		Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+		context.startActivity(browserIntent);
+	}
 }

+ 70 - 0
engine/compilers/android/src/com/garagegames/torque2d/StreamingAudioPlayer.java

@@ -0,0 +1,70 @@
+package com.garagegames.torque2d;
+
+import java.io.IOException;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+
+public class StreamingAudioPlayer {
+	
+	private static MediaPlayer mediaPlayer = null;
+
+	public static void LoadMusicTrack(Context context, String filename)
+	{
+		mediaPlayer = new MediaPlayer();
+		AssetFileDescriptor fd;
+		try {
+			fd = context.getAssets().openFd(filename);
+			mediaPlayer.setDataSource(fd.getFileDescriptor());
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	
+	}
+	
+	public static void UnLoadMusicTrack()
+	{
+		if (mediaPlayer == null)
+			return;
+		
+		mediaPlayer.stop();
+		mediaPlayer.release();
+		mediaPlayer = null;
+		
+	}
+	
+	public static boolean isMusicTrackPlaying()
+	{
+		if (mediaPlayer == null)
+			return false;
+		
+		return mediaPlayer.isPlaying();
+	}
+	
+	public static void startMusicTrack()
+	{
+		if (mediaPlayer == null)
+			return;
+		
+		mediaPlayer.start();
+	}
+	
+	public static void stopMusicTrack()
+	{
+		if (mediaPlayer == null)
+			return;
+		
+		mediaPlayer.stop();
+	}
+	
+	public static void setMusicTrackVolume(float volume)
+	{
+		if (mediaPlayer == null)
+			return;
+		
+		mediaPlayer.setVolume(volume, volume);
+	}
+	
+}

+ 121 - 0
engine/compilers/android/src/com/garagegames/torque2d/T2DUtilities.java

@@ -0,0 +1,121 @@
+package com.garagegames.torque2d;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+
+public class T2DUtilities {
+		
+	public static void OpenURL(Context context, String url)
+	{
+		Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+		context.startActivity(browserIntent);
+	}
+	
+	public static void DisplayAlertOK(Context context, String title, String message)
+	{
+		AlertDialog.Builder builder = new AlertDialog.Builder(context);
+		builder.setMessage(message);
+		builder.setTitle(title);
+		builder.setCancelable(false);
+		builder.setNeutralButton("OK", new DialogInterface.OnClickListener() {
+		           public void onClick(DialogInterface dialog, int id) {
+		                
+		           }
+		       });
+		AlertDialog alert = builder.create();
+		alert.show();
+	}
+	
+	private static boolean retValue = false;
+	
+	public static boolean DisplayAlertOKCancel(Context context, String title, String message)
+	{
+		AlertDialog.Builder builder = new AlertDialog.Builder(context);
+		builder.setMessage(message);
+		builder.setTitle(title);
+		builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+	           public void onClick(DialogInterface dialog, int id) {
+	                retValue = true;
+	           }
+	       });
+		builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+	           public void onClick(DialogInterface dialog, int id) {
+	                retValue = false;
+	           }
+	       });
+		AlertDialog alert = builder.create();
+		alert.show();
+		
+		while(alert.isShowing())
+		{
+			try {
+				Thread.sleep(64);
+			} catch (InterruptedException e) {
+			}
+		}
+		
+		return retValue;
+		
+	}
+	
+	public static boolean DisplayAlertRetry(Context context, String title, String message)
+	{
+		AlertDialog.Builder builder = new AlertDialog.Builder(context);
+		builder.setMessage(message);
+		builder.setTitle(title);
+		builder.setPositiveButton("Retry", new DialogInterface.OnClickListener() {
+	           public void onClick(DialogInterface dialog, int id) {
+	                retValue = true;
+	           }
+	       });
+		builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+	           public void onClick(DialogInterface dialog, int id) {
+	                retValue = false;
+	           }
+	       });
+		AlertDialog alert = builder.create();
+		alert.show();
+		
+		while(alert.isShowing())
+		{
+			try {
+				Thread.sleep(64);
+			} catch (InterruptedException e) {
+			}
+		}
+		
+		return retValue;
+	}
+	
+	public static boolean DisplayAlertYesNo(Context context, String title, String message)
+	{
+		AlertDialog.Builder builder = new AlertDialog.Builder(context);
+		builder.setMessage(message);
+		builder.setTitle(title);
+		builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+	           public void onClick(DialogInterface dialog, int id) {
+	                retValue = true;;
+	           }
+	       });
+		builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
+	           public void onClick(DialogInterface dialog, int id) {
+	                retValue = false;
+	           }
+	       });
+		AlertDialog alert = builder.create();
+		alert.show();
+		
+		while(alert.isShowing())
+		{
+			try {
+				Thread.sleep(64);
+			} catch (InterruptedException e) {
+			}
+		}
+		
+		return retValue;
+	}
+}

+ 13 - 0
engine/source/platform/platformAssert.cc

@@ -177,3 +177,16 @@ ConsoleFunction( Assert, void, 3, 3, "(condition, message) - Fatal Script Assert
     // Process Assertion.
     AssertISV( dAtob(argv[1]), argv[2] );
 }
+
+ConsoleFunction(testAlertBox, void, 4, 4, "(title,message,retry)")
+{
+	if (dAtob(argv[3]) == true)
+	{
+		bool retry = Platform::AlertOKCancel(argv[1], argv[2]);
+		Con::printf("testAlertBox - retry = %d", retry);
+	}
+	else
+	{
+		Platform::AlertOK(argv[1], argv[2]);
+	}
+}

+ 12 - 9
engine/source/platformAndroid/AndroidAlerts.cpp

@@ -22,17 +22,22 @@
 #include "platformAndroid/platformAndroid.h"
 #include "platformAndroid/AndroidAlerts.h"
 
+//TODO: currently crashes so commented out
+
+extern void android_AlertOK(const char *title, const char *message);
+extern bool android_AlertOKCancel(const char *title, const char *message);
+extern bool android_AlertRetry(const char *title, const char *message);
+extern bool android_AlertYesNo(const char *title, const char *message);
+
 //-----------------------------------------------------------------------------
 void Platform::AlertOK(const char *windowTitle, const char *message)
 {
-	//TODO: alertok
-	adprintf("Alert OK!");
+	//android_AlertOK(windowTitle, message);
 }
 //-----------------------------------------------------------------------------
 bool Platform::AlertOKCancel(const char *windowTitle, const char *message)
 {
-	//TODO: AlertOKCancel
-	adprintf("Alert OK CANCEL!");
+	//return android_AlertOKCancel(windowTitle, message);
 	return false;
 }
 
@@ -40,15 +45,13 @@ bool Platform::AlertOKCancel(const char *windowTitle, const char *message)
 bool Platform::AlertRetry(const char *windowTitle, const char *message)
 {//retry/cancel
 	
-	//TODO: alertRetry
-	adprintf("Alert OK RETRY!");
+	//return android_AlertRetry(windowTitle, message);
 	return false;
 }
 
-
+//-----------------------------------------------------------------------------
 bool Platform::AlertYesNo(const char *windowTitle, const char *message)
 {
-	//TODO: alertYesNo
-	adprintf("Alert Yes No");
+	//return android_AlertYesNo(windowTitle, message);
 	return false;
 }

+ 555 - 13
engine/source/platformAndroid/AndroidFileio.cpp

@@ -42,21 +42,39 @@
 
 #define MAX_MAC_PATH_LONG     2048
 
+//-----------------------------------------------------------------------------
+//Cache handling functions
+//-----------------------------------------------------------------------------
+bool isCachePath(const char* path)
+{
+	//TODO: implement isCachePath
+	if (!path || !*path)
+	      return false;
+
+	return false;
+}
+
 //-----------------------------------------------------------------------------
 bool Platform::fileDelete(const char * name)
 {
-   Con::warnf("Platform::FileDelete() - Not supported on android.");
+	if(!name )
+	  return(false);
+
+   if (dStrlen(name) > MAX_MAC_PATH_LONG)
+	  Con::warnf("Platform::FileDelete() - Filename length is pretty long...");
    
-   return false;
+   return(remove(name) == 0); // remove returns 0 on success
 }
 
 
 //-----------------------------------------------------------------------------
 bool dFileTouch(const char *path)
 {
-	Con::warnf("Platform::dFileTouch() - Not supported on android.");
+	if (!path || !*path)
+	   return false;
 
-	return true;
+    // set file at path's modification and access times to now.
+    return( utimes( path, NULL) == 0); // utimes returns 0 on success.
 }
 
 //-----------------------------------------------------------------------------
@@ -73,6 +91,7 @@ File::File()
    buffer = NULL;
    size = 0;
    filePointer = 0;
+   handle = NULL;
 }
 
 //-----------------------------------------------------------------------------
@@ -84,8 +103,10 @@ File::File()
 //-----------------------------------------------------------------------------
 File::~File()
 {
-	if (buffer != NULL)
+	if (buffer != NULL || handle != NULL)
 		close();
+
+	handle = NULL;
 }
 
 
@@ -99,6 +120,66 @@ File::~File()
 //-----------------------------------------------------------------------------
 File::Status File::open(const char *filename, const AccessMode openMode)
 {
+	//If its a cache path then we need to open it using C methods not AssetManager
+   if (isCachePath(filename))
+   {
+	  if (dStrlen(filename) > MAX_MAC_PATH_LONG)
+	   	  Con::warnf("File::open: Filename length is pretty long...");
+
+	  // Close the file if it was already open...
+	  if (currentStatus != Closed)
+	  close();
+
+	  // create the appropriate type of file...
+	  switch (openMode)
+	  {
+	  case Read:
+		 handle = (void *)fopen(filename, "rb"); // read only
+		 break;
+	  case Write:
+		 handle = (void *)fopen(filename, "wb"); // write only
+		 break;
+	  case ReadWrite:
+		 handle = (void *)fopen(filename, "ab+"); // write(append) and read
+		 break;
+	  case WriteAppend:
+		 handle = (void *)fopen(filename, "ab"); // write(append) only
+		 break;
+	  default:
+		 AssertFatal(false, "File::open: bad access mode");
+	  }
+
+	  // handle not created successfully
+	  if (handle == NULL)
+	  return setStatus();
+
+	  // successfully created file, so set the file capabilities...
+	  switch (openMode)
+	  {
+	  case Read:
+		 capability = FileRead;
+		 break;
+	  case Write:
+	  case WriteAppend:
+		 capability = FileWrite;
+		 break;
+	  case ReadWrite:
+		 capability = FileRead | FileWrite;
+		 break;
+	  default:
+		 AssertFatal(false, "File::open: bad access mode");
+	  }
+
+	  // must set the file status before setting the position.
+	  currentStatus = Ok;
+
+	  if (openMode == ReadWrite)
+	  setPosition(0);
+
+	  // success!
+	  return currentStatus;
+   }
+
    if (dStrlen(filename) > MAX_MAC_PATH_LONG)
       Con::warnf("File::open: Filename length is pretty long...");
    
@@ -146,6 +227,14 @@ File::Status File::open(const char *filename, const AccessMode openMode)
 //-----------------------------------------------------------------------------
 U32 File::getPosition() const
 {
+	if (handle != NULL)
+	{
+	    AssertFatal(currentStatus != Closed , "File::getPosition: file closed");
+	    AssertFatal(handle != NULL, "File::getPosition: invalid file handle");
+
+	    return ftell((FILE*)handle);
+	}
+
    AssertFatal(currentStatus != Closed , "File::getPosition: file closed");
    AssertFatal(buffer != NULL, "File::getPosition: invalid file buffer");
    
@@ -166,6 +255,45 @@ U32 File::getPosition() const
 //-----------------------------------------------------------------------------
 File::Status File::setPosition(S32 position, bool absolutePos)
 {
+   if (handle != NULL)
+   {
+	  AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
+	  AssertFatal(handle != NULL, "File::setPosition: invalid file handle");
+
+	  if (currentStatus != Ok && currentStatus != EOS )
+		 return currentStatus;
+
+	  U32 finalPos;
+	  if(absolutePos)
+	  {
+		 // absolute position
+		 AssertFatal(0 <= position, "File::setPosition: negative absolute position");
+		 // position beyond EOS is OK
+		 fseek((FILE*)handle, position, SEEK_SET);
+		 finalPos = ftell((FILE*)handle);
+	  }
+	  else
+	  {
+		 // relative position
+		 AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position");
+		 // position beyond EOS is OK
+		 fseek((FILE*)handle, position, SEEK_CUR);
+		 finalPos = ftell((FILE*)handle);
+	  }
+
+	  // ftell returns -1 on error. set error status
+	  if (0xffffffff == finalPos)
+		 return setStatus();
+
+	  // success, at end of file
+	  else if (finalPos >= getSize())
+		 return currentStatus = EOS;
+
+	  // success!
+	  else
+		 return currentStatus = Ok;
+   }
+
    AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
    AssertFatal(buffer != NULL, "File::setPosition: invalid file buffer");
    
@@ -210,6 +338,26 @@ File::Status File::setPosition(S32 position, bool absolutePos)
 //-----------------------------------------------------------------------------
 U32 File::getSize() const
 {
+   if (handle != NULL)
+   {
+	  AssertWarn(Closed != currentStatus, "File::getSize: file closed");
+	  AssertFatal(handle != NULL, "File::getSize: invalid file handle");
+
+	  if (Ok == currentStatus || EOS == currentStatus)
+	  {
+		 struct stat statData;
+
+		 if(fstat(fileno((FILE*)handle), &statData) != 0)
+			return 0;
+
+		 // return the size in bytes
+		 return statData.st_size;
+	  }
+
+	  return 0;
+
+   }
+
    AssertWarn(Closed != currentStatus, "File::getSize: file closed");
    AssertFatal(buffer != NULL, "File::getSize: invalid file buffer");
    
@@ -228,6 +376,17 @@ U32 File::getSize() const
 //-----------------------------------------------------------------------------
 File::Status File::flush()
 {
+	if (handle != NULL)
+	{
+	   AssertFatal(Closed != currentStatus, "File::flush: file closed");
+	   AssertFatal(handle != NULL, "File::flush: invalid file handle");
+	   AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
+
+	   if (fflush((FILE*)handle) != 0)
+		  return setStatus();
+	   else
+		  return currentStatus = Ok;
+	}
    AssertFatal(Closed != currentStatus, "File::flush: file closed");
    AssertFatal(buffer != NULL, "File::flush: invalid file buffer");
    AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
@@ -242,6 +401,22 @@ File::Status File::flush()
 //-----------------------------------------------------------------------------
 File::Status File::close()
 {
+	if (handle != NULL)
+	{
+	   // check if it's already closed...
+	   if (Closed == currentStatus)
+		  return currentStatus;
+
+	   // it's not, so close it...
+	   if (handle != NULL)
+	   {
+		  if (fclose((FILE*)handle) != 0)
+			 return setStatus();
+	   }
+	   handle = NULL;
+	   return currentStatus = Closed;
+	}
+
    // check if it's already closed...
    if (Closed == currentStatus)
       return currentStatus;
@@ -271,6 +446,24 @@ File::Status File::getStatus() const
 //-----------------------------------------------------------------------------
 File::Status File::setStatus()
 {
+	if (handle != NULL)
+	{
+	   switch (errno)
+	   {
+		  case EACCES:   // permission denied
+			 currentStatus = IOError;
+			 break;
+		  case EBADF:   // Bad File Pointer
+		  case EINVAL:   // Invalid argument
+		  case ENOENT:   // file not found
+		  case ENAMETOOLONG:
+		  default:
+			 currentStatus = UnknownError;
+	   }
+
+	   return currentStatus;
+	}
+
    currentStatus = UnknownError;
    return currentStatus;
 }
@@ -291,6 +484,32 @@ File::Status File::setStatus(File::Status status)
 //-----------------------------------------------------------------------------
 File::Status File::read(U32 _size, char *dst, U32 *bytesRead)
 {
+	if (handle != NULL)
+	{
+	   AssertFatal(Closed != currentStatus, "File::read: file closed");
+	   AssertFatal(handle != NULL, "File::read: invalid file handle");
+	   AssertFatal(NULL != dst, "File::read: NULL destination pointer");
+	   AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
+	   AssertWarn(0 != size, "File::read: size of zero");
+
+	   if (Ok != currentStatus || 0 == size)
+		  return currentStatus;
+
+	   // read from stream
+	   U32 nBytes = fread(dst, 1, size, (FILE*)handle);
+
+	   // did we hit the end of the stream?
+	   if( nBytes != size)
+		  currentStatus = EOS;
+
+	   // if bytesRead is a valid pointer, send number of bytes read there.
+	   if(bytesRead)
+		  *bytesRead = nBytes;
+
+	   // successfully read size bytes
+	   return currentStatus;
+	}
+
    AssertFatal(Closed != currentStatus, "File::read: file closed");
    AssertFatal(buffer != NULL, "File::read: invalid file buffer");
    AssertFatal(NULL != dst, "File::read: NULL destination pointer");
@@ -341,6 +560,33 @@ File::Status File::read(U32 _size, char *dst, U32 *bytesRead)
 //-----------------------------------------------------------------------------
 File::Status File::write(U32 size, const char *src, U32 *bytesWritten)
 {
+	if (handle != NULL)
+	{
+	   AssertFatal(Closed != currentStatus, "File::write: file closed");
+	   AssertFatal(handle != NULL, "File::write: invalid file handle");
+	   AssertFatal(NULL != src, "File::write: NULL source pointer");
+	   AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability");
+	   AssertWarn(0 != size, "File::write: size of zero");
+
+	   if ((Ok != currentStatus && EOS != currentStatus) || 0 == size)
+		  return currentStatus;
+
+	   // write bytes to the stream
+	   U32 nBytes = fwrite(src, 1, size,(FILE*)handle);
+
+	   // if we couldn't write everything, we've got a problem. set error status.
+	   if(nBytes != size)
+		  setStatus();
+
+	   // if bytesWritten is a valid pointer, put number of bytes read there.
+	   if(bytesWritten)
+		  *bytesWritten = nBytes;
+
+	   // return current File status, whether good or ill.
+	   return currentStatus;
+
+	}
+
    AssertFatal(0, "File::write: Not supported on Android.");
    return setStatus();
 }
@@ -374,21 +620,73 @@ bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *mo
    // and UNIX does not keep a record of a file's creation time anywhere.
    // So instead of creation time we return changed time,
    // just like the Linux platform impl does.
+  if (!path || !*path)
+	 return false;
+
+  struct stat statData;
+
+  if (stat(path, &statData) == -1)
+	 return false;
+
+  if(createTime)
+	 *createTime = statData.st_ctime;
+
+  if(modifyTime)
+	 *modifyTime = statData.st_mtime;
+
+  return true;
+
    
-   if (!path || !*path) 
-      return false;
-   
-   Con::warnf("Platform::getFileTimes - Not supported on android.");
-   
-   return true;
 }
 
 
 //-----------------------------------------------------------------------------
 bool Platform::createPath(const char *file)
 {
-   Con::warnf("Platform::createPath() - Not supported on android.");
-   return false;
+   //<Mat> needless console noise
+   //Con::warnf("creating path %s",file);
+   // if the path exists, we're done.
+   struct stat statData;
+   if( stat(file, &statData) == 0 )
+   {
+	  return true;               // exists, rejoice.
+   }
+
+   // get the parent path.
+   // we're not using basename because it's not thread safe.
+   const U32 len = dStrlen(file) + 1;
+   char parent[len];
+   bool isDirPath = false;
+
+   dSprintf(parent, len, "%s", file);
+
+   if(parent[len - 2] == '/')
+   {
+	  parent[len - 2] = '\0';    // cut off the trailing slash, if there is one
+	  isDirPath = true;          // we got a trailing slash, so file is a directory.
+   }
+
+   // recusively create the parent path.
+   // only recurse if newpath has a slash that isn't a leading slash.
+   char *slash = dStrrchr(parent,'/');
+   if( slash && slash != parent)
+   {
+	  // snip the path just after the last slash.
+	  slash[1] = '\0';
+	  // recusively create the parent path. fail if parent path creation failed.
+	  if(!Platform::createPath(parent))
+		 return false;
+   }
+
+   // create *file if it is a directory path.
+   if(isDirPath)
+   {
+	  // try to create the directory
+	  if( mkdir(file, 0777) != 0) // app may reside in global apps dir, and so must be writable to all.
+		 return false;
+   }
+
+   return true;
 }
 
 
@@ -475,6 +773,21 @@ bool Platform::isFile(const char *path)
    if (!path || !*path) 
       return false;
 
+   if (isCachePath(path))
+   {
+	  // make sure we can stat the file
+	  struct stat statData;
+	  if( stat(path, &statData) < 0 )
+		 return false;
+
+	  // now see if it's a regular file
+	  if( (statData.st_mode & S_IFMT) == S_IFREG)
+		 return true;
+
+	  return false;
+   }
+
+
    return android_IsFile(path);
 }
 
@@ -485,6 +798,20 @@ bool Platform::isDirectory(const char *path)
    if (!path || !*path) 
       return false;
    
+   if (isCachePath(path))
+   {
+	  // make sure we can stat the file
+	  struct stat statData;
+	  if( stat(path, &statData) < 0 )
+		 return false;
+
+	  // now see if it's a directory
+	  if( (statData.st_mode & S_IFMT) == S_IFDIR)
+		 return true;
+
+	  return false;
+   }
+
    return android_IsDir(path);
 }
 
@@ -494,6 +821,16 @@ S32 Platform::getFileSize(const char* pFilePath)
    if (!pFilePath || !*pFilePath) 
       return 0;
    
+   if (isCachePath(pFilePath))
+   {
+	  struct stat statData;
+	  if( stat(pFilePath, &statData) < 0 )
+		 return 0;
+
+	  // and return it's size in bytes
+	  return (S32)statData.st_size;
+   }
+
    return android_GetFileSize(pFilePath);
 }
 
@@ -525,9 +862,44 @@ inline bool isGoodDirectory(const char* path)
            && !Platform::isExcludedDirectory(name)); // not excluded
 }
 
+inline bool isGoodDirectoryCache(dirent* entry)
+{
+   return (entry->d_type == DT_DIR                          // is a dir
+           && dStrcmp(entry->d_name,".") != 0                 // not here
+           && dStrcmp(entry->d_name,"..") != 0                // not parent
+           && !Platform::isExcludedDirectory(entry->d_name)); // not excluded
+}
+
 //-----------------------------------------------------------------------------
 bool Platform::hasSubDirectory(const char *path) 
 {
+	if (isCachePath(path))
+	{
+	   DIR *dir;
+	   dirent *entry;
+
+	   dir = opendir(path);
+	   if(!dir)
+		  return false; // we got a bad path, so no, it has no subdirectory.
+
+	   while( true )
+	   {
+		   entry = readdir(dir);
+		   if ( entry == NULL )
+			   break;
+
+		  if(isGoodDirectoryCache(entry) )
+		  {
+			 closedir(dir);
+			 return true; // we have a subdirectory, that isnt on the exclude list.
+		  }
+	   }
+
+	   closedir(dir);
+	   return false; // either this dir had no subdirectories, or they were all on the exclude list.
+
+	}
+
 	android_InitDirList(path);
 	char dir[80];
 	char pdir[255];
@@ -614,9 +986,107 @@ bool recurseDumpDirectories(const char *basePath, const char *path, Vector<Strin
      return true;
 }
 
+//-----------------------------------------------------------------------------
+bool recurseDumpDirectoriesCache(const char *basePath, const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
+{
+   DIR *dir;
+   dirent *entry;
+   const U32 len = dStrlen(basePath) + dStrlen(path) + 2;
+   char pathbuf[len];
+
+   // construct the file path
+   dSprintf(pathbuf, len, "%s/%s", basePath, path);
+
+   // be sure it opens.
+   dir = opendir(pathbuf);
+   if(!dir)
+      return false;
+
+   // look inside the current directory
+   while( true )
+   {
+       entry = readdir(dir);
+       if ( entry == NULL )
+           break;
+
+      // we just want directories.
+      if(!isGoodDirectoryCache(entry))
+         continue;
+
+      // TODO: better unicode file name handling
+      //      // Apple's file system stores unicode file names in decomposed form.
+      //      // ATSUI will not reliably draw out just the accent character by itself,
+      //      // so our text renderer has no chance of rendering decomposed form unicode.
+      //      // We have to convert the entry name to precomposed normalized form.
+      //      CFStringRef cfdname = CFStringCreateWithCString(NULL,entry->d_name,kCFStringEncodingUTF8);
+      //      CFMutableStringRef cfentryName = CFStringCreateMutableCopy(NULL,0,cfdname);
+      //      CFStringNormalize(cfentryName,kCFStringNormalizationFormC);
+      //
+      //      U32 entryNameLen = CFStringGetLength(cfentryName) * 4 + 1;
+      //      char entryName[entryNameLen];
+      //      CFStringGetCString(cfentryName, entryName, entryNameLen, kCFStringEncodingUTF8);
+      //      entryName[entryNameLen-1] = NULL; // sometimes, CFStringGetCString() doesn't null terminate.
+      //      CFRelease(cfentryName);
+      //      CFRelease(cfdname);
+
+      // construct the new path string, we'll need this below.
+      const U32 newpathlen = dStrlen(path) + dStrlen(entry->d_name) + 2;
+      char newpath[newpathlen];
+      if(dStrlen(path) > 0)
+      {
+          dSprintf(newpath, newpathlen, "%s/%s", path, entry->d_name);
+      }
+      else
+      {
+         dSprintf(newpath, newpathlen, "%s", entry->d_name);
+      }
+
+      // we have a directory, add it to the list.
+      if( noBasePath )
+      {
+         directoryVector.push_back(StringTable->insert(newpath));
+      }
+      else
+      {
+         const U32 fullpathlen = dStrlen(basePath) + dStrlen(newpath) + 2;
+         char fullpath[fullpathlen];
+         dSprintf(fullpath, fullpathlen, "%s/%s",basePath,newpath);
+         directoryVector.push_back(StringTable->insert(fullpath));
+      }
+
+      // and recurse into it, unless we've run out of depth
+      if( depth != 0) // passing a val of -1 as the recurse depth means go forever
+         recurseDumpDirectoriesCache(basePath, newpath, directoryVector, depth-1, noBasePath);
+   }
+   closedir(dir);
+   return true;
+}
+
 //-----------------------------------------------------------------------------
 bool Platform::dumpDirectories(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
 {
+	if (isCachePath(path))
+	{
+	   PROFILE_START(dumpDirectories);
+
+	   ResourceManager->initExcludedDirectories();
+
+	   const S32 len = dStrlen(path)+1;
+	   char newpath[len];
+	   dSprintf(newpath, len, "%s", path);
+	   if(newpath[len - 1] == '/')
+		  newpath[len - 1] = '\0'; // cut off the trailing slash, if there is one
+
+		// Insert base path to follow what Windows does.
+		if ( !noBasePath )
+			directoryVector.push_back(StringTable->insert(newpath));
+
+		bool ret = recurseDumpDirectoriesCache(newpath, "", directoryVector, depth, noBasePath);
+	   PROFILE_END();
+
+	   return ret;
+	}
+
    PROFILE_START(dumpDirectories);
 
    ResourceManager->initExcludedDirectories();
@@ -712,10 +1182,82 @@ static bool recurseDumpPath(const char* curPath, Vector<Platform::FileInfo>& fil
    
 }
 
+//-----------------------------------------------------------------------------
+static bool recurseDumpPathCache(const char* curPath, Vector<Platform::FileInfo>& fileVector, U32 depth)
+{
+   DIR *dir;
+   dirent *entry;
+
+   // be sure it opens.
+   dir = opendir(curPath);
+   if(!dir)
+      return false;
+
+   // look inside the current directory
+   while( true )
+   {
+       entry = readdir(dir);
+       if ( entry == NULL )
+           break;
+
+      // construct the full file path. we need this to get the file size and to recurse
+      const U32 len = dStrlen(curPath) + dStrlen(entry->d_name) + 2;
+      char pathbuf[len];
+      dSprintf( pathbuf, len, "%s/%s", curPath, entry->d_name);
+
+      // ok, deal with directories and files seperately.
+      if( entry->d_type == DT_DIR )
+      {
+         if( depth == 0)
+            continue;
+
+         // filter out dirs we dont want.
+         if( !isGoodDirectoryCache(entry) )
+            continue;
+
+         // recurse into the dir
+         recurseDumpPathCache( pathbuf, fileVector, depth-1);
+      }
+      else
+      {
+         //add the file entry to the list
+         // unlike recurseDumpDirectories(), we need to return more complex info here.
+          //<Mat> commented this out in case we ever want a dir file printout again
+          //printf( "File Name: %s ", entry->d_name );
+         const U32 fileSize = Platform::getFileSize(pathbuf);
+         fileVector.increment();
+         Platform::FileInfo& rInfo = fileVector.last();
+         rInfo.pFullPath = StringTable->insert(curPath);
+         rInfo.pFileName = StringTable->insert(entry->d_name);
+         rInfo.fileSize  = fileSize;
+      }
+   }
+   closedir(dir);
+   return true;
+
+}
+
 
 //-----------------------------------------------------------------------------
 bool Platform::dumpPath(const char *path, Vector<Platform::FileInfo>& fileVector, S32 depth)
 {
+	if (isCachePath(path))
+	{
+		PROFILE_START(dumpPath);
+		const S32 len = dStrlen(path) + 1;
+	   char newpath[len];
+
+		dSprintf(newpath, len, "%s", path);
+
+	   if(newpath[len - 2] == '/')
+		  newpath[len - 2] = '\0'; // cut off the trailing slash, if there is one
+
+	   bool ret = recurseDumpPathCache( newpath, fileVector, depth);
+	   PROFILE_END();
+
+	   return ret;
+	}
+
    PROFILE_START(dumpPath);
    char apath[80];
    if (path[0] == '/')

+ 22 - 22
engine/source/platformAndroid/AndroidStreamSource.cc

@@ -22,56 +22,56 @@
 
 #include "AndroidStreamSource.h"
 
-#define BUFFERSIZE 32768
+extern void android_LoadMusicTrack( const char *mFilename );
+extern void android_UnLoadMusicTrack();
+extern bool android_isMusicTrackPlaying();
+extern void android_StartMusicTrack();
+extern void android_StopMusicTrack();
+extern void android_setMusicTrackVolume(F32 volume);
 
 AndroidStreamSource::AndroidStreamSource(const char *filename)  {
 	this->registerObject();
 	int len = dStrlen( filename );
 	mFilename = new char[len + 1];
 	dStrcpy( mFilename, filename );
-	//TODO: streaming music
-	//SoundEngine::SoundEngine_LoadBackgroundMusicTrack( mFilename, true, false );
+
+	android_LoadMusicTrack( mFilename );
 }
 
 AndroidStreamSource::~AndroidStreamSource() {
 	stop();
 	delete [] mFilename;
-	//TODO: streaming music
-	//SoundEngine::SoundEngine_UnloadBackgroundMusicTrack();
+	android_UnLoadMusicTrack();
 }
 
 bool AndroidStreamSource::isPlaying() {
-	return true;
+	return android_isMusicTrackPlaying();
 }
 
 bool AndroidStreamSource::start( bool loop ) {
-	//TODO: streaming music
-	/*
-	SoundEngine::SoundEngine_LoadBackgroundMusicTrack( mFilename, true, false );
-	SoundEngine::SoundEngine_StartBackgroundMusic();
+
+	android_LoadMusicTrack( mFilename );
+	android_StartMusicTrack();
 	if( !loop ) {
 		//stop at end
-		SoundEngine::SoundEngine_StopBackgroundMusic( true );
+		android_StopMusicTrack();
 		Con::executef(1,"onAndroidStreamEnd");
 	}
-	*/
+
 	return true;
 }
 
 bool AndroidStreamSource::stop() {
-	//false == stop now
-	//TODO: streaming music
-	/*
-	SoundEngine::SoundEngine_StopBackgroundMusic( false );
-	SoundEngine::SoundEngine_UnloadBackgroundMusicTrack();
-	*/
+
+	android_StopMusicTrack();
+	android_UnLoadMusicTrack();
+
 	return true;
 }
     
 bool AndroidStreamSource::setVolume( F32 volume) {
-	//TODO: streaming music
-	/*
-    SoundEngine::SoundEngine_SetBackgroundMusicVolume(volume);
-    */
+
+	android_setMusicTrackVolume(volume);
+
     return true;
 }

+ 0 - 10
engine/source/platformAndroid/AndroidWindow.cpp

@@ -220,16 +220,6 @@ bool appIsRunning(int batchId)
     return false;
 }
 
-bool Platform::openWebBrowser(const char *webAddress)
-{
-	//TODO: convert to JNI code
-	//Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));
-	//startActivity(browserIntent);
-
-
-	return true;
-}
-
 ConsoleFunction(setScreenOrientation, bool, 3, 3, "Sets the orientation of the screen ( portrait/landscape, upside down or right-side up )\n"
         "@(bool portrait, bool upside_down)"){
     adprintf("screen orientation is set via the manifest file on android");

+ 454 - 2
engine/source/platformAndroid/T2DActivity.cpp

@@ -936,8 +936,6 @@ struct engine engine;
  */
 void android_main(struct android_app* state) {
 
-	sleep(10);
-
 	//init startup time so U32 doesnt overflow
 	android_StartupTime();
 
@@ -1349,6 +1347,460 @@ ConsoleFunction(GetAndroidResolution, const char*, 1, 1, "Returns the resolution
 	return buffer;
 }
 
+bool Platform::openWebBrowser(const char *webAddress)
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return false;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/T2DUtilities");
+	jclass T2DUtilitiesClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jstring strURL = lJNIEnv->NewStringUTF(webAddress);
+	jmethodID MethodT2DUtilities = lJNIEnv->GetStaticMethodID(T2DUtilitiesClass, "OpenURL", "(Landroid/content/Context;Ljava/lang/String;)V");
+	lJNIEnv->CallStaticVoidMethod(T2DUtilitiesClass, MethodT2DUtilities, lNativeActivity, strURL);
+	lJNIEnv->DeleteLocalRef(strClassName);
+	lJNIEnv->DeleteLocalRef(strURL);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+
+	return true;
+}
+
+void android_AlertOK(const char *title, const char *message)
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/T2DUtilities");
+	jclass T2DUtilitiesClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jstring strTitle = lJNIEnv->NewStringUTF(title);
+	jstring strMessage = lJNIEnv->NewStringUTF(message);
+	jmethodID MethodT2DUtilities = lJNIEnv->GetStaticMethodID(T2DUtilitiesClass, "DisplayAlertOK", "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V");
+	lJNIEnv->CallStaticVoidMethod(T2DUtilitiesClass, MethodT2DUtilities, lNativeActivity, strTitle, strMessage);
+	lJNIEnv->DeleteLocalRef(strClassName);
+	lJNIEnv->DeleteLocalRef(strTitle);
+	lJNIEnv->DeleteLocalRef(strMessage);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+}
+
+bool android_AlertOKCancel(const char *title, const char *message)
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return false;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/T2DUtilities");
+	jclass T2DUtilitiesClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jstring strTitle = lJNIEnv->NewStringUTF(title);
+	jstring strMessage = lJNIEnv->NewStringUTF(message);
+	jmethodID MethodT2DUtilities = lJNIEnv->GetStaticMethodID(T2DUtilitiesClass, "DisplayAlertOKCancel", "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z");
+	jboolean jret = lJNIEnv->CallStaticBooleanMethod(T2DUtilitiesClass, MethodT2DUtilities, lNativeActivity, strTitle, strMessage);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+	lJNIEnv->DeleteLocalRef(strTitle);
+	lJNIEnv->DeleteLocalRef(strMessage);
+
+	bool ret = jret;
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+
+	return ret;
+}
+
+bool android_AlertRetry(const char *title, const char *message)
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return false;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/T2DUtilities");
+	jclass T2DUtilitiesClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jstring strTitle = lJNIEnv->NewStringUTF(title);
+	jstring strMessage = lJNIEnv->NewStringUTF(message);
+	jmethodID MethodT2DUtilities = lJNIEnv->GetStaticMethodID(T2DUtilitiesClass, "DisplayAlertRetry", "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z");
+	jboolean jret = lJNIEnv->CallStaticBooleanMethod(T2DUtilitiesClass, MethodT2DUtilities, lNativeActivity, strTitle, strMessage);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+	lJNIEnv->DeleteLocalRef(strTitle);
+	lJNIEnv->DeleteLocalRef(strMessage);
+
+	bool ret = jret;
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+
+	return ret;
+}
+
+bool android_AlertYesNo(const char *title, const char *message)
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return false;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/T2DUtilities");
+	jclass T2DUtilitiesClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jstring strTitle = lJNIEnv->NewStringUTF(title);
+	jstring strMessage = lJNIEnv->NewStringUTF(message);
+	jmethodID MethodT2DUtilities = lJNIEnv->GetStaticMethodID(T2DUtilitiesClass, "DisplayAlertYesNo", "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z");
+	jboolean jret = lJNIEnv->CallStaticBooleanMethod(T2DUtilitiesClass, MethodT2DUtilities, lNativeActivity, strTitle, strMessage);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+	lJNIEnv->DeleteLocalRef(strTitle);
+	lJNIEnv->DeleteLocalRef(strMessage);
+
+	bool ret = jret;
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+
+	return ret;
+}
+
+void android_LoadMusicTrack( const char *mFilename )
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/StreamingAudioPlayer");
+	jclass StreamingAudioPlayerClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jstring strFilename = lJNIEnv->NewStringUTF(mFilename);
+	jmethodID MethodStreamingAudioPlayer = lJNIEnv->GetStaticMethodID(StreamingAudioPlayerClass, "LoadMusicTrack", "(Landroid/content/Context;Ljava/lang/String;)V");
+	lJNIEnv->CallStaticVoidMethod(StreamingAudioPlayerClass, MethodStreamingAudioPlayer, lNativeActivity, strFilename);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+	lJNIEnv->DeleteLocalRef(strFilename);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+}
+
+void android_UnLoadMusicTrack()
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/StreamingAudioPlayer");
+	jclass StreamingAudioPlayerClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jmethodID MethodStreamingAudioPlayer = lJNIEnv->GetStaticMethodID(StreamingAudioPlayerClass, "UnLoadMusicTrack", "()V");
+	lJNIEnv->CallStaticVoidMethod(StreamingAudioPlayerClass, MethodStreamingAudioPlayer);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+}
+
+bool android_isMusicTrackPlaying()
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return false;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/StreamingAudioPlayer");
+	jclass StreamingAudioPlayerClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jmethodID MethodStreamingAudioPlayer = lJNIEnv->GetStaticMethodID(StreamingAudioPlayerClass, "isMusicTrackPlaying", "()Z");
+	jboolean jret = lJNIEnv->CallStaticBooleanMethod(StreamingAudioPlayerClass, MethodStreamingAudioPlayer);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+
+	bool ret = jret;
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+
+	return ret;
+}
+
+void android_StartMusicTrack()
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/StreamingAudioPlayer");
+	jclass StreamingAudioPlayerClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jmethodID MethodStreamingAudioPlayer = lJNIEnv->GetStaticMethodID(StreamingAudioPlayerClass, "startMusicTrack", "()V");
+	lJNIEnv->CallStaticVoidMethod(StreamingAudioPlayerClass, MethodStreamingAudioPlayer);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+}
+
+void android_StopMusicTrack()
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/StreamingAudioPlayer");
+	jclass StreamingAudioPlayerClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jmethodID MethodStreamingAudioPlayer = lJNIEnv->GetStaticMethodID(StreamingAudioPlayerClass, "stopMusicTrack", "()V");
+	lJNIEnv->CallStaticVoidMethod(StreamingAudioPlayerClass, MethodStreamingAudioPlayer);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+}
+
+void android_setMusicTrackVolume(F32 volume)
+{
+	// Attaches the current thread to the JVM.
+	jint lResult;
+	jint lFlags = 0;
+
+	JavaVM* lJavaVM = engine.app->activity->vm;
+	JNIEnv* lJNIEnv = engine.app->activity->env;
+
+	JavaVMAttachArgs lJavaVMAttachArgs;
+	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
+	lJavaVMAttachArgs.name = "NativeThread";
+	lJavaVMAttachArgs.group = NULL;
+
+	lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
+	if (lResult == JNI_ERR) {
+		return;
+	}
+
+	// Retrieves NativeActivity.
+	jobject lNativeActivity = engine.app->activity->clazz;
+	jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+
+	jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
+	jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
+	jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
+	jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+	jstring strClassName = lJNIEnv->NewStringUTF("com/garagegames/torque2d/StreamingAudioPlayer");
+	jclass StreamingAudioPlayerClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, strClassName);
+	jmethodID MethodStreamingAudioPlayer = lJNIEnv->GetStaticMethodID(StreamingAudioPlayerClass, "setMusicTrackVolume", "(F)V");
+	lJNIEnv->CallStaticVoidMethod(StreamingAudioPlayerClass, MethodStreamingAudioPlayer, (jfloat)volume);
+
+	lJNIEnv->DeleteLocalRef(strClassName);
+
+		// Finished with the JVM.
+	lJavaVM->DetachCurrentThread();
+}
+
 ConsoleFunction(doDeviceVibrate, void, 1, 1, "Makes the device do a quick vibration. Only works on devices with vibration functionality.")
 {
 	// Vibrate for 500 milliseconds