All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.app.AndroidHarnessFragment Maven / Gradle / Ivy

There is a newer version: 3.7.0-stable
Show newest version
/*
 * Copyright (c) 2009-2021 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.app;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.jme3.audio.AudioRenderer;
import com.jme3.input.JoyInput;
import com.jme3.input.TouchInput;
import com.jme3.input.android.AndroidSensorJoyInput;
import com.jme3.input.controls.TouchListener;
import com.jme3.input.controls.TouchTrigger;
import com.jme3.input.event.TouchEvent;
import static com.jme3.input.event.TouchEvent.Type.KEY_UP;
import com.jme3.system.AppSettings;
import com.jme3.system.SystemListener;
import com.jme3.system.android.JmeAndroidSystem;
import com.jme3.system.android.OGLESContext;
import com.jme3.util.AndroidLogHandler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

/**
 *
 * @author iwgeric
 */
public class AndroidHarnessFragment extends Fragment implements
        TouchListener, DialogInterface.OnClickListener, View.OnLayoutChangeListener, SystemListener {
    private static final Logger logger = Logger.getLogger(AndroidHarnessFragment.class.getName());

    /**
     * The application class to start
     */
    protected String appClass = "jme3test.android.Test";

    /**
     * Sets the desired RGB size for the surfaceview.  16 = RGB565, 24 = RGB888.
     * (default = 24)
     */
    protected int eglBitsPerPixel = 24;

    /**
     * Sets the desired number of Alpha bits for the surfaceview.  This affects
     * how the surfaceview is able to display Android views that are located
     * under the surfaceview jME uses to render the scenegraph.
     * 0 = Opaque surfaceview background (fastest)
     * 1->7 = Transparent surfaceview background
     * 8 or higher = Translucent surfaceview background
     * (default = 0)
     */
    protected int eglAlphaBits = 0;

    /**
     * The number of depth bits specifies the precision of the depth buffer.
     * (default = 16)
     */
    protected int eglDepthBits = 16;

    /**
     * Sets the number of samples to use for multisampling.
* Leave 0 (default) to disable multisampling.
* Set to 2 or 4 to enable multisampling. */ protected int eglSamples = 0; /** * Set the number of stencil bits. * (default = 0) */ protected int eglStencilBits = 0; /** * Set the desired frame rate. If frameRate higher than 0, the application * will be capped at the desired frame rate. * (default = -1, no frame rate cap) */ protected int frameRate = -1; /** * Set the maximum resolution for the surfaceview in either the * width or height screen direction depending on the screen size. * If the surfaceview is rectangular, the longest side (width or height) * will have the resolution set to a maximum of maxResolutionDimension. * The other direction will be set to a value that maintains the aspect * ratio of the surfaceview.
* Any value less than 0 (default = -1) will result in the surfaceview having the * same resolution as the view layout (i.e. no max resolution). */ protected int maxResolutionDimension = -1; /** * Sets the type of Audio Renderer to be used. *

* Android MediaPlayer / SoundPool can be used on all * supported Android platform versions (2.2+)
* OpenAL Soft uses an OpenSL backend and is only supported on Android * versions 2.3+. *

* Only use ANDROID_ static strings found in AppSettings * */ protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; /** * If true Android Sensors are used as simulated Joysticks. Users can use the * Android sensor feedback through the RawInputListener or by registering * JoyAxisTriggers. */ protected boolean joystickEventsEnabled = false; /** * If true KeyEvents are generated from TouchEvents */ protected boolean keyEventsEnabled = true; /** * If true MouseEvents are generated from TouchEvents */ protected boolean mouseEventsEnabled = true; /** * Flip X axis */ protected boolean mouseEventsInvertX = false; /** * Flip Y axis */ protected boolean mouseEventsInvertY = false; /** * if true finish this activity when the jme app is stopped */ protected boolean finishOnAppStop = true; /** * set to false if you don't want the harness to handle the exit hook */ protected boolean handleExitHook = 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."; /** * 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; protected FrameLayout frameLayout = null; protected GLSurfaceView view = null; protected ImageView splashImageView = null; final private String ESCAPE_EVENT = "TouchEscape"; private boolean firstDrawFrame = true; private LegacyApplication app = null; private int viewWidth = 0; private int viewHeight = 0; // Retrieves the jME application object public Application getJmeApplication() { return app; } @Override public void onAttach(Activity activity) { super.onAttach(activity); } /** * This Fragment uses setRetainInstance(true) so the onCreate method will only * be called once. During device configuration changes, the instance of * this Fragment will be reused in the new Activity. This method should not * contain any View related objects. They are created and destroyed by * other methods. View related objects should not be reused, but rather * created and destroyed along with the Activity. * * @param savedInstanceState the saved instance state */ @Override @SuppressWarnings("unchecked") public void onCreate(Bundle savedInstanceState) { initializeLogHandler(); logger.fine("onCreate"); super.onCreate(savedInstanceState); // Create Settings logger.log(Level.FINE, "Creating settings"); AppSettings settings = new AppSettings(true); settings.setEmulateMouse(mouseEventsEnabled); settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY); settings.setUseJoysticks(joystickEventsEnabled); settings.setEmulateKeyboard(keyEventsEnabled); settings.setBitsPerPixel(eglBitsPerPixel); settings.setAlphaBits(eglAlphaBits); settings.setDepthBits(eglDepthBits); settings.setSamples(eglSamples); settings.setStencilBits(eglStencilBits); settings.setAudioRenderer(audioRendererType); settings.setFrameRate(frameRate); // Create application instance try { if (app == null) { Class clazz = Class.forName(appClass); app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); } app.setSettings(settings); app.start(); } catch (Exception ex) { handleError("Class " + appClass + " init failed", ex); } OGLESContext ctx = (OGLESContext) app.getContext(); // AndroidHarness wraps the app as a SystemListener. ctx.setSystemListener(this); setRetainInstance(true); } /** * Called by the system to create the View hierarchy associated with this * Fragment. For jME, this is a FrameLayout that contains the GLSurfaceView * and an overlaying SplashScreen Image (if used). The View that is returned * will be placed on the screen within the boundaries of the View borders defined * by the Activity's layout parameters for this Fragment. For jME, we also * update the application reference to the new view. * * @param inflater ignored * @param container ignored * @param savedInstanceState ignored * @return the new view */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { logger.fine("onCreateView"); // Create the GLSurfaceView for the application view = ((OGLESContext) app.getContext()).createView(getActivity()); // store the glSurfaceView in JmeAndroidSystem for future use JmeAndroidSystem.setView(view); createLayout(); view.addOnLayoutChangeListener(this); return frameLayout; } @Override public void onActivityCreated(Bundle savedInstanceState) { logger.fine("onActivityCreated"); super.onActivityCreated(savedInstanceState); } @Override public void onStart() { logger.fine("onStart"); super.onStart(); } /** * When the Fragment resumes (i.e. after app resumes or device screen turned * back on), call the gainFocus() in the jME application. */ @Override public void onResume() { logger.fine("onResume"); super.onResume(); gainFocus(); } /** * When the Fragment pauses (i.e. after home button pressed on the device * or device screen turned off) , call the loseFocus() in the jME application. */ @Override public void onPause() { logger.fine("onPause"); loseFocus(); super.onPause(); } @Override public void onStop() { logger.fine("onStop"); super.onStop(); } /** * Called by the Android system each time the Activity is destroyed or recreated. * For jME, we clear references to the GLSurfaceView. */ @Override public void onDestroyView() { logger.fine("onDestroyView"); if (splashImageView != null && splashImageView.getParent() != null) { ((ViewGroup) splashImageView.getParent()).removeView(splashImageView); } if (view.getParent() != null) { ((ViewGroup) view.getParent()).removeView(view); } if (frameLayout != null && frameLayout.getParent() != null) { ((ViewGroup) frameLayout.getParent()).removeView(frameLayout); } view.removeOnLayoutChangeListener(this); splashImageView = null; frameLayout = null; view = null; JmeAndroidSystem.setView(null); super.onDestroyView(); } /** * Called by the system when the application is being destroyed. In this case, * the jME application is actually closed as well. This method is not called * during device configuration changes or when the application is put in the * background. */ @Override public void onDestroy() { logger.fine("onDestroy"); if (app != null) { app.stop(false); } app = null; super.onDestroy(); } @Override public void onDetach() { logger.fine("onDetach"); super.onDetach(); } /** * Called when an error has occurred. By default, will show an error message * to the user and print the exception/error to the log. */ @Override 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); getActivity().runOnUiThread(new Runnable() { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(finalTitle); builder.setPositiveButton("Kill", AndroidHarnessFragment.this); builder.setMessage(finalMsg); AlertDialog dialog = builder.create(); dialog.show(); } }); } /** * Called by the android alert dialog, terminate the activity and OpenGL * rendering * * @param dialog ignored * @param whichButton the button index */ @Override public void onClick(DialogInterface dialog, int whichButton) { if (whichButton != -2) { if (app != null) { app.stop(true); } app = null; getActivity().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: getActivity().runOnUiThread(new Runnable() { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(exitDialogTitle); builder.setPositiveButton("Yes", AndroidHarnessFragment.this); builder.setNegativeButton("No", AndroidHarnessFragment.this); builder.setMessage(exitDialogMessage); AlertDialog dialog = builder.create(); dialog.show(); } }); break; default: break; } } } public void createLayout() { logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); if (frameLayout != null && frameLayout.getParent() != null) { ((ViewGroup) frameLayout.getParent()).removeView(frameLayout); } frameLayout = new FrameLayout(getActivity()); if (view.getParent() != null) { ((ViewGroup) view.getParent()).removeView(view); } frameLayout.addView(view); if (splashPicID != 0) { splashImageView = new ImageView(getActivity()); Drawable drawable = getResources().getDrawable(splashPicID); if (drawable instanceof NinePatchDrawable) { splashImageView.setBackgroundDrawable(drawable); } else { splashImageView.setImageResource(splashPicID); } if (splashImageView.getParent() != null) { ((ViewGroup) splashImageView.getParent()).removeView(splashImageView); } frameLayout.addView(splashImageView, lp); logger.fine("Splash Screen Created"); } else { logger.fine("Splash Screen Skipped."); } } public void removeSplashScreen() { logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); if (splashPicID != 0) { if (frameLayout != null) { if (splashImageView != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { splashImageView.setVisibility(View.INVISIBLE); frameLayout.removeView(splashImageView); } }); } else { logger.fine("splashImageView is null"); } } else { logger.fine("frameLayout is null"); } } } /** * Removes the standard Android log handler due to an issue with not logging * entries lower than INFO level and adds a handler that produces * JME formatted log messages. */ protected void initializeLogHandler() { Logger log = LogManager.getLogManager().getLogger(""); for (Handler handler : log.getHandlers()) { if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) { Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName()); } log.removeHandler(handler); } Handler handler = new AndroidLogHandler(); log.addHandler(handler); handler.setLevel(Level.ALL); } @Override public void initialize() { app.initialize(); if (handleExitHook) { // remove existing mapping from SimpleApplication that stops the app // when the esc key is pressed (esc key = android back key) so that // AndroidHarness can produce the exit app dialog box. if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) { app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT); } app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT}); } } @Override public void reshape(int width, int height) { app.reshape(width, height); } @Override public void rescale(float x, float y) { app.rescale(x, y); } @Override public void update() { app.update(); // call to remove the splash screen, if present. // call after app.update() to make sure no gap between // splash screen going away and app display being shown. if (firstDrawFrame) { removeSplashScreen(); firstDrawFrame = false; } } @Override public void requestClose(boolean esc) { app.requestClose(esc); } @Override public void destroy() { if (app != null) { app.destroy(); } if (finishOnAppStop) { getActivity().finish(); } } @Override public void gainFocus() { logger.fine("gainFocus"); if (view != null) { view.onResume(); } if (app != null) { //resume the audio AudioRenderer audioRenderer = app.getAudioRenderer(); if (audioRenderer != null) { audioRenderer.resumeAll(); } //resume the sensors (aka joysticks) if (app.getContext() != null) { JoyInput joyInput = app.getContext().getJoyInput(); if (joyInput != null) { if (joyInput instanceof AndroidSensorJoyInput) { AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; androidJoyInput.resumeSensors(); } } } } if (app != null) { app.gainFocus(); } } @Override public void loseFocus() { logger.fine("loseFocus"); if (app != null) { app.loseFocus(); } if (view != null) { view.onPause(); } if (app != null) { //pause the audio AudioRenderer audioRenderer = app.getAudioRenderer(); if (audioRenderer != null) { audioRenderer.pauseAll(); } //pause the sensors (aka joysticks) if (app.getContext() != null) { JoyInput joyInput = app.getContext().getJoyInput(); if (joyInput != null) { if (joyInput instanceof AndroidSensorJoyInput) { AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; androidJoyInput.pauseSensors(); } } } } } @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if (v.equals(view)) { // logger.log(Level.INFO, "surfaceview layout changed. left: {0}, top: {1}, right: {2}, bottom: {3}", // new Object[]{left, top, right, bottom}); if (v.equals(view) && maxResolutionDimension > 0) { int newWidth = right-left; int newHeight = bottom-top; if (viewWidth != newWidth || viewHeight != newHeight) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SurfaceView layout changed: old width: {0}, old height: {1}, new width: {2}, new height: {3}", new Object[]{viewWidth, viewHeight, newWidth, newHeight}); } viewWidth = newWidth; viewHeight = newHeight; int fixedSizeWidth = viewWidth; int fixedSizeHeight = viewHeight; if (viewWidth > viewHeight && viewWidth > maxResolutionDimension) { // landscape fixedSizeWidth = maxResolutionDimension; fixedSizeHeight = (int)(maxResolutionDimension * (viewHeight / (float)viewWidth)); } else if (viewHeight > viewWidth && viewHeight > maxResolutionDimension) { // portrait fixedSizeWidth = (int)(maxResolutionDimension * (viewWidth / (float)viewHeight)); fixedSizeHeight = maxResolutionDimension; } else if (viewWidth == viewHeight && viewWidth > maxResolutionDimension) { fixedSizeWidth = maxResolutionDimension; fixedSizeHeight = maxResolutionDimension; } // set the surfaceview resolution if the size != current view size if (fixedSizeWidth != viewWidth || fixedSizeHeight != viewHeight) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "setting surfaceview resolution to width: {0}, height: {1}", new Object[]{fixedSizeWidth, fixedSizeHeight}); } view.getHolder().setFixedSize(fixedSizeWidth, fixedSizeHeight); } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy