Home | History | Annotate | Download | only in app
      1 package com.jme3.app;
      2 
      3 import android.app.Activity;
      4 import android.app.AlertDialog;
      5 import android.content.DialogInterface;
      6 import android.content.pm.ActivityInfo;
      7 import android.graphics.drawable.Drawable;
      8 import android.graphics.drawable.NinePatchDrawable;
      9 import android.opengl.GLSurfaceView;
     10 import android.os.Bundle;
     11 import android.view.ViewGroup.LayoutParams;
     12 import android.view.*;
     13 import android.widget.FrameLayout;
     14 import android.widget.ImageView;
     15 import android.widget.TextView;
     16 import com.jme3.audio.AudioRenderer;
     17 import com.jme3.audio.android.AndroidAudioRenderer;
     18 import com.jme3.input.android.AndroidInput;
     19 import com.jme3.input.controls.TouchListener;
     20 import com.jme3.input.event.TouchEvent;
     21 import com.jme3.system.AppSettings;
     22 import com.jme3.system.JmeSystem;
     23 import com.jme3.system.android.AndroidConfigChooser.ConfigType;
     24 import com.jme3.system.android.JmeAndroidSystem;
     25 import com.jme3.system.android.OGLESContext;
     26 import com.jme3.util.JmeFormatter;
     27 import java.io.PrintWriter;
     28 import java.io.StringWriter;
     29 import java.util.logging.Handler;
     30 import java.util.logging.Level;
     31 import java.util.logging.Logger;
     32 
     33 /**
     34  * <code>AndroidHarness</code> wraps a jme application object and runs it on
     35  * Android
     36  *
     37  * @author Kirill
     38  * @author larynx
     39  */
     40 public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener {
     41 
     42     protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
     43     /**
     44      * The application class to start
     45      */
     46     protected String appClass = "jme3test.android.Test";
     47     /**
     48      * The jme3 application object
     49      */
     50     protected Application app = null;
     51     /**
     52      * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is
     53      * RGBA8888 or better if supported by the hardware
     54      */
     55     protected ConfigType eglConfigType = ConfigType.FASTEST;
     56     /**
     57      * If true all valid and not valid egl configs are logged
     58      */
     59     protected boolean eglConfigVerboseLogging = false;
     60     /**
     61      * If true MouseEvents are generated from TouchEvents
     62      */
     63     protected boolean mouseEventsEnabled = true;
     64     /**
     65      * Flip X axis
     66      */
     67     protected boolean mouseEventsInvertX = true;
     68     /**
     69      * Flip Y axis
     70      */
     71     protected boolean mouseEventsInvertY = true;
     72     /**
     73      * if true finish this activity when the jme app is stopped
     74      */
     75     protected boolean finishOnAppStop = true;
     76     /**
     77      * Title of the exit dialog, default is "Do you want to exit?"
     78      */
     79     protected String exitDialogTitle = "Do you want to exit?";
     80     /**
     81      * Message of the exit dialog, default is "Use your home key to bring this
     82      * app into the background or exit to terminate it."
     83      */
     84     protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
     85     /**
     86      * Set the screen window mode. If screenFullSize is true, then the
     87      * notification bar and title bar are removed and the screen covers the
     88      * entire display.  If screenFullSize is false, then the notification bar
     89      * remains visible if screenShowTitle is true while screenFullScreen is
     90      * false, then the title bar is also displayed under the notification bar.
     91      */
     92     protected boolean screenFullScreen = true;
     93     /**
     94      * if screenShowTitle is true while screenFullScreen is false, then the
     95      * title bar is also displayed under the notification bar
     96      */
     97     protected boolean screenShowTitle = true;
     98     /**
     99      * Splash Screen picture Resource ID. If a Splash Screen is desired, set
    100      * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
    101      * splashPicID = 0, then no splash screen will be displayed.
    102      */
    103     protected int splashPicID = 0;
    104     /**
    105      * Set the screen orientation, default is SENSOR
    106      * ActivityInfo.SCREEN_ORIENTATION_* constants package
    107      * android.content.pm.ActivityInfo
    108      *
    109      * SCREEN_ORIENTATION_UNSPECIFIED SCREEN_ORIENTATION_LANDSCAPE
    110      * SCREEN_ORIENTATION_PORTRAIT SCREEN_ORIENTATION_USER
    111      * SCREEN_ORIENTATION_BEHIND SCREEN_ORIENTATION_SENSOR (default)
    112      * SCREEN_ORIENTATION_NOSENSOR
    113      */
    114     protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
    115     protected OGLESContext ctx;
    116     protected GLSurfaceView view = null;
    117     protected boolean isGLThreadPaused = true;
    118     private ImageView splashImageView = null;
    119     private FrameLayout frameLayout = null;
    120     final private String ESCAPE_EVENT = "TouchEscape";
    121 
    122     static {
    123         try {
    124             System.loadLibrary("bulletjme");
    125         } catch (UnsatisfiedLinkError e) {
    126         }
    127         JmeSystem.setSystemDelegate(new JmeAndroidSystem());
    128     }
    129 
    130     @Override
    131     public void onCreate(Bundle savedInstanceState) {
    132         super.onCreate(savedInstanceState);
    133 
    134         Logger log = logger;
    135         boolean bIsLogFormatSet = false;
    136         do {
    137             if (log.getHandlers().length == 0) {
    138                 log = log.getParent();
    139                 if (log != null) {
    140                     for (Handler h : log.getHandlers()) {
    141                         //h.setFormatter(new SimpleFormatter());
    142                         h.setFormatter(new JmeFormatter());
    143                         bIsLogFormatSet = true;
    144                     }
    145                 }
    146             }
    147         } while (log != null && !bIsLogFormatSet);
    148 
    149         JmeAndroidSystem.setResources(getResources());
    150         JmeAndroidSystem.setActivity(this);
    151 
    152         if (screenFullScreen) {
    153             requestWindowFeature(Window.FEATURE_NO_TITLE);
    154             getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    155                     WindowManager.LayoutParams.FLAG_FULLSCREEN);
    156         } else {
    157             if (!screenShowTitle) {
    158                 requestWindowFeature(Window.FEATURE_NO_TITLE);
    159             }
    160         }
    161 
    162         setRequestedOrientation(screenOrientation);
    163 
    164         // Create Settings
    165         AppSettings settings = new AppSettings(true);
    166 
    167         // Create the input class
    168         AndroidInput input = new AndroidInput(this);
    169         input.setMouseEventsInvertX(mouseEventsInvertX);
    170         input.setMouseEventsInvertY(mouseEventsInvertY);
    171         input.setMouseEventsEnabled(mouseEventsEnabled);
    172 
    173         // Create application instance
    174         try {
    175             if (app == null) {
    176                 @SuppressWarnings("unchecked")
    177                 Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
    178                 app = clazz.newInstance();
    179             }
    180 
    181             app.setSettings(settings);
    182             app.start();
    183             ctx = (OGLESContext) app.getContext();
    184             view = ctx.createView(input, eglConfigType, eglConfigVerboseLogging);
    185 
    186             // Set the screen reolution
    187             WindowManager wind = this.getWindowManager();
    188             Display disp = wind.getDefaultDisplay();
    189             ctx.getSettings().setResolution(disp.getWidth(), disp.getHeight());
    190 
    191             AppSettings s = ctx.getSettings();
    192             logger.log(Level.INFO, "Settings: Width {0} Height {1}", new Object[]{s.getWidth(), s.getHeight()});
    193 
    194             layoutDisplay();
    195         } catch (Exception ex) {
    196             handleError("Class " + appClass + " init failed", ex);
    197             setContentView(new TextView(this));
    198         }
    199     }
    200 
    201     @Override
    202     protected void onRestart() {
    203         super.onRestart();
    204         if (app != null) {
    205             app.restart();
    206         }
    207 
    208         logger.info("onRestart");
    209     }
    210 
    211     @Override
    212     protected void onStart() {
    213         super.onStart();
    214         logger.info("onStart");
    215     }
    216 
    217     @Override
    218     protected void onResume() {
    219         super.onResume();
    220         if (view != null) {
    221             view.onResume();
    222         }
    223 
    224         //resume the audio
    225         AudioRenderer result = app.getAudioRenderer();
    226         if (result != null) {
    227             if (result instanceof AndroidAudioRenderer) {
    228                 AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
    229                 renderer.resumeAll();
    230             }
    231         }
    232 
    233         isGLThreadPaused = false;
    234         logger.info("onResume");
    235     }
    236 
    237     @Override
    238     protected void onPause() {
    239         super.onPause();
    240         if (view != null) {
    241             view.onPause();
    242         }
    243 
    244         //pause the audio
    245         AudioRenderer result = app.getAudioRenderer();
    246         if (result != null) {
    247             logger.info("pause: " + result.getClass().getSimpleName());
    248             if (result instanceof AndroidAudioRenderer) {
    249                 AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
    250                 renderer.pauseAll();
    251             }
    252         }
    253 
    254         isGLThreadPaused = true;
    255         logger.info("onPause");
    256     }
    257 
    258     @Override
    259     protected void onStop() {
    260         super.onStop();
    261 
    262         logger.info("onStop");
    263     }
    264 
    265     @Override
    266     protected void onDestroy() {
    267         if (app != null) {
    268             app.stop(!isGLThreadPaused);
    269         }
    270 
    271         logger.info("onDestroy");
    272         super.onDestroy();
    273     }
    274 
    275     public Application getJmeApplication() {
    276         return app;
    277     }
    278 
    279     /**
    280      * Called when an error has occurred. By default, will show an error message
    281      * to the user and print the exception/error to the log.
    282      */
    283     public void handleError(final String errorMsg, final Throwable t) {
    284         String stackTrace = "";
    285         String title = "Error";
    286 
    287         if (t != null) {
    288             // Convert exception to string
    289             StringWriter sw = new StringWriter(100);
    290             t.printStackTrace(new PrintWriter(sw));
    291             stackTrace = sw.toString();
    292             title = t.toString();
    293         }
    294 
    295         final String finalTitle = title;
    296         final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
    297                 + "\n" + stackTrace;
    298 
    299         logger.log(Level.SEVERE, finalMsg);
    300 
    301         runOnUiThread(new Runnable() {
    302 
    303             @Override
    304             public void run() {
    305                 AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
    306                         .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create();
    307                 dialog.show();
    308             }
    309         });
    310     }
    311 
    312     /**
    313      * Called by the android alert dialog, terminate the activity and OpenGL
    314      * rendering
    315      *
    316      * @param dialog
    317      * @param whichButton
    318      */
    319     public void onClick(DialogInterface dialog, int whichButton) {
    320         if (whichButton != -2) {
    321             if (app != null) {
    322                 app.stop(true);
    323             }
    324             this.finish();
    325         }
    326     }
    327 
    328     /**
    329      * Gets called by the InputManager on all touch/drag/scale events
    330      */
    331     @Override
    332     public void onTouch(String name, TouchEvent evt, float tpf) {
    333         if (name.equals(ESCAPE_EVENT)) {
    334             switch (evt.getType()) {
    335                 case KEY_UP:
    336                     runOnUiThread(new Runnable() {
    337 
    338                         @Override
    339                         public void run() {
    340                             AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
    341                                     .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create();
    342                             dialog.show();
    343                         }
    344                     });
    345                     break;
    346                 default:
    347                     break;
    348             }
    349         }
    350     }
    351 
    352     public void layoutDisplay() {
    353         logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);
    354         if (splashPicID != 0) {
    355             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
    356                     LayoutParams.FILL_PARENT,
    357                     LayoutParams.FILL_PARENT,
    358                     Gravity.CENTER);
    359 
    360             frameLayout = new FrameLayout(this);
    361             splashImageView = new ImageView(this);
    362 
    363             Drawable drawable = this.getResources().getDrawable(splashPicID);
    364             if (drawable instanceof NinePatchDrawable) {
    365                 splashImageView.setBackgroundDrawable(drawable);
    366             } else {
    367                 splashImageView.setImageResource(splashPicID);
    368             }
    369 
    370             frameLayout.addView(view);
    371             frameLayout.addView(splashImageView, lp);
    372 
    373             setContentView(frameLayout);
    374             logger.log(Level.INFO, "Splash Screen Created");
    375         } else {
    376             logger.log(Level.INFO, "Splash Screen Skipped.");
    377             setContentView(view);
    378         }
    379     }
    380 
    381     public void removeSplashScreen() {
    382         logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);
    383         if (splashPicID != 0) {
    384             if (frameLayout != null) {
    385                 if (splashImageView != null) {
    386                     this.runOnUiThread(new Runnable() {
    387 
    388                         @Override
    389                         public void run() {
    390                             splashImageView.setVisibility(View.INVISIBLE);
    391                             frameLayout.removeView(splashImageView);
    392                         }
    393                     });
    394                 } else {
    395                     logger.log(Level.INFO, "splashImageView is null");
    396                 }
    397             } else {
    398                 logger.log(Level.INFO, "frameLayout is null");
    399             }
    400         }
    401     }
    402 
    403     public boolean isFinishOnAppStop() {
    404         return finishOnAppStop;
    405     }
    406 
    407 
    408 }
    409