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         }
JmeSystem.setSystemDelegate(new JmeAndroidSystem())127         JmeSystem.setSystemDelegate(new JmeAndroidSystem());
128     }
129 
130     @Override
onCreate(Bundle savedInstanceState)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
onRestart()202     protected void onRestart() {
203         super.onRestart();
204         if (app != null) {
205             app.restart();
206         }
207 
208         logger.info("onRestart");
209     }
210 
211     @Override
onStart()212     protected void onStart() {
213         super.onStart();
214         logger.info("onStart");
215     }
216 
217     @Override
onResume()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
onPause()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
onStop()259     protected void onStop() {
260         super.onStop();
261 
262         logger.info("onStop");
263     }
264 
265     @Override
onDestroy()266     protected void onDestroy() {
267         if (app != null) {
268             app.stop(!isGLThreadPaused);
269         }
270 
271         logger.info("onDestroy");
272         super.onDestroy();
273     }
274 
getJmeApplication()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      */
handleError(final String errorMsg, final Throwable t)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      */
onClick(DialogInterface dialog, int whichButton)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
onTouch(String name, TouchEvent evt, float tpf)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 
layoutDisplay()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 
removeSplashScreen()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 
isFinishOnAppStop()403     public boolean isFinishOnAppStop() {
404         return finishOnAppStop;
405     }
406 
407 
408 }
409