Home | History | Annotate | Download | only in android
      1 /*
      2  * Copyright (c) 2003-2009 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.system.android;
     33 
     34 import android.app.Activity;
     35 import android.app.AlertDialog;
     36 import android.content.Context;
     37 import android.content.DialogInterface;
     38 import android.opengl.GLSurfaceView;
     39 import android.text.InputType;
     40 import android.view.Gravity;
     41 import android.view.SurfaceHolder;
     42 import android.view.ViewGroup.LayoutParams;
     43 import android.widget.EditText;
     44 import android.widget.FrameLayout;
     45 import com.jme3.app.AndroidHarness;
     46 import com.jme3.app.Application;
     47 import com.jme3.input.JoyInput;
     48 import com.jme3.input.KeyInput;
     49 import com.jme3.input.MouseInput;
     50 import com.jme3.input.SoftTextDialogInput;
     51 import com.jme3.input.TouchInput;
     52 import com.jme3.input.android.AndroidInput;
     53 import com.jme3.input.controls.SoftTextDialogInputListener;
     54 import com.jme3.input.controls.TouchTrigger;
     55 import com.jme3.input.dummy.DummyKeyInput;
     56 import com.jme3.input.dummy.DummyMouseInput;
     57 import com.jme3.renderer.android.OGLESShaderRenderer;
     58 import com.jme3.system.AppSettings;
     59 import com.jme3.system.JmeContext;
     60 import com.jme3.system.JmeSystem;
     61 import com.jme3.system.SystemListener;
     62 import com.jme3.system.Timer;
     63 import com.jme3.system.android.AndroidConfigChooser.ConfigType;
     64 import java.util.concurrent.atomic.AtomicBoolean;
     65 import java.util.logging.Level;
     66 import java.util.logging.Logger;
     67 import javax.microedition.khronos.egl.EGL10;
     68 import javax.microedition.khronos.egl.EGLConfig;
     69 import javax.microedition.khronos.egl.EGLContext;
     70 import javax.microedition.khronos.egl.EGLDisplay;
     71 import javax.microedition.khronos.opengles.GL10;
     72 
     73 public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTextDialogInput {
     74 
     75     private static final Logger logger = Logger.getLogger(OGLESContext.class.getName());
     76     protected final AtomicBoolean created = new AtomicBoolean(false);
     77     protected final AtomicBoolean renderable = new AtomicBoolean(false);
     78     protected final AtomicBoolean needClose = new AtomicBoolean(false);
     79     protected final AppSettings settings = new AppSettings(true);
     80 
     81     /*
     82      * >= OpenGL ES 2.0 (Android 2.2+)
     83      */
     84     protected OGLESShaderRenderer renderer;
     85     protected Timer timer;
     86     protected SystemListener listener;
     87     protected boolean autoFlush = true;
     88     protected AndroidInput view;
     89     private boolean firstDrawFrame = true;
     90     //protected int minFrameDuration = 1000 / frameRate;  // Set a max FPS of 33
     91     protected int minFrameDuration = 0;                   // No FPS cap
     92     /**
     93      * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 |
     94      * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0
     95      */
     96     protected int clientOpenGLESVersion = 1;
     97     protected boolean verboseLogging = false;
     98     final private String ESCAPE_EVENT = "TouchEscape";
     99 
    100     public OGLESContext() {
    101     }
    102 
    103     @Override
    104     public Type getType() {
    105         return Type.Display;
    106     }
    107 
    108     /**
    109      * <code>createView</code>
    110      *
    111      * @param activity The Android activity which is parent for the
    112      * GLSurfaceView
    113      * @return GLSurfaceView The newly created view
    114      */
    115     public GLSurfaceView createView(Activity activity) {
    116         return createView(new AndroidInput(activity));
    117     }
    118 
    119     /**
    120      * <code>createView</code>
    121      *
    122      * @param view The Android input which will be used as the GLSurfaceView for
    123      * this context
    124      * @return GLSurfaceView The newly created view
    125      */
    126     public GLSurfaceView createView(AndroidInput view) {
    127         return createView(view, ConfigType.FASTEST, false);
    128     }
    129 
    130     /**
    131      * <code>createView</code> initializes the GLSurfaceView
    132      *
    133      * @param view The Android input which will be used as the GLSurfaceView for
    134      * this context
    135      * @param configType ConfigType.FASTEST (Default) | ConfigType.LEGACY |
    136      * ConfigType.BEST
    137      * @param eglConfigVerboseLogging if true show all found configs
    138      * @return GLSurfaceView The newly created view
    139      */
    140     public GLSurfaceView createView(AndroidInput view, ConfigType configType, boolean eglConfigVerboseLogging) {
    141         // Start to set up the view
    142         this.view = view;
    143         verboseLogging = eglConfigVerboseLogging;
    144 
    145         if (configType == ConfigType.LEGACY) {
    146             // Hardcoded egl setup
    147             clientOpenGLESVersion = 2;
    148             view.setEGLContextClientVersion(2);
    149             //RGB565, Depth16
    150             view.setEGLConfigChooser(5, 6, 5, 0, 16, 0);
    151             logger.info("ConfigType.LEGACY using RGB565");
    152         } else {
    153             EGL10 egl = (EGL10) EGLContext.getEGL();
    154             EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
    155 
    156             int[] version = new int[2];
    157             if (egl.eglInitialize(display, version) == true) {
    158                 logger.log(Level.INFO, "Display EGL Version: {0}.{1}", new Object[]{version[0], version[1]});
    159             }
    160 
    161             try {
    162                 // Create a config chooser
    163                 AndroidConfigChooser configChooser = new AndroidConfigChooser(configType);
    164                 // Init chooser
    165                 if (!configChooser.findConfig(egl, display)) {
    166                     listener.handleError("Unable to find suitable EGL config", null);
    167                     return null;
    168                 }
    169 
    170                 clientOpenGLESVersion = configChooser.getClientOpenGLESVersion();
    171                 if (clientOpenGLESVersion < 2) {
    172                     listener.handleError("OpenGL ES 2.0 is not supported on this device", null);
    173                     return null;
    174                 }
    175 
    176                 // Requesting client version from GLSurfaceView which is extended by
    177                 // AndroidInput.
    178                 view.setEGLContextClientVersion(clientOpenGLESVersion);
    179                 view.setEGLConfigChooser(configChooser);
    180                 view.getHolder().setFormat(configChooser.getPixelFormat());
    181             } finally {
    182                 if (display != null) {
    183                     egl.eglTerminate(display);
    184                 }
    185             }
    186         }
    187 
    188         view.setFocusableInTouchMode(true);
    189         view.setFocusable(true);
    190         view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
    191         view.setRenderer(this);
    192 
    193         return view;
    194     }
    195 
    196     // renderer:initialize
    197     @Override
    198     public void onSurfaceCreated(GL10 gl, EGLConfig cfg) {
    199 
    200         if (created.get() && renderer != null) {
    201             renderer.resetGLObjects();
    202         } else {
    203             if (!created.get()) {
    204                 logger.info("GL Surface created, doing JME3 init");
    205                 initInThread();
    206             } else {
    207                 logger.warning("GL Surface already created");
    208             }
    209         }
    210     }
    211 
    212     protected void initInThread() {
    213         created.set(true);
    214 
    215         logger.info("OGLESContext create");
    216         logger.info("Running on thread: " + Thread.currentThread().getName());
    217 
    218         final Context ctx = this.view.getContext();
    219 
    220         // Setup unhandled Exception Handler
    221         if (ctx instanceof AndroidHarness) {
    222             Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    223 
    224                 public void uncaughtException(Thread thread, Throwable thrown) {
    225                     ((AndroidHarness) ctx).handleError("Exception thrown in " + thread.toString(), thrown);
    226                 }
    227             });
    228         } else {
    229             Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    230 
    231                 public void uncaughtException(Thread thread, Throwable thrown) {
    232                     listener.handleError("Exception thrown in " + thread.toString(), thrown);
    233                 }
    234             });
    235         }
    236 
    237         if (clientOpenGLESVersion < 2) {
    238             throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device");
    239         }
    240 
    241         timer = new AndroidTimer();
    242         renderer = new OGLESShaderRenderer();
    243 
    244         renderer.setUseVA(true);
    245         renderer.setVerboseLogging(verboseLogging);
    246 
    247         renderer.initialize();
    248         listener.initialize();
    249 
    250         // Setup exit hook
    251         if (ctx instanceof AndroidHarness) {
    252             Application app = ((AndroidHarness) ctx).getJmeApplication();
    253             if (app.getInputManager() != null) {
    254                 app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK));
    255                 app.getInputManager().addListener((AndroidHarness) ctx, new String[]{ESCAPE_EVENT});
    256             }
    257         }
    258 
    259         JmeSystem.setSoftTextDialogInput(this);
    260 
    261         needClose.set(false);
    262         renderable.set(true);
    263     }
    264 
    265     /**
    266      * De-initialize in the OpenGL thread.
    267      */
    268     protected void deinitInThread() {
    269         if (renderable.get()) {
    270             created.set(false);
    271             if (renderer != null) {
    272                 renderer.cleanup();
    273             }
    274 
    275             listener.destroy();
    276 
    277             listener = null;
    278             renderer = null;
    279             timer = null;
    280 
    281             // do android specific cleaning here
    282             logger.info("Display destroyed.");
    283 
    284             renderable.set(false);
    285             final Context ctx = this.view.getContext();
    286             if (ctx instanceof AndroidHarness) {
    287                 AndroidHarness harness = (AndroidHarness) ctx;
    288                 if (harness.isFinishOnAppStop()) {
    289                     harness.finish();
    290                 }
    291             }
    292         }
    293     }
    294 
    295     protected void applySettingsToRenderer(OGLESShaderRenderer renderer, AppSettings settings) {
    296         logger.warning("setSettings.USE_VA: [" + settings.getBoolean("USE_VA") + "]");
    297         logger.warning("setSettings.VERBOSE_LOGGING: [" + settings.getBoolean("VERBOSE_LOGGING") + "]");
    298         renderer.setUseVA(settings.getBoolean("USE_VA"));
    299         renderer.setVerboseLogging(settings.getBoolean("VERBOSE_LOGGING"));
    300     }
    301 
    302     protected void applySettings(AppSettings settings) {
    303         setSettings(settings);
    304         if (renderer != null) {
    305             applySettingsToRenderer(renderer, this.settings);
    306         }
    307     }
    308 
    309     @Override
    310     public void setSettings(AppSettings settings) {
    311         this.settings.copyFrom(settings);
    312     }
    313 
    314     @Override
    315     public void setSystemListener(SystemListener listener) {
    316         this.listener = listener;
    317     }
    318 
    319     @Override
    320     public AppSettings getSettings() {
    321         return settings;
    322     }
    323 
    324     @Override
    325     public com.jme3.renderer.Renderer getRenderer() {
    326         return renderer;
    327     }
    328 
    329     @Override
    330     public MouseInput getMouseInput() {
    331         return new DummyMouseInput();
    332     }
    333 
    334     @Override
    335     public KeyInput getKeyInput() {
    336         return new DummyKeyInput();
    337     }
    338 
    339     @Override
    340     public JoyInput getJoyInput() {
    341         return null;
    342     }
    343 
    344     @Override
    345     public TouchInput getTouchInput() {
    346         return view;
    347     }
    348 
    349     @Override
    350     public Timer getTimer() {
    351         return timer;
    352     }
    353 
    354     @Override
    355     public void setTitle(String title) {
    356     }
    357 
    358     @Override
    359     public boolean isCreated() {
    360         return created.get();
    361     }
    362 
    363     @Override
    364     public void setAutoFlushFrames(boolean enabled) {
    365         this.autoFlush = enabled;
    366     }
    367 
    368     // SystemListener:reshape
    369     @Override
    370     public void onSurfaceChanged(GL10 gl, int width, int height) {
    371         logger.info("GL Surface changed, width: " + width + " height: " + height);
    372         settings.setResolution(width, height);
    373         listener.reshape(width, height);
    374     }
    375 
    376     // SystemListener:update
    377     @Override
    378     public void onDrawFrame(GL10 gl) {
    379         if (needClose.get()) {
    380             deinitInThread();
    381             return;
    382         }
    383 
    384         if (renderable.get()) {
    385             if (!created.get()) {
    386                 throw new IllegalStateException("onDrawFrame without create");
    387             }
    388 
    389             long milliStart = System.currentTimeMillis();
    390 
    391             listener.update();
    392 
    393             // call to AndroidHarness to remove the splash screen, if present.
    394             // call after listener.update() to make sure no gap between
    395             //   splash screen going away and app display being shown.
    396             if (firstDrawFrame) {
    397                 final Context ctx = this.view.getContext();
    398                 if (ctx instanceof AndroidHarness) {
    399                     ((AndroidHarness) ctx).removeSplashScreen();
    400                 }
    401                 firstDrawFrame = false;
    402             }
    403 
    404             if (autoFlush) {
    405                 renderer.onFrame();
    406             }
    407 
    408             long milliDelta = System.currentTimeMillis() - milliStart;
    409 
    410             // Enforce a FPS cap
    411             if (milliDelta < minFrameDuration) {
    412                 //logger.log(Level.INFO, "Time per frame {0}", milliDelta);
    413                 try {
    414                     Thread.sleep(minFrameDuration - milliDelta);
    415                 } catch (InterruptedException e) {
    416                 }
    417             }
    418 
    419         }
    420     }
    421 
    422     @Override
    423     public boolean isRenderable() {
    424         return renderable.get();
    425     }
    426 
    427     @Override
    428     public void create(boolean waitFor) {
    429         if (waitFor) {
    430             waitFor(true);
    431         }
    432     }
    433 
    434     public void create() {
    435         create(false);
    436     }
    437 
    438     @Override
    439     public void restart() {
    440     }
    441 
    442     @Override
    443     public void destroy(boolean waitFor) {
    444         needClose.set(true);
    445         if (waitFor) {
    446             waitFor(false);
    447         }
    448     }
    449 
    450     public void destroy() {
    451         destroy(true);
    452     }
    453 
    454     protected void waitFor(boolean createdVal) {
    455         while (renderable.get() != createdVal) {
    456             try {
    457                 Thread.sleep(10);
    458             } catch (InterruptedException ex) {
    459             }
    460         }
    461     }
    462 
    463     public int getClientOpenGLESVersion() {
    464         return clientOpenGLESVersion;
    465     }
    466 
    467     public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) {
    468         logger.log(Level.INFO, "requestDialog: title: {0}, initialValue: {1}",
    469                 new Object[]{title, initialValue});
    470 
    471         JmeAndroidSystem.getActivity().runOnUiThread(new Runnable() {
    472 
    473             @Override
    474             public void run() {
    475 
    476                 final FrameLayout layoutTextDialogInput = new FrameLayout(JmeAndroidSystem.getActivity());
    477                 final EditText editTextDialogInput = new EditText(JmeAndroidSystem.getActivity());
    478                 editTextDialogInput.setWidth(LayoutParams.FILL_PARENT);
    479                 editTextDialogInput.setHeight(LayoutParams.FILL_PARENT);
    480                 editTextDialogInput.setPadding(20, 20, 20, 20);
    481                 editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL);
    482 
    483                 editTextDialogInput.setText(initialValue);
    484 
    485                 switch (id) {
    486                     case SoftTextDialogInput.TEXT_ENTRY_DIALOG:
    487 
    488                         editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT);
    489                         break;
    490 
    491                     case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG:
    492 
    493                         editTextDialogInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
    494                         break;
    495 
    496                     case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG:
    497 
    498                         editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE);
    499                         break;
    500 
    501                     default:
    502                         break;
    503                 }
    504 
    505                 layoutTextDialogInput.addView(editTextDialogInput);
    506 
    507                 AlertDialog dialogTextInput = new AlertDialog.Builder(JmeAndroidSystem.getActivity()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK",
    508                         new DialogInterface.OnClickListener() {
    509 
    510                             public void onClick(DialogInterface dialog, int whichButton) {
    511                                 /* User clicked OK, send COMPLETE action
    512                                  * and text */
    513                                 listener.onSoftText(SoftTextDialogInputListener.COMPLETE, editTextDialogInput.getText().toString());
    514                             }
    515                         }).setNegativeButton("Cancel",
    516                         new DialogInterface.OnClickListener() {
    517 
    518                             public void onClick(DialogInterface dialog, int whichButton) {
    519                                 /* User clicked CANCEL, send CANCEL action
    520                                  * and text */
    521                                 listener.onSoftText(SoftTextDialogInputListener.CANCEL, editTextDialogInput.getText().toString());
    522                             }
    523                         }).create();
    524 
    525                 dialogTextInput.show();
    526             }
    527         });
    528     }
    529 }
    530