1977 lines
70 KiB
Java
1977 lines
70 KiB
Java
package org.libsdl.app;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.app.ProgressDialog;
|
|
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
|
|
import android.content.res.Configuration;
|
|
|
|
import android.graphics.Canvas;
|
|
import android.graphics.PixelFormat;
|
|
|
|
import android.hardware.Sensor;
|
|
import android.hardware.SensorEvent;
|
|
import android.hardware.SensorEventListener;
|
|
import android.hardware.SensorManager;
|
|
|
|
import android.media.AudioFormat;
|
|
import android.media.AudioManager;
|
|
import android.media.AudioTrack;
|
|
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.os.Environment;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.StrictMode;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.view.KeyEvent;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.MotionEvent;
|
|
import android.view.SubMenu;
|
|
import android.view.SurfaceHolder;
|
|
import android.view.SurfaceView;
|
|
import android.view.VelocityTracker;
|
|
import android.view.View;
|
|
|
|
import android.view.View.OnKeyListener;
|
|
|
|
import android.view.WindowManager;
|
|
|
|
import android.widget.FrameLayout;
|
|
|
|
import android.widget.FrameLayout.LayoutParams;
|
|
|
|
import android.widget.PopupMenu;
|
|
|
|
import net.wagic.app.R;
|
|
|
|
import net.wagic.utils.DeckImporter;
|
|
import net.wagic.utils.ImgDownloader;
|
|
import net.wagic.utils.StorageOptions;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
|
|
import java.net.URL;
|
|
import java.net.URLConnection;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Enumeration;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipFile;
|
|
|
|
import javax.microedition.khronos.egl.EGL10;
|
|
import javax.microedition.khronos.egl.EGLConfig;
|
|
import javax.microedition.khronos.egl.EGLContext;
|
|
import javax.microedition.khronos.egl.EGLDisplay;
|
|
import javax.microedition.khronos.egl.EGLSurface;
|
|
|
|
|
|
/**
|
|
* SDL Activity
|
|
*/
|
|
public class SDLActivity extends Activity implements OnKeyListener {
|
|
private static final String TAG = SDLActivity.class.getCanonicalName();
|
|
|
|
// Main components
|
|
private static SDLActivity mSingleton;
|
|
private static SDLSurface mSurface;
|
|
|
|
// Audio
|
|
private static Thread mAudioThread;
|
|
private static AudioTrack mAudioTrack;
|
|
|
|
// Resource download
|
|
public static final int DIALOG_DOWNLOAD_PROGRESS = 0;
|
|
public static final int DIALOG_DOWNLOAD_ERROR = 1;
|
|
|
|
//public final static String RES_FOLDER = Environment.getExternalStorageDirectory().getPath() + "/Wagic/Res/";
|
|
public static String RES_FILENAME = "";
|
|
public static String databaseurl = "https://github.com/WagicProject/wagic/releases/latest/download/CardImageLinks.csv";
|
|
|
|
// Preferences
|
|
public static final String kWagicSharedPreferencesKey = "net.wagic.app.preferences.wagic";
|
|
public static final String kStoreDataOnRemovableSdCardPreference = "StoreDataOnRemovableStorage";
|
|
public static final String kSaveDataPathPreference = "StorageDataLocation";
|
|
public static final String kWagicDataStorageOptionsKey = "dataStorageOptions";
|
|
public static final int kStorageDataOptionsMenuId = 2000;
|
|
public static final int kdownloadResOptionsMenuId = 4000;
|
|
public static final int kOtherOptionsMenuId = 3000;
|
|
|
|
static {
|
|
System.loadLibrary("SDL");
|
|
// System.loadLibrary("SDL_image");
|
|
// System.loadLibrary("SDL_mixer");
|
|
// System.loadLibrary("SDL_ttf");
|
|
System.loadLibrary("main");
|
|
}
|
|
|
|
// Messages from the SDLMain thread
|
|
static int COMMAND_CHANGE_TITLE = 1;
|
|
static int COMMAND_JGE_MSG = 2;
|
|
|
|
// Audio
|
|
private static Object buf;
|
|
|
|
//import deck globals
|
|
public ArrayList<String> myresult = new ArrayList<String>();
|
|
public String myclickedItem = "";
|
|
private ProgressDialog mProgressDialog;
|
|
private AlertDialog mErrorDialog;
|
|
public String mErrorMessage = "";
|
|
public Boolean mErrorHappened = false;
|
|
public String systemFolder = Environment.getExternalStorageDirectory()
|
|
.getPath() + "/Wagic/Res/";
|
|
private String userFolder = Environment.getExternalStorageDirectory()
|
|
.getPath() + "/Wagic/User/";
|
|
|
|
// path to the onboard sd card that is not removable (typically /mnt/sdcard )
|
|
private String internalPath = "";
|
|
|
|
// path to removable sd card (on motorala devices /mnt/sdcard-ext, samsung devices: /mnt/sdcard/external_sd )
|
|
private String sdcardPath = "";
|
|
|
|
// Android only supports internal memory and internal sdcard. removable media is not currently accessible via API
|
|
// using StorageOptions for now gives us a temporary interface to scan all available mounted drives.
|
|
private Context mContext;
|
|
String set = "";
|
|
String[] availableSets;
|
|
ArrayList<String> selectedSets;
|
|
boolean[] checkedSet;
|
|
Integer totalset = 0;
|
|
boolean finished = false;
|
|
boolean loadResInProgress = false;
|
|
ProgressDialog progressBarDialogRes;
|
|
boolean fast = false;
|
|
String targetRes = "High";
|
|
boolean error = false;
|
|
boolean skipDownloaded = false;
|
|
boolean borderless = false;
|
|
String res = "";
|
|
public volatile boolean downloadInProgress = false;
|
|
public volatile boolean paused = false;
|
|
ProgressDialog cardDownloader;
|
|
volatile int currentIndex = 0;
|
|
MenuItem importDecks;
|
|
MenuItem downloader;
|
|
MenuItem about;
|
|
MenuItem storage;
|
|
MenuItem resource;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Accessors
|
|
public String getSystemStorageLocation() {
|
|
return systemFolder;
|
|
}
|
|
|
|
public String getUserStorageLocation() {
|
|
return userFolder;
|
|
}
|
|
|
|
// setters
|
|
public void updateStorageLocations() {
|
|
boolean usesInternalSdCard = (!getSharedPreferences(kWagicSharedPreferencesKey,
|
|
MODE_PRIVATE)
|
|
.getBoolean(kStoreDataOnRemovableSdCardPreference,
|
|
false)) &&
|
|
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
|
|
|
systemFolder = (usesInternalSdCard ? internalPath : sdcardPath) +
|
|
"/Res/";
|
|
userFolder = (usesInternalSdCard ? internalPath : sdcardPath) +
|
|
"/User/";
|
|
}
|
|
|
|
/**
|
|
* checks to see if the device has a memory card to write to that is in a valid state.
|
|
*
|
|
* @return true if the device can write to the sdcard, false if not.
|
|
*/
|
|
public boolean checkStorageState() {
|
|
SharedPreferences settings = getSharedPreferences(kWagicSharedPreferencesKey,
|
|
MODE_PRIVATE);
|
|
boolean mExternalStorageAvailable = false;
|
|
boolean mExternalStorageWriteable = false;
|
|
String state = Environment.getExternalStorageState();
|
|
boolean useSdCard = (!settings.getBoolean(kStoreDataOnRemovableSdCardPreference,
|
|
false)) && mExternalStorageWriteable;
|
|
String systemStoragePath = getSystemStorageLocation();
|
|
|
|
if (useSdCard && (systemStoragePath.indexOf(sdcardPath) != -1)) {
|
|
Log.i(TAG, "Data will be written to sdcard.");
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!useSdCard && (systemStoragePath.indexOf(internalPath) != -1)) {
|
|
Log.i(TAG, "Data will be written to internal storage.");
|
|
|
|
return true;
|
|
}
|
|
|
|
if (Environment.MEDIA_MOUNTED.equals(state)) {
|
|
// We can read and write the media
|
|
mExternalStorageAvailable = mExternalStorageWriteable = true;
|
|
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
|
|
// We can only read the media
|
|
mExternalStorageAvailable = true;
|
|
mExternalStorageWriteable = false;
|
|
} else {
|
|
// Something else is wrong. It may be one of many other states, but all we need
|
|
// to know is we can neither read nor write
|
|
mExternalStorageAvailable = mExternalStorageWriteable = false;
|
|
}
|
|
|
|
return (mExternalStorageAvailable && mExternalStorageWriteable);
|
|
}
|
|
|
|
private boolean getRemovableMediaStorageState() {
|
|
for (String extMediaPath : StorageOptions.paths) {
|
|
File mediaPath = new File(extMediaPath);
|
|
|
|
if (mediaPath.canWrite()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void displayStorageOptions() {
|
|
AlertDialog.Builder setStorage = new AlertDialog.Builder(this);
|
|
setStorage.setTitle(
|
|
"Where would you like to store your data? On your removable SD Card or the built-in memory?");
|
|
StorageOptions.determineStorageOptions(mContext);
|
|
|
|
SharedPreferences settings = getSharedPreferences(kWagicSharedPreferencesKey,
|
|
MODE_PRIVATE);
|
|
String selectedPath = settings.getString(kSaveDataPathPreference, "");
|
|
int selectedIndex = -1;
|
|
|
|
for (int i = 0; i < StorageOptions.labels.length; i++) {
|
|
if (StorageOptions.labels[i].contains(selectedPath)) {
|
|
selectedIndex = i;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
setStorage.setSingleChoiceItems(StorageOptions.labels, selectedIndex,
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int item) {
|
|
savePathPreference(item);
|
|
}
|
|
});
|
|
|
|
setStorage.setPositiveButton("OK",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
initStorage();
|
|
|
|
if (mSurface == null) {
|
|
mSingleton.initializeGame();
|
|
}
|
|
}
|
|
});
|
|
|
|
setStorage.create().show();
|
|
}
|
|
|
|
private void importDeckOptions() {
|
|
AlertDialog.Builder importDeck = new AlertDialog.Builder(this);
|
|
importDeck.setTitle("Choose Deck to Import:");
|
|
|
|
File root = new File(System.getenv("EXTERNAL_STORAGE") + "/Download");
|
|
File[] files = root.listFiles();
|
|
|
|
for (File f : files) {
|
|
if (!myresult.contains(f.toString()) &&
|
|
(f.toString().contains(".txt") ||
|
|
f.toString().contains(".dck") ||
|
|
f.toString().contains(".dec"))) {
|
|
myresult.add(f.toString());
|
|
}
|
|
}
|
|
|
|
//get first item?
|
|
if (!myresult.isEmpty()) {
|
|
myclickedItem = myresult.get(0).toString();
|
|
}
|
|
|
|
importDeck.setSingleChoiceItems(myresult.toArray(
|
|
new String[myresult.size()]), 0,
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int item) {
|
|
myclickedItem = myresult.get(item).toString();
|
|
}
|
|
});
|
|
|
|
importDeck.setPositiveButton("Import Deck",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
processSelectedDeck(myclickedItem);
|
|
|
|
if (mSurface == null) {
|
|
mSingleton.initializeGame();
|
|
}
|
|
}
|
|
});
|
|
|
|
importDeck.create().show();
|
|
}
|
|
|
|
private void processSelectedDeck(String mypath) {
|
|
AlertDialog.Builder infoDialog = new AlertDialog.Builder(this);
|
|
infoDialog.setTitle("Imported Deck:");
|
|
|
|
String activePath = sdcardPath;
|
|
|
|
if (activePath == "") {
|
|
activePath = internalPath;
|
|
}
|
|
|
|
File f = new File(mypath);
|
|
|
|
//Call the deck importer....
|
|
String state = DeckImporter.importDeck(f, mypath, activePath);
|
|
infoDialog.setMessage(state);
|
|
infoDialog.show();
|
|
}
|
|
|
|
private void checkStorageLocationPreference() {
|
|
SharedPreferences settings = getSharedPreferences(kWagicSharedPreferencesKey,
|
|
MODE_PRIVATE);
|
|
final SharedPreferences.Editor prefsEditor = settings.edit();
|
|
boolean hasRemovableMediaMounted = getRemovableMediaStorageState();
|
|
|
|
if (!settings.contains(kStoreDataOnRemovableSdCardPreference)) {
|
|
if (hasRemovableMediaMounted) {
|
|
displayStorageOptions();
|
|
} else {
|
|
prefsEditor.putBoolean(kStoreDataOnRemovableSdCardPreference,
|
|
false);
|
|
prefsEditor.commit();
|
|
initStorage();
|
|
mSingleton.initializeGame();
|
|
}
|
|
} else {
|
|
boolean storeOnRemovableMedia = settings.getBoolean(kStoreDataOnRemovableSdCardPreference,
|
|
false);
|
|
|
|
if (storeOnRemovableMedia && !hasRemovableMediaMounted) {
|
|
AlertDialog setStorage = new AlertDialog.Builder(this).create();
|
|
setStorage.setTitle("Storage Preference");
|
|
setStorage.setMessage(
|
|
"Removable Sd Card not detected. Saving data to internal memory.");
|
|
|
|
prefsEditor.putBoolean(kStoreDataOnRemovableSdCardPreference,
|
|
false);
|
|
prefsEditor.commit();
|
|
|
|
initStorage();
|
|
mSingleton.initializeGame();
|
|
setStorage.show();
|
|
} else {
|
|
initStorage();
|
|
mSingleton.initializeGame();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void initStorage() {
|
|
// check the state of the external storage to ensure we can even write to it.
|
|
// we are going to assume that if an external location exists, and can be written to, use it.
|
|
// Otherwise use internal storage
|
|
try {
|
|
//
|
|
// initialize where all the files are going to be stored.
|
|
//
|
|
File wagicMediaPath = null;
|
|
|
|
// String packageName = mContext.getPackageName(); // possibly use this to differentiate between different mods of Wagic.
|
|
File externalFilesDir = Environment.getExternalStorageDirectory();
|
|
|
|
if (externalFilesDir != null) {
|
|
internalPath = externalFilesDir.getAbsolutePath() + "/Wagic";
|
|
}
|
|
|
|
String state = Environment.getExternalStorageState();
|
|
|
|
if (Environment.MEDIA_MOUNTED.equals(state)) {
|
|
wagicMediaPath = new File(internalPath);
|
|
|
|
if (wagicMediaPath.canWrite()) {
|
|
wagicMediaPath.mkdirs();
|
|
}
|
|
}
|
|
|
|
// initialize the external mount
|
|
SharedPreferences settings = getSharedPreferences(kWagicSharedPreferencesKey,
|
|
MODE_PRIVATE);
|
|
String selectedRemovableCardPath = settings.getString(kSaveDataPathPreference,
|
|
internalPath);
|
|
|
|
if ((selectedRemovableCardPath != null) &&
|
|
!internalPath.equalsIgnoreCase(selectedRemovableCardPath)) {
|
|
wagicMediaPath = new File(selectedRemovableCardPath);
|
|
|
|
if (!wagicMediaPath.exists() || !wagicMediaPath.canWrite()) {
|
|
Log.e(TAG,
|
|
"Error in initializing system folder: " +
|
|
selectedRemovableCardPath);
|
|
} else { // found a removable media location
|
|
sdcardPath = selectedRemovableCardPath + "/Wagic";
|
|
}
|
|
}
|
|
|
|
updateStorageLocations();
|
|
} catch (Exception ioex) {
|
|
Log.e(TAG, "An error occurred in setting up the storage locations.");
|
|
}
|
|
}
|
|
|
|
private void savePathPreference(int selectedOption) {
|
|
SharedPreferences settings = getSharedPreferences(kWagicSharedPreferencesKey,
|
|
MODE_PRIVATE);
|
|
String selectedMediaPath = StorageOptions.paths[selectedOption];
|
|
final SharedPreferences.Editor prefsEditor = settings.edit();
|
|
boolean saveToRemovableMedia = !"/mnt/sdcard".equalsIgnoreCase(selectedMediaPath);
|
|
|
|
prefsEditor.putBoolean(kStoreDataOnRemovableSdCardPreference,
|
|
saveToRemovableMedia);
|
|
prefsEditor.putString(kSaveDataPathPreference, selectedMediaPath);
|
|
prefsEditor.commit();
|
|
}
|
|
|
|
private void startDownload() {
|
|
String url = getResourceUrl();
|
|
|
|
if (!checkStorageState()) {
|
|
Log.e(TAG, "Error in initializing storage space.");
|
|
mSingleton.downloadError(
|
|
"Failed to initialize storage space for game. Please verify that your sdcard or internal memory is mounted properly.");
|
|
}
|
|
|
|
new DownloadFileAsync().execute(url);
|
|
}
|
|
|
|
public void downloadError(String errorMessage) {
|
|
mErrorHappened = true;
|
|
mErrorMessage = errorMessage;
|
|
}
|
|
|
|
private void buildStorageOptionsMenu(Menu menu) {
|
|
StorageOptions.determineStorageOptions(mContext);
|
|
|
|
for (int idx = 0; idx < StorageOptions.count; idx++) {
|
|
menu.add(kStorageDataOptionsMenuId,
|
|
kStorageDataOptionsMenuId + idx, idx, StorageOptions.labels[idx]);
|
|
}
|
|
}
|
|
|
|
private void loadAvailableSets() {
|
|
final Handler mHandler = new Handler();
|
|
progressBarDialogRes = new ProgressDialog(this);
|
|
progressBarDialogRes.setTitle("Loading all available sets...");
|
|
progressBarDialogRes.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
progressBarDialogRes.setProgress(0);
|
|
new Thread(new Runnable() {
|
|
public void run() {
|
|
ArrayList<String> sets = new ArrayList<String>();
|
|
|
|
if (availableSets == null) {
|
|
loadResInProgress = true;
|
|
|
|
File baseFolder = new File(getSystemStorageLocation());
|
|
File[] listOfFiles = baseFolder.listFiles();
|
|
ZipFile zipFile = null;
|
|
|
|
try {
|
|
zipFile = new ZipFile(baseFolder + "/" +
|
|
RES_FILENAME);
|
|
|
|
Enumeration<?extends ZipEntry> e = zipFile.entries();
|
|
|
|
while (e.hasMoreElements()) {
|
|
ZipEntry entry = e.nextElement();
|
|
String entryName = entry.getName();
|
|
|
|
if ((entryName != null) &&
|
|
entryName.contains("sets/")) {
|
|
if (!entryName.equalsIgnoreCase("sets/") &&
|
|
!entryName.contains("primitives") &&
|
|
!entryName.contains(".")) {
|
|
String[] names = entryName.split("/");
|
|
sets.add(names[1]);
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException ioe) {
|
|
System.out.println("Error opening zip file" + ioe);
|
|
} finally {
|
|
try {
|
|
if (zipFile != null) {
|
|
zipFile.close();
|
|
}
|
|
} catch (IOException ioe) {
|
|
System.out.println(
|
|
"Error while closing zip file" + ioe);
|
|
}
|
|
}
|
|
|
|
availableSets = new String[sets.size()];
|
|
checkedSet = new boolean[sets.size()];
|
|
progressBarDialogRes.setMax(sets.size());
|
|
|
|
for (int i = 0; i < availableSets.length; i++) {
|
|
availableSets[i] = sets.get(i) + " - " +
|
|
ImgDownloader.getSetInfo(sets.get(i), true,
|
|
getSystemStorageLocation());
|
|
checkedSet[i] = false;
|
|
progressBarDialogRes.incrementProgressBy((int) (1));
|
|
}
|
|
}
|
|
|
|
finished = true;
|
|
loadResInProgress = false;
|
|
progressBarDialogRes.dismiss();
|
|
mHandler.post(new Runnable() {
|
|
public void run() {
|
|
while (!finished) {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (Exception e) {
|
|
}
|
|
}
|
|
|
|
selectedSets = new ArrayList<String>();
|
|
showWarningFast();
|
|
}
|
|
});
|
|
}
|
|
}).start();
|
|
|
|
new Thread(new Runnable() {
|
|
public void run() {
|
|
fast = ImgDownloader.loadDatabase(getSystemStorageLocation(),
|
|
databaseurl);
|
|
}
|
|
}).start();
|
|
|
|
progressBarDialogRes.show();
|
|
}
|
|
|
|
private void showWarningFast() {
|
|
AlertDialog.Builder infoDialog = new AlertDialog.Builder(this);
|
|
|
|
if (!fast) {
|
|
infoDialog.setTitle("Problem downloading the images database file");
|
|
infoDialog.setMessage(
|
|
"The program will use the slow (not indexed) method, so the images download may take really long time...");
|
|
|
|
infoDialog.setNegativeButton("Retry",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
fast = ImgDownloader.loadDatabase(getSystemStorageLocation(),
|
|
databaseurl);
|
|
showWarningFast();
|
|
}
|
|
});
|
|
} else {
|
|
infoDialog.setTitle("Images Database correctly downloaded");
|
|
infoDialog.setMessage(
|
|
"The program will use the fast (indexed) method, so the images download will not take long time!");
|
|
}
|
|
|
|
infoDialog.setPositiveButton("Continue",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
downloadCardImages();
|
|
}
|
|
});
|
|
|
|
infoDialog.create().show();
|
|
}
|
|
|
|
private void downloadCardImages() {
|
|
AlertDialog.Builder cardDownloader = new AlertDialog.Builder(this);
|
|
cardDownloader.setTitle("Which Sets would you like to download?");
|
|
|
|
cardDownloader.setMultiChoiceItems(availableSets, checkedSet,
|
|
new DialogInterface.OnMultiChoiceClickListener() {
|
|
public void onClick(DialogInterface dialog, int which,
|
|
boolean isChecked) {
|
|
checkedSet[which] = isChecked;
|
|
|
|
if (checkedSet[which]) {
|
|
selectedSets.add(availableSets[which].split(" - ")[0]);
|
|
} else {
|
|
selectedSets.remove(availableSets[which].split(" - ")[0]);
|
|
}
|
|
}
|
|
});
|
|
|
|
cardDownloader.setNeutralButton("Download All",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
selectedSets.clear();
|
|
|
|
for (int i = 0; i < availableSets.length; i++) {
|
|
selectedSets.add(availableSets[i].split(" - ")[0]);
|
|
}
|
|
|
|
chooseResolution();
|
|
}
|
|
});
|
|
|
|
cardDownloader.setPositiveButton("Download Selected",
|
|
new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
}
|
|
});
|
|
|
|
final AlertDialog dialog = cardDownloader.create();
|
|
dialog.show();
|
|
|
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
if (selectedSets.size() > 0) {
|
|
chooseResolution();
|
|
dialog.dismiss();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void chooseResolution() {
|
|
AlertDialog.Builder resChooser = new AlertDialog.Builder(this);
|
|
|
|
resChooser.setTitle("Which resolution would you like to use?");
|
|
|
|
final String[] availableRes = new String[] {
|
|
"High - (672x936)", "High - (672x936) - Borderless",
|
|
"Medium - (488x680)", "Medium - (488x680) - Borderless",
|
|
"Low - (244x340)", "Low - (244x340) - Borderless",
|
|
"Tiny - (180x255)", "Tiny - (180x255) - Borderless"
|
|
};
|
|
|
|
resChooser.setSingleChoiceItems(availableRes, 0,
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int item) {
|
|
targetRes = availableRes[item].split(" - ")[0];
|
|
borderless = (availableRes[item].split(" - ").length > 2);
|
|
}
|
|
});
|
|
|
|
resChooser.setPositiveButton("Start Download",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
skipDownloadedSets();
|
|
}
|
|
});
|
|
|
|
resChooser.setNegativeButton("Change Selection",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
downloadCardImages();
|
|
}
|
|
});
|
|
|
|
resChooser.create().show();
|
|
}
|
|
|
|
private void skipDownloadedSets() {
|
|
AlertDialog.Builder skipChooser = new AlertDialog.Builder(this);
|
|
|
|
skipChooser.setTitle("Do you want to overwrite existing sets?");
|
|
|
|
skipChooser.setPositiveButton("Yes",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
skipDownloaded = false;
|
|
downloadCardImagesStart();
|
|
}
|
|
});
|
|
|
|
skipChooser.setNegativeButton("No",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
skipDownloaded = true;
|
|
downloadCardImagesStart();
|
|
}
|
|
});
|
|
|
|
skipChooser.create().show();
|
|
}
|
|
|
|
private void downloadCardImagesStart() {
|
|
final SDLActivity parent = this;
|
|
final Handler mHandler = new Handler();
|
|
cardDownloader = new ProgressDialog(this);
|
|
cardDownloader.setTitle("Downloading now set: " + set);
|
|
cardDownloader.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
cardDownloader.setProgress(0);
|
|
|
|
if (selectedSets.size() == 1) {
|
|
cardDownloader.setMessage(
|
|
"You choose to download just 1 set: Please don't quit Wagic or turn off Internet connection, you can hide this window and continue to play, a pop-up will notify the completion of download process.");
|
|
} else {
|
|
cardDownloader.setMessage("You choose to download " +
|
|
selectedSets.size() +
|
|
" sets: Please don't quit Wagic or turn off Internet connection, you can hide this window and continue to play, a pop-up will notify the completion of download process.");
|
|
}
|
|
|
|
new Thread(new Runnable() {
|
|
public void run() {
|
|
downloadInProgress = true;
|
|
paused = false;
|
|
|
|
if (selectedSets != null) {
|
|
for (currentIndex = 0;
|
|
(currentIndex < selectedSets.size()) &&
|
|
downloadInProgress; currentIndex++) {
|
|
while (paused) {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException e) {
|
|
}
|
|
|
|
if (!downloadInProgress) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
try {
|
|
set = selectedSets.get(currentIndex);
|
|
mHandler.post(new Runnable() {
|
|
public void run() {
|
|
cardDownloader.setTitle(
|
|
"Downloading set: " + set +
|
|
" (" + (currentIndex + 1) +
|
|
" of " + selectedSets.size() +
|
|
")");
|
|
}
|
|
});
|
|
|
|
String details = ImgDownloader.DownloadCardImages(set,
|
|
availableSets, targetRes,
|
|
getSystemStorageLocation(),
|
|
getUserStorageLocation() + "sets/",
|
|
cardDownloader, parent, skipDownloaded,
|
|
borderless);
|
|
|
|
if (!details.isEmpty()) {
|
|
if (!res.isEmpty()) {
|
|
res = res + "\nSET " + set + ":\n" +
|
|
details;
|
|
} else {
|
|
res = "SET " + set + ":\n" + details;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
res = res + "\n" + e.getMessage();
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
mHandler.post(new Runnable() {
|
|
public void run() {
|
|
if (downloadInProgress) {
|
|
downloadSelectedSetsCompleted(error, res);
|
|
downloadInProgress = false;
|
|
paused = false;
|
|
}
|
|
|
|
cardDownloader.dismiss();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}).start();
|
|
|
|
cardDownloader.setButton(DialogInterface.BUTTON_POSITIVE, "Hide",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
cardDownloader.hide();
|
|
}
|
|
});
|
|
|
|
cardDownloader.setButton(DialogInterface.BUTTON_NEGATIVE, "Stop",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(final DialogInterface dialog, int which) {
|
|
mHandler.post(new Runnable() {
|
|
public void run() {
|
|
downloadCardInterruped(set,
|
|
cardDownloader.getProgress(),
|
|
cardDownloader.getMax());
|
|
downloadInProgress = false;
|
|
paused = false;
|
|
|
|
AlertDialog d = (AlertDialog) dialog;
|
|
d.getButton(AlertDialog.BUTTON_NEUTRAL)
|
|
.setText("Pause");
|
|
cardDownloader.setTitle("Downloading now set: " +
|
|
set + " - Interrupted");
|
|
cardDownloader.dismiss();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
cardDownloader.setButton(DialogInterface.BUTTON_NEUTRAL, "Pause",
|
|
new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
}
|
|
});
|
|
|
|
final AlertDialog dialog = (AlertDialog) cardDownloader;
|
|
cardDownloader.show();
|
|
|
|
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new View.OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
if (!paused) {
|
|
paused = true;
|
|
|
|
AlertDialog d = (AlertDialog) dialog;
|
|
d.getButton(AlertDialog.BUTTON_NEUTRAL).setText("Resume");
|
|
cardDownloader.setTitle("Downloading now set: " + set +
|
|
" - Paused");
|
|
} else {
|
|
paused = false;
|
|
|
|
AlertDialog d = (AlertDialog) dialog;
|
|
d.getButton(AlertDialog.BUTTON_NEUTRAL).setText("Pause");
|
|
cardDownloader.setTitle("Downloading now set: " + set);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void downloadCardInterruped(String set, int cardsDownloaded,
|
|
int total) {
|
|
AlertDialog.Builder infoDialog = new AlertDialog.Builder(this);
|
|
infoDialog.setTitle("Download of " + set + " has been interrupted!");
|
|
infoDialog.setMessage("WARNING: Only " + cardsDownloaded + " of " +
|
|
total + " total cards have been downloaded and zip archive (" +
|
|
set +
|
|
".zip) has not been created. You have to start the download again in order to complete the entire set.");
|
|
|
|
infoDialog.setPositiveButton("OK",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
downloadCardImages();
|
|
}
|
|
});
|
|
|
|
res = "";
|
|
set = "";
|
|
targetRes = "High";
|
|
skipDownloaded = false;
|
|
borderless = false;
|
|
currentIndex = 0;
|
|
selectedSets = new ArrayList<String>();
|
|
|
|
for (int i = 0; i < checkedSet.length; i++) {
|
|
checkedSet[i] = false;
|
|
}
|
|
|
|
error = false;
|
|
|
|
infoDialog.create().show();
|
|
}
|
|
|
|
private void downloadSelectedSetsCompleted(boolean error, String res) {
|
|
AlertDialog.Builder infoDialog = new AlertDialog.Builder(this);
|
|
|
|
if (!error) {
|
|
infoDialog.setTitle(
|
|
"The download process has completed without any error");
|
|
|
|
if (!res.isEmpty()) {
|
|
infoDialog.setMessage(
|
|
"Following cards could not be downloaded:\n" + res);
|
|
}
|
|
} else {
|
|
infoDialog.setTitle("Some errors occurred during the process!");
|
|
infoDialog.setMessage(res);
|
|
}
|
|
|
|
res = "";
|
|
set = "";
|
|
targetRes = "High";
|
|
skipDownloaded = false;
|
|
borderless = false;
|
|
currentIndex = 0;
|
|
selectedSets = new ArrayList<String>();
|
|
|
|
for (int i = 0; i < checkedSet.length; i++) {
|
|
checkedSet[i] = false;
|
|
}
|
|
|
|
error = false;
|
|
|
|
infoDialog.create().show();
|
|
}
|
|
|
|
public void prepareOptionMenu(Menu menu) {
|
|
if (menu == null) {
|
|
PopupMenu p = new PopupMenu(mContext, null);
|
|
menu = p.getMenu();
|
|
}
|
|
SubMenu settingsMenu = menu.addSubMenu(Menu.NONE, 1, 1, "Settings");
|
|
importDecks = menu.add(Menu.NONE, 2, 2, "Import Decks");
|
|
downloader = menu.add(Menu.NONE, 3, 3, "Download Cards");
|
|
about = menu.add(Menu.NONE, 4, 4, "About");
|
|
storage = settingsMenu.add(kStorageDataOptionsMenuId,
|
|
kStorageDataOptionsMenuId, Menu.NONE, "Storage Data Options");
|
|
resource = settingsMenu.add(kdownloadResOptionsMenuId,
|
|
kdownloadResOptionsMenuId, Menu.NONE, "Download Core & Quit");
|
|
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
prepareOptionMenu(menu);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
// Handle item selection
|
|
int itemId = item.getItemId();
|
|
|
|
if (itemId == kStorageDataOptionsMenuId) {
|
|
displayStorageOptions();
|
|
} else if (itemId == kdownloadResOptionsMenuId) {
|
|
File oldRes = new File(getSystemStorageLocation() + RES_FILENAME);
|
|
oldRes.delete();
|
|
startDownload();
|
|
} else if (itemId == 2) {
|
|
importDeckOptions();
|
|
} else if (itemId == 3) {
|
|
if (availableSets == null) {
|
|
loadAvailableSets();
|
|
} else {
|
|
if (loadResInProgress) {
|
|
progressBarDialogRes.show();
|
|
progressBarDialogRes.show();
|
|
} else if (downloadInProgress) {
|
|
cardDownloader.show();
|
|
cardDownloader.show();
|
|
} else {
|
|
downloadCardImages();
|
|
}
|
|
}
|
|
} else if (itemId == 4) {
|
|
// display some info about the app
|
|
AlertDialog.Builder infoDialog = new AlertDialog.Builder(this);
|
|
infoDialog.setTitle("Wagic Info");
|
|
infoDialog.setMessage("Version: " +
|
|
getResources().getString(R.string.app_version) + "\r\n" +
|
|
getResources().getString(R.string.info_text));
|
|
infoDialog.show();
|
|
} else {
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void showSettingsSubMenu() {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle("Settings Menu");
|
|
String[] choices = { "Storage Data Options", "Download Core & Quit" };
|
|
builder.setItems(choices,
|
|
new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
switch (which) {
|
|
case 0:
|
|
onOptionsItemSelected(storage);
|
|
break;
|
|
case 1:
|
|
onOptionsItemSelected(resource);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
AlertDialog dialog = builder.create();
|
|
dialog.show();
|
|
}
|
|
|
|
public void showOptionMenu() {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle("Options Menu");
|
|
String[] choices = { "Settings", "Import Decks", "Download Cards", "About" };
|
|
builder.setItems(choices,
|
|
new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
switch (which) {
|
|
case 0:
|
|
showSettingsSubMenu();
|
|
break;
|
|
case 1:
|
|
onOptionsItemSelected(importDecks);
|
|
break;
|
|
case 2:
|
|
onOptionsItemSelected(downloader);
|
|
break;
|
|
case 3:
|
|
onOptionsItemSelected(about);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
AlertDialog dialog = builder.create();
|
|
dialog.show();
|
|
}
|
|
|
|
@Override
|
|
protected Dialog onCreateDialog(int id) {
|
|
switch (id) {
|
|
case DIALOG_DOWNLOAD_PROGRESS:
|
|
mProgressDialog = new ProgressDialog(this);
|
|
mProgressDialog.setMessage("Downloading resource files (" +
|
|
RES_FILENAME + ")");
|
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
mProgressDialog.setCancelable(false);
|
|
mProgressDialog.show();
|
|
|
|
return mProgressDialog;
|
|
|
|
case DIALOG_DOWNLOAD_ERROR:
|
|
|
|
// prepare alertDialog
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setMessage(mErrorMessage).setCancelable(false).setPositiveButton("Exit",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
System.exit(0);
|
|
}
|
|
});
|
|
|
|
|
|
mErrorDialog = builder.create();
|
|
mErrorDialog.show();
|
|
|
|
return mErrorDialog;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// create main application
|
|
public void mainDisplay() {
|
|
FrameLayout _videoLayout = new FrameLayout(this);
|
|
|
|
// mGLView = new DemoGLSurfaceView(this);
|
|
|
|
// Set up the surface
|
|
mSurface = new SDLSurface(getApplication(), this);
|
|
|
|
// setContentView(mSurface);
|
|
SurfaceHolder holder = mSurface.getHolder();
|
|
holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
|
|
|
|
_videoLayout.addView(mSurface,
|
|
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
|
|
// mGLView.setFocusableInTouchMode(true);
|
|
// mGLView.setFocusable(true);
|
|
// adView.requestFreshAd();
|
|
setContentView(_videoLayout,
|
|
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
|
|
mSurface.requestFocus();
|
|
}
|
|
|
|
// Setup
|
|
private void enterImmersiveMode() {
|
|
final View decorView = getWindow().getDecorView();
|
|
decorView.setSystemUiVisibility(
|
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
super.onWindowFocusChanged(hasFocus);
|
|
if (hasFocus) {
|
|
enterImmersiveMode();
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
|
StrictMode.setThreadPolicy(policy);
|
|
|
|
setContentView(R.layout.main);
|
|
|
|
// Enable immersive mode
|
|
enterImmersiveMode();
|
|
|
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
|
|
// So we can call stuff from static callbacks
|
|
mSingleton = this;
|
|
mContext = this.getApplicationContext();
|
|
RES_FILENAME = getResourceName();
|
|
StorageOptions.determineStorageOptions(mContext);
|
|
checkStorageLocationPreference();
|
|
prepareOptionMenu(null);
|
|
}
|
|
|
|
public void forceResDownload(final File oldRes) {
|
|
AlertDialog.Builder resChooser = new AlertDialog.Builder(this);
|
|
final SDLActivity parent = this;
|
|
|
|
resChooser.setTitle("Do you want to download latest core file?");
|
|
|
|
resChooser.setPositiveButton("Yes",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
FrameLayout _videoLayout = new FrameLayout(parent);
|
|
setContentView(_videoLayout,
|
|
new LayoutParams(LayoutParams.FILL_PARENT,
|
|
LayoutParams.FILL_PARENT));
|
|
oldRes.delete();
|
|
startDownload();
|
|
}
|
|
});
|
|
|
|
resChooser.setNegativeButton("No",
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
mainDisplay();
|
|
}
|
|
});
|
|
|
|
resChooser.create().show();
|
|
}
|
|
|
|
public void initializeGame() {
|
|
String coreFileLocation = getSystemStorageLocation() + RES_FILENAME;
|
|
|
|
File file = new File(coreFileLocation);
|
|
|
|
if (file.exists()) {
|
|
forceResDownload(file);
|
|
} else {
|
|
FrameLayout _videoLayout = new FrameLayout(this);
|
|
setContentView(_videoLayout,
|
|
new LayoutParams(LayoutParams.FILL_PARENT,
|
|
LayoutParams.FILL_PARENT));
|
|
startDownload();
|
|
}
|
|
}
|
|
|
|
// Events
|
|
@Override
|
|
protected void onPause() {
|
|
// Log.d(TAG, "onPause()");
|
|
super.onPause();
|
|
SDLActivity.nativePause();
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
// Log.d(TAG, "onResume()");
|
|
super.onResume();
|
|
SDLActivity.nativeResume();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
// Log.d(TAG, "onDestroy()");
|
|
super.onDestroy();
|
|
mSurface.onDestroy();
|
|
}
|
|
|
|
// Handler for Messages coming from JGE
|
|
// Suggested syntax for JGE messages is a string separated by the ":" symbol
|
|
protected void processJGEMsg(String command) {
|
|
if (null == command) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Send a message from the SDLMain thread
|
|
void sendCommand(int command, Object data) {
|
|
Message msg = commandHandler.obtainMessage();
|
|
msg.arg1 = command;
|
|
msg.obj = data;
|
|
commandHandler.sendMessage(msg);
|
|
}
|
|
|
|
// C functions we call
|
|
public static native String getResourceUrl();
|
|
|
|
public static native String getResourceName();
|
|
|
|
public static native void nativeInit();
|
|
|
|
public static native void nativeQuit();
|
|
|
|
public static native void nativePause();
|
|
|
|
public static native void nativeResume();
|
|
|
|
public static native void onNativeResize(int x, int y, int format);
|
|
|
|
public static native void onNativeKeyDown(int keycode);
|
|
|
|
public static native void onNativeKeyUp(int keycode);
|
|
|
|
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 onNativeAccel(float x, float y, float z);
|
|
|
|
public static native void nativeRunAudioThread();
|
|
|
|
// Java functions called from C
|
|
// Receive a message from the SDLMain thread
|
|
public static String getSystemFolderPath() {
|
|
return mSingleton.getSystemStorageLocation();
|
|
}
|
|
|
|
public static String getUserFolderPath() {
|
|
return mSingleton.getUserStorageLocation();
|
|
}
|
|
|
|
public static void jgeSendCommand(String command) {
|
|
mSingleton.sendCommand(COMMAND_JGE_MSG, command);
|
|
}
|
|
|
|
public static boolean createGLContext(int majorVersion, int minorVersion) {
|
|
return mSurface.initEGL(majorVersion, minorVersion);
|
|
}
|
|
|
|
public static void flipBuffers() {
|
|
mSurface.flipEGL();
|
|
}
|
|
|
|
public static void setActivityTitle(String title) {
|
|
// Called from SDLMain() thread and can't directly affect the view
|
|
mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
|
|
}
|
|
|
|
public static Object audioInit(int sampleRate, boolean is16Bit,
|
|
boolean isStereo, int desiredFrames) {
|
|
int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO
|
|
: AudioFormat.CHANNEL_CONFIGURATION_MONO;
|
|
int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT
|
|
: AudioFormat.ENCODING_PCM_8BIT;
|
|
int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
|
|
|
|
// Log.d(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
|
|
|
|
// Let the user pick a larger buffer if they really want -- but ye
|
|
// gods they probably shouldn't, the minimums are horrifyingly high
|
|
// latency already
|
|
desiredFrames = Math.max(desiredFrames,
|
|
((AudioTrack.getMinBufferSize(sampleRate, channelConfig,
|
|
audioFormat) + frameSize) - 1) / frameSize);
|
|
|
|
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
|
|
channelConfig, audioFormat, desiredFrames * frameSize,
|
|
AudioTrack.MODE_STREAM);
|
|
|
|
audioStartThread();
|
|
|
|
// Log.d(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) +
|
|
// "kHz, " + desiredFrames + " frames buffer");
|
|
if (is16Bit) {
|
|
buf = new short[desiredFrames * (isStereo ? 2 : 1)];
|
|
} else {
|
|
buf = new byte[desiredFrames * (isStereo ? 2 : 1)];
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
public static void audioStartThread() {
|
|
mAudioThread = new Thread(new Runnable() {
|
|
public void run() {
|
|
mAudioTrack.play();
|
|
nativeRunAudioThread();
|
|
}
|
|
});
|
|
|
|
// I'd take REALTIME if I could get it!
|
|
mAudioThread.setPriority(Thread.MAX_PRIORITY);
|
|
mAudioThread.start();
|
|
}
|
|
|
|
public static void audioWriteShortBuffer(short[] buffer) {
|
|
for (int i = 0; i < buffer.length;) {
|
|
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
|
|
|
if (result > 0) {
|
|
i += result;
|
|
} else if (result == 0) {
|
|
try {
|
|
Thread.sleep(1);
|
|
} catch (InterruptedException e) {
|
|
// Nom nom
|
|
}
|
|
} else {
|
|
Log.w(TAG, "SDL audio: error return from write(short)");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void audioWriteByteBuffer(byte[] buffer) {
|
|
for (int i = 0; i < buffer.length;) {
|
|
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
|
|
|
if (result > 0) {
|
|
i += result;
|
|
} else if (result == 0) {
|
|
try {
|
|
Thread.sleep(1);
|
|
} catch (InterruptedException e) {
|
|
// Nom nom
|
|
}
|
|
} else {
|
|
Log.w(TAG, "SDL audio: error return from write(short)");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void audioQuit() {
|
|
if (mAudioThread != null) {
|
|
try {
|
|
mAudioThread.join();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Problem stopping audio thread: " + e);
|
|
}
|
|
|
|
mAudioThread = null;
|
|
|
|
// Log.d(TAG, "Finished waiting for audio thread");
|
|
}
|
|
|
|
if (mAudioTrack != null) {
|
|
mAudioTrack.stop();
|
|
mAudioTrack = null;
|
|
}
|
|
}
|
|
|
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
if ((keyCode == KeyEvent.KEYCODE_MENU) &&
|
|
(KeyEvent.ACTION_DOWN == event.getAction())) {
|
|
super.onKeyDown(keyCode, event);
|
|
|
|
return true;
|
|
} else if ((keyCode == KeyEvent.KEYCODE_MENU) &&
|
|
(KeyEvent.ACTION_UP == event.getAction())) {
|
|
super.onKeyUp(keyCode, event);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private String getApplicationCode() {
|
|
int v = 0;
|
|
|
|
try {
|
|
v = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
|
|
} catch (NameNotFoundException e) {
|
|
// Huh? Really?
|
|
v = 184; // shouldn't really happen but we need to default to something
|
|
}
|
|
|
|
return "0" + v;
|
|
}
|
|
|
|
// Empty onConfigurationChanged to stop the Activity from destroying/recreating on screen off
|
|
@Override
|
|
public void onConfigurationChanged(Configuration newConfig) {
|
|
super.onConfigurationChanged(newConfig);
|
|
}
|
|
|
|
class DownloadFileAsync extends AsyncTask<String, Integer, Long> {
|
|
private final String TAG = DownloadFileAsync.class.getCanonicalName();
|
|
|
|
@Override
|
|
protected void onPreExecute() {
|
|
super.onPreExecute();
|
|
showDialog(DIALOG_DOWNLOAD_PROGRESS);
|
|
}
|
|
|
|
@Override
|
|
protected Long doInBackground(String... aurl) {
|
|
int count;
|
|
long totalBytes = 0;
|
|
OutputStream output = null;
|
|
InputStream input = null;
|
|
|
|
try {
|
|
//
|
|
// Prepare the sdcard folders in order to download the resource file
|
|
//
|
|
String storageLocation = mSingleton.getSystemStorageLocation();
|
|
|
|
File resDirectory = new File(storageLocation);
|
|
File userDirectory = new File(mSingleton.getUserStorageLocation());
|
|
|
|
if ((!resDirectory.exists() && !resDirectory.mkdirs()) ||
|
|
(!userDirectory.exists() && !userDirectory.mkdirs())) {
|
|
throw new Exception(
|
|
"Failed to initialize system and user directories.");
|
|
}
|
|
|
|
URL url = new URL(aurl[0]);
|
|
String filename = url.getPath()
|
|
.substring(url.getPath().lastIndexOf('/') +
|
|
1);
|
|
URLConnection conexion = url.openConnection();
|
|
conexion.connect();
|
|
|
|
int lengthOfFile = conexion.getContentLength();
|
|
// Log.d(TAG, " Length of file: " + lengthOfFile);
|
|
input = new BufferedInputStream(url.openStream());
|
|
|
|
// create a File object for the output file
|
|
File outputFile = new File(resDirectory, filename);
|
|
|
|
output = new FileOutputStream(outputFile);
|
|
|
|
byte[] data = new byte[1024];
|
|
|
|
while ((count = input.read(data)) != -1) {
|
|
totalBytes += count;
|
|
publishProgress((int) ((totalBytes * 100) / lengthOfFile));
|
|
output.write(data, 0, count);
|
|
}
|
|
|
|
output.flush();
|
|
output.close();
|
|
input.close();
|
|
} catch (Exception e) {
|
|
String errorMessage = "An error happened while downloading the resources. It could be that our server is temporarily down, that your device is not connected to a network, or that we cannot write to " +
|
|
mSingleton.getSystemStorageLocation() +
|
|
". Please check your phone settings and try again. For more help please go to http://wololo.net/forum/";
|
|
mSingleton.downloadError(errorMessage);
|
|
Log.e(TAG, errorMessage);
|
|
Log.e(TAG, e.getMessage());
|
|
}
|
|
|
|
return Long.valueOf(totalBytes);
|
|
}
|
|
|
|
protected void onProgressUpdate(Integer... progress) {
|
|
if (progress[0] != mProgressDialog.getProgress()) {
|
|
// Log.d(TAG, "current progress : " + progress[0]);
|
|
mProgressDialog.setProgress(progress[0]);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Long unused) {
|
|
if (mErrorHappened) {
|
|
dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
|
|
showDialog(DIALOG_DOWNLOAD_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
// rename the temporary file into the final filename
|
|
String storageLocation = getSystemStorageLocation();
|
|
|
|
File preFile = new File(storageLocation + RES_FILENAME + ".tmp");
|
|
File postFile = new File(storageLocation + RES_FILENAME);
|
|
|
|
if (preFile.exists()) {
|
|
preFile.renameTo(postFile);
|
|
}
|
|
|
|
dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
|
|
// Start game;
|
|
mSingleton.mainDisplay();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* SDLSurface. This is what we draw on, so we need to know when it's created in order to do anything useful.
|
|
* <p>
|
|
* Because of this, that's where we set up the SDL thread
|
|
*/
|
|
class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
|
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
|
private static final String TAG = SDLSurface.class.getCanonicalName();
|
|
|
|
// Sensors
|
|
private static SensorManager mSensorManager;
|
|
private static VelocityTracker mVelocityTracker;
|
|
private static SDLActivity parent;
|
|
|
|
// This is what SDL runs in. It invokes SDL_main(), eventually
|
|
private Thread mSDLThread;
|
|
|
|
// EGL private objects
|
|
private EGLContext mEGLContext;
|
|
private EGLSurface mEGLSurface;
|
|
private EGLDisplay mEGLDisplay;
|
|
private EGLConfig mEGLConfig;
|
|
final private Object mSemSurface;
|
|
private Boolean mSurfaceValid;
|
|
|
|
// Variables for touch events
|
|
public float y1;
|
|
|
|
// Variables for touch events
|
|
public float y2;
|
|
public final int DELTA_Y = 800;
|
|
|
|
// Startup
|
|
public SDLSurface(Context context, SDLActivity app) {
|
|
super(context);
|
|
mSemSurface = new Object();
|
|
mSurfaceValid = false;
|
|
getHolder().addCallback(this);
|
|
|
|
setFocusable(true);
|
|
setFocusableInTouchMode(true);
|
|
requestFocus();
|
|
setOnKeyListener(this);
|
|
setOnTouchListener(this);
|
|
|
|
mSensorManager = (SensorManager) context.getSystemService("sensor");
|
|
parent = app;
|
|
}
|
|
|
|
void startSDLThread() {
|
|
if (mSDLThread == null) {
|
|
mSDLThread = new Thread(new SDLMain(), "SDLThread");
|
|
mSDLThread.start();
|
|
}
|
|
}
|
|
|
|
// Called when we have a valid drawing surface
|
|
public void surfaceCreated(SurfaceHolder holder) {
|
|
//Log.d(TAG, "surfaceCreated()");
|
|
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
|
}
|
|
|
|
public void onDestroy() {
|
|
// Send a quit message to the application
|
|
// should that be in SDLActivity "onDestroy" instead ?
|
|
SDLActivity.nativeQuit();
|
|
|
|
// Now wait for the SDL thread to quit
|
|
if (mSDLThread != null) {
|
|
try {
|
|
mSDLThread.join();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Problem stopping thread: " + e);
|
|
}
|
|
|
|
mSDLThread = null;
|
|
|
|
// Log.d(TAG, "Finished waiting for SDL thread");
|
|
}
|
|
}
|
|
|
|
// Called when we lose the surface
|
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
Log.d(TAG, "surfaceDestroyed()");
|
|
|
|
synchronized (mSemSurface) {
|
|
mSurfaceValid = false;
|
|
mSemSurface.notifyAll();
|
|
}
|
|
|
|
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
|
}
|
|
|
|
// Called when the surface is resized
|
|
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
|
int height) {
|
|
Log.d(TAG, "surfaceChanged()");
|
|
|
|
int sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565 by default
|
|
|
|
switch (format) {
|
|
case PixelFormat.A_8:
|
|
Log.d("TAG", "pixel format A_8");
|
|
|
|
break;
|
|
|
|
case PixelFormat.LA_88:
|
|
Log.d("TAG", "pixel format LA_88");
|
|
|
|
break;
|
|
|
|
case PixelFormat.L_8:
|
|
Log.d("TAG", "pixel format L_8");
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGBA_4444:
|
|
Log.d("TAG", "pixel format RGBA_4444");
|
|
sdlFormat = 0x85421002; // SDL_PIXELFORMAT_RGBA4444
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGBA_5551:
|
|
Log.d(TAG, "pixel format RGBA_5551");
|
|
sdlFormat = 0x85441002; // SDL_PIXELFORMAT_RGBA5551
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGBA_8888:
|
|
Log.d(TAG, "pixel format RGBA_8888");
|
|
sdlFormat = 0x86462004; // SDL_PIXELFORMAT_RGBA8888
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGBX_8888:
|
|
Log.d(TAG, "pixel format RGBX_8888");
|
|
sdlFormat = 0x86262004; // SDL_PIXELFORMAT_RGBX8888
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGB_332:
|
|
Log.d(TAG, "pixel format RGB_332");
|
|
sdlFormat = 0x84110801; // SDL_PIXELFORMAT_RGB332
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGB_565:
|
|
Log.d(TAG, "pixel format RGB_565");
|
|
sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565
|
|
|
|
break;
|
|
|
|
case PixelFormat.RGB_888:
|
|
Log.d(TAG, "pixel format RGB_888");
|
|
// Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
|
|
sdlFormat = 0x86161804; // SDL_PIXELFORMAT_RGB888
|
|
|
|
break;
|
|
|
|
default:
|
|
Log.d(TAG, "pixel format unknown " + format);
|
|
|
|
break;
|
|
}
|
|
|
|
SDLActivity.onNativeResize(width, height, sdlFormat);
|
|
|
|
// Now start up the C app thread
|
|
startSDLThread();
|
|
}
|
|
|
|
// unused
|
|
public void onDraw(Canvas canvas) {
|
|
}
|
|
|
|
// EGL functions
|
|
public boolean initEGL(int majorVersion, int minorVersion) {
|
|
Log.d(TAG, "Starting up OpenGL ES " + majorVersion + "." +
|
|
minorVersion);
|
|
|
|
try {
|
|
EGL10 egl = (EGL10) EGLContext.getEGL();
|
|
|
|
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
|
|
|
int[] version = new int[2];
|
|
egl.eglInitialize(dpy, version);
|
|
|
|
int EGL_OPENGL_ES_BIT = 1;
|
|
int EGL_OPENGL_ES2_BIT = 4;
|
|
int renderableType = 0;
|
|
|
|
if (majorVersion == 2) {
|
|
renderableType = EGL_OPENGL_ES2_BIT;
|
|
} else if (majorVersion == 1) {
|
|
renderableType = EGL_OPENGL_ES_BIT;
|
|
}
|
|
|
|
int[] configSpec = {
|
|
// EGL10.EGL_DEPTH_SIZE, 16,
|
|
EGL10.EGL_RENDERABLE_TYPE, renderableType, EGL10.EGL_NONE
|
|
};
|
|
EGLConfig[] configs = new EGLConfig[1];
|
|
int[] num_config = new int[1];
|
|
|
|
if (!egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config) ||
|
|
(num_config[0] == 0)) {
|
|
Log.e(TAG, "No EGL config available");
|
|
|
|
return false;
|
|
}
|
|
|
|
mEGLConfig = configs[0];
|
|
|
|
EGLContext ctx = egl.eglCreateContext(dpy, mEGLConfig,
|
|
EGL10.EGL_NO_CONTEXT, null);
|
|
|
|
if (ctx == EGL10.EGL_NO_CONTEXT) {
|
|
Log.e(TAG, "Couldn't create context");
|
|
|
|
return false;
|
|
}
|
|
|
|
mEGLContext = ctx;
|
|
mEGLDisplay = dpy;
|
|
|
|
if (!createSurface(this.getHolder())) {
|
|
return false;
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, e + "");
|
|
|
|
for (StackTraceElement s : e.getStackTrace()) {
|
|
Log.e(TAG, s.toString());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public Boolean createSurface(SurfaceHolder holder) {
|
|
/*
|
|
* The window size has changed, so we need to create a new surface.
|
|
*/
|
|
EGL10 egl = (EGL10) EGLContext.getEGL();
|
|
|
|
if (mEGLSurface != null) {
|
|
/*
|
|
* Unbind and destroy the old EGL surface, if there is one.
|
|
*/
|
|
egl.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
|
egl.eglDestroySurface(mEGLDisplay, mEGLSurface);
|
|
}
|
|
|
|
/*
|
|
* Create an EGL surface we can render into.
|
|
*/
|
|
mEGLSurface = egl.eglCreateWindowSurface(mEGLDisplay, mEGLConfig,
|
|
holder, null);
|
|
|
|
if (mEGLSurface == EGL10.EGL_NO_SURFACE) {
|
|
Log.e(TAG, "Couldn't create surface");
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Before we can issue GL commands, we need to make sure the context is current and bound to a surface.
|
|
*/
|
|
if (!egl.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface,
|
|
mEGLContext)) {
|
|
Log.e(TAG, "Couldn't make context current");
|
|
|
|
return false;
|
|
}
|
|
|
|
mSurfaceValid = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
// EGL buffer flip
|
|
public void flipEGL() {
|
|
if (!mSurfaceValid) {
|
|
createSurface(this.getHolder());
|
|
}
|
|
|
|
try {
|
|
EGL10 egl = (EGL10) EGLContext.getEGL();
|
|
|
|
egl.eglWaitNative(EGL10.EGL_CORE_NATIVE_ENGINE, null);
|
|
|
|
// drawing here
|
|
egl.eglWaitGL();
|
|
|
|
egl.eglSwapBuffers(mEGLDisplay, mEGLSurface);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "flipEGL(): " + e);
|
|
|
|
for (StackTraceElement s : e.getStackTrace()) {
|
|
Log.e(TAG, s.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Key events
|
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
|
return false;
|
|
}
|
|
|
|
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) ||
|
|
(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
|
|
return false;
|
|
}
|
|
|
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
// Log.d(TAG, "key down: " + keyCode);
|
|
SDLActivity.onNativeKeyDown(keyCode);
|
|
|
|
return true;
|
|
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
|
// Log.d(TAG, "key up: " + keyCode);
|
|
SDLActivity.onNativeKeyUp(keyCode);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Touch events
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
switch (event.getAction()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
case MotionEvent.ACTION_POINTER_DOWN:
|
|
y1 = event.getY();
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_POINTER_UP:
|
|
y2 = event.getY();
|
|
float deltaY = y2 - y1;
|
|
if (deltaY > DELTA_Y) {
|
|
parent.showOptionMenu(); // Emulate Android "optionmenu" button pressure (for devices without sidebar, e.g. like Android 10).
|
|
return true;
|
|
} else if (deltaY < -DELTA_Y){
|
|
SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_BACK); // Emulate Android "back" button pressure (for devices without sidebar, e.g. like Android 10).
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
for (int index = 0; index < event.getPointerCount(); ++index) {
|
|
int action = event.getActionMasked();
|
|
float x = event.getX(index);
|
|
float y = event.getY(index);
|
|
float p = event.getPressure(index);
|
|
|
|
// TODO: Anything else we need to pass?
|
|
SDLActivity.onNativeTouch(index, action, x, y, p);
|
|
}
|
|
|
|
// account for 'flick' type gestures by monitoring velocity
|
|
if (event.getActionIndex() == 0) {
|
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
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);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Sensor events
|
|
public void enableSensor(int sensortype, boolean enabled) {
|
|
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
|
if (enabled) {
|
|
mSensorManager.registerListener(this,
|
|
mSensorManager.getDefaultSensor(sensortype),
|
|
SensorManager.SENSOR_DELAY_GAME, null);
|
|
} else {
|
|
mSensorManager.unregisterListener(this,
|
|
mSensorManager.getDefaultSensor(sensortype));
|
|
}
|
|
}
|
|
|
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
|
// TODO
|
|
}
|
|
|
|
public void onSensorChanged(SensorEvent event) {
|
|
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
|
SDLActivity.onNativeAccel(event.values[0], event.values[1],
|
|
event.values[2]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simple nativeInit() runnable
|
|
*/
|
|
class SDLMain implements Runnable {
|
|
public void run() {
|
|
// Runs SDL_main()
|
|
SDLActivity.nativeInit();
|
|
|
|
SDLActivity.nativeQuit();
|
|
|
|
Log.d(TAG, "SDL thread terminated");
|
|
|
|
// On exit, tear everything down for a fresh restart next time.
|
|
System.exit(0);
|
|
}
|
|
}
|
|
}
|