blob: 0ad2ba168610894633de5089fce1385c6d03cbe2 [file] [log] [blame]
package com.jme3.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.view.*;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.android.AndroidAudioRenderer;
import com.jme3.input.android.AndroidInput;
import com.jme3.input.controls.TouchListener;
import com.jme3.input.event.TouchEvent;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeSystem;
import com.jme3.system.android.AndroidConfigChooser.ConfigType;
import com.jme3.system.android.JmeAndroidSystem;
import com.jme3.system.android.OGLESContext;
import com.jme3.util.JmeFormatter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <code>AndroidHarness</code> wraps a jme application object and runs it on
* Android
*
* @author Kirill
* @author larynx
*/
public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener {
protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
/**
* The application class to start
*/
protected String appClass = "jme3test.android.Test";
/**
* The jme3 application object
*/
protected Application app = null;
/**
* ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is
* RGBA8888 or better if supported by the hardware
*/
protected ConfigType eglConfigType = ConfigType.FASTEST;
/**
* If true all valid and not valid egl configs are logged
*/
protected boolean eglConfigVerboseLogging = false;
/**
* If true MouseEvents are generated from TouchEvents
*/
protected boolean mouseEventsEnabled = true;
/**
* Flip X axis
*/
protected boolean mouseEventsInvertX = true;
/**
* Flip Y axis
*/
protected boolean mouseEventsInvertY = true;
/**
* if true finish this activity when the jme app is stopped
*/
protected boolean finishOnAppStop = true;
/**
* Title of the exit dialog, default is "Do you want to exit?"
*/
protected String exitDialogTitle = "Do you want to exit?";
/**
* Message of the exit dialog, default is "Use your home key to bring this
* app into the background or exit to terminate it."
*/
protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
/**
* Set the screen window mode. If screenFullSize is true, then the
* notification bar and title bar are removed and the screen covers the
* entire display.   If screenFullSize is false, then the notification bar
* remains visible if screenShowTitle is true while screenFullScreen is
* false, then the title bar is also displayed under the notification bar.
*/
protected boolean screenFullScreen = true;
/**
* if screenShowTitle is true while screenFullScreen is false, then the
* title bar is also displayed under the notification bar
*/
protected boolean screenShowTitle = true;
/**
* Splash Screen picture Resource ID. If a Splash Screen is desired, set
* splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
* splashPicID = 0, then no splash screen will be displayed.
*/
protected int splashPicID = 0;
/**
* Set the screen orientation, default is SENSOR
* ActivityInfo.SCREEN_ORIENTATION_* constants package
* android.content.pm.ActivityInfo
*
* SCREEN_ORIENTATION_UNSPECIFIED SCREEN_ORIENTATION_LANDSCAPE
* SCREEN_ORIENTATION_PORTRAIT SCREEN_ORIENTATION_USER
* SCREEN_ORIENTATION_BEHIND SCREEN_ORIENTATION_SENSOR (default)
* SCREEN_ORIENTATION_NOSENSOR
*/
protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
protected OGLESContext ctx;
protected GLSurfaceView view = null;
protected boolean isGLThreadPaused = true;
private ImageView splashImageView = null;
private FrameLayout frameLayout = null;
final private String ESCAPE_EVENT = "TouchEscape";
static {
try {
System.loadLibrary("bulletjme");
} catch (UnsatisfiedLinkError e) {
}
JmeSystem.setSystemDelegate(new JmeAndroidSystem());
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Logger log = logger;
boolean bIsLogFormatSet = false;
do {
if (log.getHandlers().length == 0) {
log = log.getParent();
if (log != null) {
for (Handler h : log.getHandlers()) {
//h.setFormatter(new SimpleFormatter());
h.setFormatter(new JmeFormatter());
bIsLogFormatSet = true;
}
}
}
} while (log != null && !bIsLogFormatSet);
JmeAndroidSystem.setResources(getResources());
JmeAndroidSystem.setActivity(this);
if (screenFullScreen) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
if (!screenShowTitle) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
setRequestedOrientation(screenOrientation);
// Create Settings
AppSettings settings = new AppSettings(true);
// Create the input class
AndroidInput input = new AndroidInput(this);
input.setMouseEventsInvertX(mouseEventsInvertX);
input.setMouseEventsInvertY(mouseEventsInvertY);
input.setMouseEventsEnabled(mouseEventsEnabled);
// Create application instance
try {
if (app == null) {
@SuppressWarnings("unchecked")
Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
app = clazz.newInstance();
}
app.setSettings(settings);
app.start();
ctx = (OGLESContext) app.getContext();
view = ctx.createView(input, eglConfigType, eglConfigVerboseLogging);
// Set the screen reolution
WindowManager wind = this.getWindowManager();
Display disp = wind.getDefaultDisplay();
ctx.getSettings().setResolution(disp.getWidth(), disp.getHeight());
AppSettings s = ctx.getSettings();
logger.log(Level.INFO, "Settings: Width {0} Height {1}", new Object[]{s.getWidth(), s.getHeight()});
layoutDisplay();
} catch (Exception ex) {
handleError("Class " + appClass + " init failed", ex);
setContentView(new TextView(this));
}
}
@Override
protected void onRestart() {
super.onRestart();
if (app != null) {
app.restart();
}
logger.info("onRestart");
}
@Override
protected void onStart() {
super.onStart();
logger.info("onStart");
}
@Override
protected void onResume() {
super.onResume();
if (view != null) {
view.onResume();
}
//resume the audio
AudioRenderer result = app.getAudioRenderer();
if (result != null) {
if (result instanceof AndroidAudioRenderer) {
AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
renderer.resumeAll();
}
}
isGLThreadPaused = false;
logger.info("onResume");
}
@Override
protected void onPause() {
super.onPause();
if (view != null) {
view.onPause();
}
//pause the audio
AudioRenderer result = app.getAudioRenderer();
if (result != null) {
logger.info("pause: " + result.getClass().getSimpleName());
if (result instanceof AndroidAudioRenderer) {
AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
renderer.pauseAll();
}
}
isGLThreadPaused = true;
logger.info("onPause");
}
@Override
protected void onStop() {
super.onStop();
logger.info("onStop");
}
@Override
protected void onDestroy() {
if (app != null) {
app.stop(!isGLThreadPaused);
}
logger.info("onDestroy");
super.onDestroy();
}
public Application getJmeApplication() {
return app;
}
/**
* Called when an error has occurred. By default, will show an error message
* to the user and print the exception/error to the log.
*/
public void handleError(final String errorMsg, final Throwable t) {
String stackTrace = "";
String title = "Error";
if (t != null) {
// Convert exception to string
StringWriter sw = new StringWriter(100);
t.printStackTrace(new PrintWriter(sw));
stackTrace = sw.toString();
title = t.toString();
}
final String finalTitle = title;
final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
+ "\n" + stackTrace;
logger.log(Level.SEVERE, finalMsg);
runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
.setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create();
dialog.show();
}
});
}
/**
* Called by the android alert dialog, terminate the activity and OpenGL
* rendering
*
* @param dialog
* @param whichButton
*/
public void onClick(DialogInterface dialog, int whichButton) {
if (whichButton != -2) {
if (app != null) {
app.stop(true);
}
this.finish();
}
}
/**
* Gets called by the InputManager on all touch/drag/scale events
*/
@Override
public void onTouch(String name, TouchEvent evt, float tpf) {
if (name.equals(ESCAPE_EVENT)) {
switch (evt.getType()) {
case KEY_UP:
runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
.setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create();
dialog.show();
}
});
break;
default:
break;
}
}
}
public void layoutDisplay() {
logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);
if (splashPicID != 0) {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT,
Gravity.CENTER);
frameLayout = new FrameLayout(this);
splashImageView = new ImageView(this);
Drawable drawable = this.getResources().getDrawable(splashPicID);
if (drawable instanceof NinePatchDrawable) {
splashImageView.setBackgroundDrawable(drawable);
} else {
splashImageView.setImageResource(splashPicID);
}
frameLayout.addView(view);
frameLayout.addView(splashImageView, lp);
setContentView(frameLayout);
logger.log(Level.INFO, "Splash Screen Created");
} else {
logger.log(Level.INFO, "Splash Screen Skipped.");
setContentView(view);
}
}
public void removeSplashScreen() {
logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);
if (splashPicID != 0) {
if (frameLayout != null) {
if (splashImageView != null) {
this.runOnUiThread(new Runnable() {
@Override
public void run() {
splashImageView.setVisibility(View.INVISIBLE);
frameLayout.removeView(splashImageView);
}
});
} else {
logger.log(Level.INFO, "splashImageView is null");
}
} else {
logger.log(Level.INFO, "frameLayout is null");
}
}
}
public boolean isFinishOnAppStop() {
return finishOnAppStop;
}
}