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; /** * AndroidHarness 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; /** * 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 = logger.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 clazz = (Class) 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"); } } } }