diff --git a/build.gradle b/build.gradle index d3bf104cf2..4f715d5958 100644 --- a/build.gradle +++ b/build.gradle @@ -244,6 +244,7 @@ tasks.register('mergedJavadoc', Javadoc) { options.overview = file("javadoc-overview.html") source = mergedJavadocSubprojects.collect { project(it).sourceSets.main.allJava } + source += fileTree("jme3-android/src/androidx-stubs/java") classpath = files(mergedJavadocSubprojects.collect { project(it).sourceSets.main.compileClasspath }) } diff --git a/jme3-android-examples/src/main/AndroidManifest.xml b/jme3-android-examples/src/main/AndroidManifest.xml index 075d2a515c..1ec9d5d342 100644 --- a/jme3-android-examples/src/main/AndroidManifest.xml +++ b/jme3-android-examples/src/main/AndroidManifest.xml @@ -25,6 +25,20 @@ android:launchMode="singleTask" android:screenOrientation="landscape"> + + + + diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestHarnessActivity.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestHarnessActivity.java new file mode 100644 index 0000000000..39b531262a --- /dev/null +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestHarnessActivity.java @@ -0,0 +1,32 @@ +package org.jmonkeyengine.jme3androidexamples; + +import android.os.Bundle; +import com.jme3.app.AndroidHarness; + +/** + * Example and verification Activity extending the compatibility AndroidHarness. + */ +public class TestHarnessActivity extends AndroidHarness { + + public TestHarnessActivity() { + appClass = "jme3test.android.TestAndroidSensors"; + eglBitsPerPixel = 24; + eglAlphaBits = 0; + eglDepthBits = 24; + eglSamples = 0; + eglStencilBits = 8; + frameRate = 60; + mouseEventsEnabled = true; + keyEventsEnabled = true; + joystickEventsEnabled = true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Bundle bundle = getIntent().getExtras(); + if (bundle != null && bundle.containsKey(MainActivity.SELECTED_APP_CLASS)) { + appClass = bundle.getString(MainActivity.SELECTED_APP_CLASS); + } + super.onCreate(savedInstanceState); + } +} diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestSurfaceViewActivity.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestSurfaceViewActivity.java new file mode 100644 index 0000000000..ed3b4714b2 --- /dev/null +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestSurfaceViewActivity.java @@ -0,0 +1,76 @@ +package org.jmonkeyengine.jme3androidexamples; + +import android.os.Bundle; +import android.widget.RelativeLayout; +import androidx.fragment.app.FragmentActivity; +import com.jme3.app.LegacyApplication; +import com.jme3.system.AppSettings; +import com.jme3.view.surfaceview.JmeSurfaceView; +import com.jme3.view.surfaceview.OnExceptionThrown; +import com.jme3.view.surfaceview.OnLayoutDrawn; +import com.jme3.view.surfaceview.OnRendererCompleted; +import com.jme3.view.surfaceview.OnRendererStarted; + +/** + * Example and verification Activity embedding JmeSurfaceView. + */ +public class TestSurfaceViewActivity extends FragmentActivity { + + private JmeSurfaceView jmeSurfaceView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); + getWindow().setFlags(android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN, + android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN); + super.onCreate(savedInstanceState); + + RelativeLayout layout = new RelativeLayout(this); + jmeSurfaceView = new JmeSurfaceView(this); + + String appClass = "jme3test.android.TestAndroidSensors"; + Bundle bundle = getIntent().getExtras(); + if (bundle != null && bundle.containsKey(MainActivity.SELECTED_APP_CLASS)) { + appClass = bundle.getString(MainActivity.SELECTED_APP_CLASS); + } + + try { + Class clazz = Class.forName(appClass); + LegacyApplication app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + jmeSurfaceView.setLegacyApplication(app); + } catch (Exception e) { + throw new RuntimeException(e); + } + + jmeSurfaceView.setOnRendererStarted(new OnRendererStarted() { + @Override + public void onRenderStart(LegacyApplication application, android.view.View layout) { + } + }); + + jmeSurfaceView.setOnRendererCompleted(new OnRendererCompleted() { + @Override + public void onRenderCompletion(LegacyApplication application, AppSettings appSettings) { + } + }); + + jmeSurfaceView.setOnLayoutDrawn(new OnLayoutDrawn() { + @Override + public void onLayoutDrawn(LegacyApplication application, android.view.View layout) { + } + }); + + jmeSurfaceView.setOnExceptionThrown(new OnExceptionThrown() { + @Override + public void onExceptionThrown(Throwable e) { + } + }); + + getLifecycle().addObserver(jmeSurfaceView); + + layout.addView(jmeSurfaceView); + setContentView(layout); + + jmeSurfaceView.startRenderer(0); + } +} diff --git a/jme3-android/build.gradle b/jme3-android/build.gradle index 9ed242a3e0..fa8c702555 100644 --- a/jme3-android/build.gradle +++ b/jme3-android/build.gradle @@ -17,6 +17,11 @@ dependencies { compileOnly files(rootProject.file('lib/android.jar')) } +compileJava { + options.sourcepath = files("src/androidx-stubs/java") + options.compilerArgs += ["-implicit:none"] +} + tasks.withType(Jar).configureEach { exclude('androidx/**') } @@ -26,5 +31,6 @@ tasks.named('sourcesJar') { } javadoc { + source += fileTree("src/androidx-stubs/java") exclude('androidx/**') } diff --git a/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentActivity.java b/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentActivity.java index 4a1ad4af46..e93893efe9 100644 --- a/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentActivity.java +++ b/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentActivity.java @@ -40,4 +40,8 @@ * application. This class is excluded from jme3-android artifacts.

*/ public class FragmentActivity extends Activity { + + public FragmentManager getSupportFragmentManager() { + return null; + } } diff --git a/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentManager.java b/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentManager.java new file mode 100644 index 0000000000..5ae28b71d8 --- /dev/null +++ b/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentManager.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2026 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 androidx.fragment.app; + +/** + * Compile-time stub for the AndroidX FragmentManager API. + * + *

The real AndroidX Fragment dependency must be supplied by the Android + * application. This class is excluded from jme3-android artifacts.

+ */ +public class FragmentManager { + + public Fragment findFragmentByTag(String tag) { + return null; + } + + public FragmentTransaction beginTransaction() { + return null; + } +} diff --git a/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentTransaction.java b/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentTransaction.java new file mode 100644 index 0000000000..ba509e93fe --- /dev/null +++ b/jme3-android/src/androidx-stubs/java/androidx/fragment/app/FragmentTransaction.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2026 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 androidx.fragment.app; + +/** + * Compile-time stub for the AndroidX FragmentTransaction API. + * + *

The real AndroidX Fragment dependency must be supplied by the Android + * application. This class is excluded from jme3-android artifacts.

+ */ +public class FragmentTransaction { + + public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { + return this; + } + + public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { + return this; + } + + public int commit() { + return 0; + } +} diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java new file mode 100644 index 0000000000..5683402b15 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2009-2026 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.AlertDialog; +import android.content.DialogInterface; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.JoyInput; +import com.jme3.input.TouchInput; +import com.jme3.input.android.AndroidJoyInput; +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 com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.system.android.OGLESContext; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Compatibility harness Activity that delegates lifecycle management to an + * internal {@link AndroidHarnessFragment}. + * + * @deprecated Prefer using {@link AndroidHarnessFragment} directly. + */ +@Deprecated +public class AndroidHarness extends FragmentActivity + implements TouchListener, DialogInterface.OnClickListener, SystemListener { + + protected static final Logger logger = Logger.getLogger(AndroidHarness.class.getName()); + private static final String HARNESS_FRAGMENT_TAG = "com.jme3.app.AndroidHarness.fragment"; + private static final String ESCAPE_EVENT = "TouchEscape"; + + /** + * The application class to start. + */ + protected String appClass = "jme3test.android.Test"; + + /** + * The jME application object. + */ + protected LegacyApplication app; + + protected int eglBitsPerPixel = 24; + protected int eglAlphaBits = 0; + protected int eglDepthBits = 16; + protected int eglSamples = 0; + protected int eglStencilBits = 0; + protected int frameRate = -1; + protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + protected boolean joystickEventsEnabled = false; + protected boolean keyEventsEnabled = true; + protected boolean mouseEventsEnabled = true; + protected boolean mouseEventsInvertX = false; + protected boolean mouseEventsInvertY = false; + protected boolean finishOnAppStop = true; + protected boolean handleExitHook = true; + protected String exitDialogTitle = "Do you want to exit?"; + protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; + protected boolean screenFullScreen = true; + protected boolean screenShowTitle = true; + protected int splashPicID = 0; + + protected OGLESContext ctx; + protected GLSurfaceView view; + protected boolean isGLThreadPaused = true; + protected ImageView splashImageView; + protected FrameLayout frameLayout; + private boolean firstDrawFrame = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + configureWindow(); + + HarnessFragment fragment = attachFragment(); + fragment.setFinishOnAppStop(finishOnAppStop); + } + + private void configureWindow() { + 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); + } + } + + private HarnessFragment attachFragment() { + FragmentManager fm = getSupportFragmentManager(); + HarnessFragment fragment = (HarnessFragment) fm.findFragmentByTag(HARNESS_FRAGMENT_TAG); + if (fragment == null) { + fragment = new HarnessFragment(); + fm.beginTransaction() + .add(android.R.id.content, fragment, HARNESS_FRAGMENT_TAG) + .commit(); + } + return fragment; + } + + public Application getJmeApplication() { + return app; + } + + public void handleError(final String errorMsg, final Throwable throwable) { + String stackTrace = ""; + String title = "Error"; + + if (throwable != null) { + StringWriter writer = new StringWriter(100); + throwable.printStackTrace(new PrintWriter(writer)); + stackTrace = writer.toString(); + title = throwable.toString(); + } + + final String finalTitle = title; + final String finalMessage = (errorMsg != null ? errorMsg : "Uncaught Exception") + + "\n" + stackTrace; + + logger.log(Level.SEVERE, finalMessage); + + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) + .setTitle(finalTitle) + .setMessage(finalMessage) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (app != null) { + app.stop(true); + } + finish(); + } + }) + .setNegativeButton("Kill", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + android.os.Process.killProcess(android.os.Process.myPid()); + } + }) + .setCancelable(true) + .create(); + dialog.show(); + } + }); + } + + @Override + public void onClick(DialogInterface dialog, int whichButton) { + if (whichButton == DialogInterface.BUTTON_POSITIVE) { + if (app != null) { + app.stop(true); + } + app = null; + if (finishOnAppStop) { + finish(); + } + } + } + + @Override + public void onTouch(String name, TouchEvent event, float tpf) { + if (name.equals(ESCAPE_EVENT)) { + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) + .setTitle(exitDialogTitle) + .setMessage(exitDialogMessage) + .setPositiveButton("Yes", AndroidHarness.this) + .setNegativeButton("No", AndroidHarness.this) + .create(); + dialog.show(); + } + }); + } + } + + public void layoutDisplay() { + logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); + frameLayout = null; + splashImageView = null; + + if (splashPicID == 0 || view == null) { + return; + } + + frameLayout = new FrameLayout(this); + frameLayout.addView(view); + + splashImageView = new ImageView(this); + Drawable drawable = getResources().getDrawable(splashPicID); + if (drawable instanceof NinePatchDrawable) { + splashImageView.setBackgroundDrawable(drawable); + } else { + splashImageView.setImageResource(splashPicID); + } + + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.CENTER); + frameLayout.addView(splashImageView, layoutParams); + } + + public void removeSplashScreen() { + if (splashImageView != null && frameLayout != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + splashImageView.setVisibility(View.INVISIBLE); + frameLayout.removeView(splashImageView); + } + }); + } + } + + @Override + public void initialize() { + if (app != null) { + app.initialize(); + if (handleExitHook) { + 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) { + if (app != null) { + app.reshape(width, height); + } + } + + @Override + public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { + if (app != null) { + app.reshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight); + } + } + + @Override + public void rescale(float x, float y) { + if (app != null) { + app.rescale(x, y); + } + } + + @Override + public void update() { + if (app != null) { + app.update(); + } + if (firstDrawFrame) { + removeSplashScreen(); + firstDrawFrame = false; + } + } + + @Override + public void requestClose(boolean esc) { + if (app != null) { + app.requestClose(esc); + } + } + + @Override + public void destroy() { + if (app != null) { + app.destroy(); + } + if (finishOnAppStop) { + finish(); + } + } + + @Override + public void gainFocus() { + logger.fine("gainFocus"); + if (view != null) { + view.onResume(); + } + + if (app != null) { + AudioRenderer audioRenderer = app.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.resumeAll(); + } + + JoyInput joyInput = app.getContext() != null ? app.getContext().getJoyInput() : null; + if (joyInput instanceof AndroidJoyInput) { + ((AndroidJoyInput) joyInput).resumeJoysticks(); + } else if (joyInput instanceof AndroidSensorJoyInput) { + ((AndroidSensorJoyInput) joyInput).resumeSensors(); + } + + app.gainFocus(); + } + isGLThreadPaused = false; + } + + @Override + public void loseFocus() { + logger.fine("loseFocus"); + if (app != null) { + app.loseFocus(); + } + + if (view != null) { + view.onPause(); + } + + if (app != null) { + AudioRenderer audioRenderer = app.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.pauseAll(); + } + + JoyInput joyInput = app.getContext() != null ? app.getContext().getJoyInput() : null; + if (joyInput instanceof AndroidJoyInput) { + ((AndroidJoyInput) joyInput).pauseJoysticks(); + } else if (joyInput instanceof AndroidSensorJoyInput) { + ((AndroidSensorJoyInput) joyInput).pauseSensors(); + } + } + isGLThreadPaused = true; + } + + public static class HarnessFragment extends AndroidHarnessFragment { + + private FrameLayout frameLayout; + private ImageView splashImageView; + + private AndroidHarness harness() { + return (AndroidHarness) requireActivity(); + } + + @Override + protected LegacyApplication createApplication() throws Exception { + AndroidHarness harness = harness(); + Class clazz = Class.forName(harness.appClass); + LegacyApplication application = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + + AppSettings settings = new AppSettings(true); + settings.setAudioRenderer(harness.audioRendererType); + settings.setEmulateMouse(harness.mouseEventsEnabled); + settings.setEmulateMouseFlipAxis(harness.mouseEventsInvertX, harness.mouseEventsInvertY); + settings.setUseJoysticks(harness.joystickEventsEnabled); + settings.setEmulateKeyboard(harness.keyEventsEnabled); + + settings.setBitsPerPixel(harness.eglBitsPerPixel); + settings.setAlphaBits(harness.eglAlphaBits); + settings.setDepthBits(harness.eglDepthBits); + settings.setSamples(harness.eglSamples); + settings.setStencilBits(harness.eglStencilBits); + settings.setFrameRate(harness.frameRate); + + application.setSettings(settings); + harness.app = application; + return application; + } + + @Override + public View onCreateView(android.view.LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View jmeView = super.onCreateView(inflater, container, savedInstanceState); + AndroidHarness harness = harness(); + if (jmeView instanceof GLSurfaceView) { + harness.view = (GLSurfaceView) jmeView; + } + harness.ctx = harness.app != null ? (OGLESContext) harness.app.getContext() : null; + if (harness.app == null) { + return jmeView; + } + + harness.layoutDisplay(); + frameLayout = harness.frameLayout; + splashImageView = harness.splashImageView; + return frameLayout != null ? frameLayout : jmeView; + } + + @Override + public void onDestroyView() { + if (splashImageView != null && splashImageView.getParent() instanceof ViewGroup) { + ((ViewGroup) splashImageView.getParent()).removeView(splashImageView); + } + if (frameLayout != null && frameLayout.getParent() instanceof ViewGroup) { + ((ViewGroup) frameLayout.getParent()).removeView(frameLayout); + } + splashImageView = null; + frameLayout = null; + AndroidHarness harness = harness(); + harness.frameLayout = null; + harness.splashImageView = null; + harness.view = null; + JmeAndroidSystem.setView(null); + super.onDestroyView(); + } + + @Override + public void initialize() { + harness().initialize(); + } + + @Override + public void reshape(int width, int height) { + harness().reshape(width, height); + } + + @Override + public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { + harness().reshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight); + } + + @Override + public void rescale(float x, float y) { + harness().rescale(x, y); + } + + @Override + public void update() { + harness().update(); + } + + @Override + public void requestClose(boolean esc) { + harness().requestClose(esc); + } + + @Override + public void destroy() { + harness().destroy(); + } + + @Override + public void gainFocus() { + harness().gainFocus(); + } + + @Override + public void loseFocus() { + harness().loseFocus(); + } + + @Override + public void handleError(String errorMsg, Throwable throwable) { + harness().handleError(errorMsg, throwable); + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java new file mode 100644 index 0000000000..8084a08e4c --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java @@ -0,0 +1,664 @@ +/* + * Copyright (c) 2009-2026 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.view.surfaceview; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.ConfigurationInfo; +import android.opengl.GLSurfaceView; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; +import com.jme3.app.LegacyApplication; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.JoyInput; +import com.jme3.input.android.AndroidJoyInput; +import com.jme3.input.android.AndroidSensorJoyInput; +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.AndroidNativeBufferAllocator; +import com.jme3.util.BufferAllocatorFactory; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An OpenGL Android View wrapper for embedding a jMonkeyEngine application in custom layouts. + * + * @author pavl_g. + * @deprecated Use {@link com.jme3.app.AndroidHarnessFragment} instead. + */ +@Deprecated +public class JmeSurfaceView extends RelativeLayout + implements SystemListener, DialogInterface.OnClickListener, LifecycleEventObserver { + + private static final Logger logger = Logger.getLogger(JmeSurfaceView.class.getName()); + private static final String SAFER_BUFFER_ALLOCATOR_CLASS = "com.jme3.util.SaferBufferAllocator"; + + public enum DestructionPolicy { + DESTROY_WHEN_FINISH, + KEEP_WHEN_FINISH + } + + protected static final class GameState { + private static LegacyApplication legacyApplication; + private static boolean firstUpdatePassed = false; + + private GameState() { + } + + protected static LegacyApplication getLegacyApplication() { + return legacyApplication; + } + + protected static void setLegacyApplication(LegacyApplication legacyApplication) { + GameState.legacyApplication = legacyApplication; + } + + protected static boolean isFirstUpdatePassed() { + return firstUpdatePassed; + } + + protected static void setFirstUpdatePassed(boolean firstUpdatePassed) { + GameState.firstUpdatePassed = firstUpdatePassed; + } + } + + protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + private LegacyApplication legacyApplication; + private AppSettings appSettings; + private int eglBitsPerPixel = 24; + private int eglAlphaBits = 0; + private int eglDepthBits = 16; + private int eglSamples = 0; + private int eglStencilBits = 0; + private int frameRate = -1; + private boolean emulateKeyBoard = true; + private boolean emulateMouse = true; + private boolean useJoyStickEvents = true; + private boolean isGLThreadPaused = true; + private boolean showErrorDialog = true; + private boolean bindAppState = true; + private boolean showEscExitPrompt = true; + private boolean exitOnEscPressed = true; + private String crashLog = ""; + private String glEsVersion = ""; + + private GLSurfaceView glSurfaceView; + private OGLESContext oglesContext; + private DestructionPolicy destructionPolicy = DestructionPolicy.DESTROY_WHEN_FINISH; + private Handler handler = new Handler(Looper.getMainLooper()); + + private OnRendererStarted onRendererStarted; + private OnRendererCompleted onRendererCompleted; + private OnLayoutDrawn onLayoutDrawn; + private OnExceptionThrown onExceptionThrown; + + public JmeSurfaceView(@NonNull Context context) { + super(context); + } + + public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public JmeSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void startRenderer(int delayMillis) { + delayMillis = Math.max(0, delayMillis); + + try { + ConfigurationInfo configurationInfo = ((ActivityManager) getContext() + .getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo(); + glEsVersion = "GL_ES Version : " + configurationInfo.getGlEsVersion(); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to retrieve GL ES version", e); + } + + if (legacyApplication == null) { + throw new IllegalStateException("Cannot build a SurfaceView for a null app, make sure to use setLegacyApplication() to pass in your app!"); + } + + if (System.getProperty(BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION) == null) { + String allocator = isClassPresent(SAFER_BUFFER_ALLOCATOR_CLASS) + ? SAFER_BUFFER_ALLOCATOR_CLASS + : AndroidNativeBufferAllocator.class.getName(); + System.setProperty(BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION, allocator); + } + + if (appSettings == null) { + appSettings = new AppSettings(true); + } + appSettings.setAudioRenderer(audioRendererType); + if (getLayoutParams() != null && getLayoutParams().width > 0 && getLayoutParams().height > 0) { + appSettings.setResolution(getLayoutParams().width, getLayoutParams().height); + } + appSettings.setAlphaBits(eglAlphaBits); + appSettings.setDepthBits(eglDepthBits); + appSettings.setSamples(eglSamples); + appSettings.setStencilBits(eglStencilBits); + appSettings.setBitsPerPixel(eglBitsPerPixel); + appSettings.setFrameRate(frameRate); + appSettings.setEmulateKeyboard(emulateKeyBoard); + appSettings.setEmulateMouse(emulateMouse); + appSettings.setUseJoysticks(useJoyStickEvents); + + if (GameState.getLegacyApplication() != null) { + this.legacyApplication = GameState.getLegacyApplication(); + logger.log(Level.INFO, "Reusing existing GameState application"); + } else { + legacyApplication.setSettings(appSettings); + logger.log(Level.INFO, "Starting a new GameState application"); + legacyApplication.start(); + + if (onRendererStarted != null && getContext() instanceof Activity) { + ((Activity) getContext()).runOnUiThread(new Runnable() { + @Override + public void run() { + onRendererStarted.onRenderStart(legacyApplication, JmeSurfaceView.this); + } + }); + } + } + + oglesContext = (OGLESContext) legacyApplication.getContext(); + oglesContext.setSystemListener(this); + glSurfaceView = oglesContext.createView(getContext()); + JmeAndroidSystem.setView(this); + + glSurfaceView.setLayoutParams(new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + if (GameState.getLegacyApplication() != null || delayMillis == 0) { + addGlSurfaceView(); + } else { + handler.postDelayed(new Runnable() { + @Override + public void run() { + addGlSurfaceView(); + } + }, delayMillis); + } + } + + private static boolean isClassPresent(String className) { + try { + Class.forName(className, false, JmeSurfaceView.class.getClassLoader()); + return true; + } catch (Throwable ignored) { + return false; + } + } + + protected void addGlSurfaceView() { + if (glSurfaceView == null) { + return; + } + post(new Runnable() { + @Override + public void run() { + if (glSurfaceView.getParent() instanceof ViewGroup) { + ((ViewGroup) glSurfaceView.getParent()).removeView(glSurfaceView); + } + addView(glSurfaceView); + if (onLayoutDrawn != null && getContext() instanceof Activity) { + ((Activity) getContext()).runOnUiThread(new Runnable() { + @Override + public void run() { + onLayoutDrawn.onLayoutDrawn(legacyApplication, JmeSurfaceView.this); + } + }); + } + } + }); + } + + private void removeGLSurfaceView() { + if (glSurfaceView != null) { + post(new Runnable() { + @Override + public void run() { + removeView(glSurfaceView); + } + }); + } + } + + @Override + public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { + if (!bindAppState) { + return; + } + if (event == Lifecycle.Event.ON_RESUME) { + gainFocus(); + } else if (event == Lifecycle.Event.ON_PAUSE) { + loseFocus(); + } else if (event == Lifecycle.Event.ON_DESTROY) { + if (destructionPolicy == DestructionPolicy.DESTROY_WHEN_FINISH) { + destroy(); + } + } + } + + @Override + public void initialize() { + if (legacyApplication != null) { + legacyApplication.initialize(); + } + } + + @Override + public void reshape(int width, int height) { + if (legacyApplication != null) { + legacyApplication.reshape(width, height); + } + } + + @Override + public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { + if (legacyApplication != null) { + legacyApplication.reshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight); + } + } + + @Override + public void rescale(float x, float y) { + if (legacyApplication != null) { + legacyApplication.rescale(x, y); + } + } + + @Override + public void update() { + if (legacyApplication != null) { + legacyApplication.update(); + } + if (!GameState.isFirstUpdatePassed()) { + GameState.setFirstUpdatePassed(true); + if (onRendererCompleted != null && getContext() instanceof Activity) { + ((Activity) getContext()).runOnUiThread(new Runnable() { + @Override + public void run() { + onRendererCompleted.onRenderCompletion(legacyApplication, appSettings); + } + }); + } + } + } + + @Override + public void requestClose(boolean esc) { + if (legacyApplication != null) { + legacyApplication.requestClose(esc); + } + if (esc && showEscExitPrompt && getContext() instanceof Activity) { + ((Activity) getContext()).runOnUiThread(new Runnable() { + @Override + public void run() { + new AlertDialog.Builder(getContext()) + .setTitle("Do you want to exit?") + .setMessage("Use your home key to bring this app into the background or exit to terminate it.") + .setPositiveButton("Yes", JmeSurfaceView.this) + .setNegativeButton("No", JmeSurfaceView.this) + .create() + .show(); + } + }); + } + } + + @Override + public void gainFocus() { + logger.fine("gainFocus"); + if (glSurfaceView != null) { + glSurfaceView.onResume(); + } + + if (legacyApplication != null) { + AudioRenderer audioRenderer = legacyApplication.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.resumeAll(); + } + + JoyInput joyInput = legacyApplication.getContext() != null + ? legacyApplication.getContext().getJoyInput() : null; + if (joyInput instanceof AndroidJoyInput) { + ((AndroidJoyInput) joyInput).resumeJoysticks(); + } else if (joyInput instanceof AndroidSensorJoyInput) { + ((AndroidSensorJoyInput) joyInput).resumeSensors(); + } + + legacyApplication.gainFocus(); + } + isGLThreadPaused = false; + } + + @Override + public void loseFocus() { + logger.fine("loseFocus"); + if (legacyApplication != null) { + legacyApplication.loseFocus(); + } + + if (glSurfaceView != null) { + glSurfaceView.onPause(); + } + + if (legacyApplication != null) { + AudioRenderer audioRenderer = legacyApplication.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.pauseAll(); + } + + JoyInput joyInput = legacyApplication.getContext() != null + ? legacyApplication.getContext().getJoyInput() : null; + if (joyInput instanceof AndroidJoyInput) { + ((AndroidJoyInput) joyInput).pauseJoysticks(); + } else if (joyInput instanceof AndroidSensorJoyInput) { + ((AndroidSensorJoyInput) joyInput).pauseSensors(); + } + } + isGLThreadPaused = true; + } + + @Override + public void destroy() { + logger.fine("destroy"); + if (legacyApplication != null) { + legacyApplication.stop(false); + } + + removeGLSurfaceView(); + + if (destructionPolicy == DestructionPolicy.KEEP_WHEN_FINISH) { + GameState.setLegacyApplication(legacyApplication); + } else { + GameState.setLegacyApplication(null); + GameState.setFirstUpdatePassed(false); + } + + legacyApplication = null; + glSurfaceView = null; + oglesContext = null; + JmeAndroidSystem.setView(null); + } + + @Override + public void handleError(final String errorMsg, final Throwable throwable) { + String stackTrace = ""; + String title = "Error"; + + if (throwable != null) { + StringWriter writer = new StringWriter(100); + throwable.printStackTrace(new PrintWriter(writer)); + stackTrace = writer.toString(); + title = throwable.toString(); + } + + final String finalTitle = title; + final String finalMessage = (errorMsg != null ? errorMsg : "Uncaught Exception") + + "\n" + stackTrace; + this.crashLog = finalMessage; + + logger.log(Level.SEVERE, finalMessage); + + if (onExceptionThrown != null) { + onExceptionThrown.onExceptionThrown(throwable); + } + + if (showErrorDialog && getContext() instanceof Activity) { + ((Activity) getContext()).runOnUiThread(new Runnable() { + @Override + public void run() { + new AlertDialog.Builder(getContext()) + .setTitle(finalTitle) + .setMessage(finalMessage) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + destroy(); + if (getContext() instanceof Activity) { + ((Activity) getContext()).finish(); + } + } + }) + .setNegativeButton("Kill", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + android.os.Process.killProcess(android.os.Process.myPid()); + } + }) + .create() + .show(); + } + }); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + if (exitOnEscPressed) { + destroy(); + if (getContext() instanceof Activity) { + ((Activity) getContext()).finish(); + } + } + } + } + + public void bindAppStateToActivityLifeCycle(final boolean condition) { + this.bindAppState = condition; + } + + public DestructionPolicy getDestructionPolicy() { + return destructionPolicy; + } + + public void setDestructionPolicy(DestructionPolicy destructionPolicy) { + this.destructionPolicy = destructionPolicy; + } + + public boolean isAppStateBoundToActivityLifeCycle() { + return bindAppState; + } + + public boolean isShowEscExitPrompt() { + return showEscExitPrompt; + } + + public void setShowEscExitPrompt(boolean showEscExitPrompt) { + this.showEscExitPrompt = showEscExitPrompt; + } + + public boolean isExitOnEscPressed() { + return exitOnEscPressed; + } + + public void setExitOnEscPressed(boolean exitOnEscPressed) { + this.exitOnEscPressed = exitOnEscPressed; + } + + public LegacyApplication getLegacyApplication() { + return legacyApplication; + } + + public void setLegacyApplication(@NonNull LegacyApplication legacyApplication) { + this.legacyApplication = legacyApplication; + } + + public AppSettings getAppSettings() { + return appSettings; + } + + public void setAppSettings(@NonNull AppSettings appSettings) { + this.appSettings = appSettings; + } + + public int getEglBitsPerPixel() { + return eglBitsPerPixel; + } + + public void setEglBitsPerPixel(int eglBitsPerPixel) { + this.eglBitsPerPixel = eglBitsPerPixel; + } + + public int getEglAlphaBits() { + return eglAlphaBits; + } + + public void setEglAlphaBits(int eglAlphaBits) { + this.eglAlphaBits = eglAlphaBits; + } + + public int getEglDepthBits() { + return eglDepthBits; + } + + public void setEglDepthBits(int eglDepthBits) { + this.eglDepthBits = eglDepthBits; + } + + public int getEglSamples() { + return eglSamples; + } + + public void setEglSamples(int eglSamples) { + this.eglSamples = eglSamples; + } + + public int getEglStencilBits() { + return eglStencilBits; + } + + public void setEglStencilBits(int eglStencilBits) { + this.eglStencilBits = eglStencilBits; + } + + public int getFrameRate() { + return frameRate; + } + + public void setFrameRate(int frameRate) { + this.frameRate = frameRate; + } + + public String getAudioRendererType() { + return audioRendererType; + } + + public void setAudioRendererType(String audioRendererType) { + this.audioRendererType = audioRendererType; + } + + public boolean isEmulateKeyBoard() { + return emulateKeyBoard; + } + + public void setEmulateKeyBoard(boolean emulateKeyBoard) { + this.emulateKeyBoard = emulateKeyBoard; + } + + public boolean isEmulateMouse() { + return emulateMouse; + } + + public void setEmulateMouse(boolean emulateMouse) { + this.emulateMouse = emulateMouse; + } + + public boolean isUseJoyStickEvents() { + return useJoyStickEvents; + } + + public void setUseJoyStickEvents(boolean useJoyStickEvents) { + this.useJoyStickEvents = useJoyStickEvents; + } + + public boolean isGLThreadPaused() { + return isGLThreadPaused; + } + + protected void setGLThreadPaused(boolean glThreadPaused) { + this.isGLThreadPaused = glThreadPaused; + } + + public void setOnRendererCompleted(OnRendererCompleted onRendererCompleted) { + this.onRendererCompleted = onRendererCompleted; + } + + public void setOnExceptionThrown(OnExceptionThrown onExceptionThrown) { + this.onExceptionThrown = onExceptionThrown; + } + + public void setOnRendererStarted(OnRendererStarted onRendererStarted) { + this.onRendererStarted = onRendererStarted; + } + + public void setOnLayoutDrawn(OnLayoutDrawn onLayoutDrawn) { + this.onLayoutDrawn = onLayoutDrawn; + } + + public String getGlEsVersion() { + return glEsVersion; + } + + public boolean isShowErrorDialog() { + return showErrorDialog; + } + + public void setShowErrorDialog(boolean showErrorDialog) { + this.showErrorDialog = showErrorDialog; + } +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java new file mode 100644 index 0000000000..8c36aa09b7 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2022 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.view.surfaceview; + +/** + * An interface designed to listen for exceptions and fire an event when an exception is thrown. + * + * @author pavl_g. + * @see JmeSurfaceView#setOnExceptionThrown(OnExceptionThrown) + * @deprecated Use {@link com.jme3.app.AndroidHarnessFragment} instead. + */ +@Deprecated +public interface OnExceptionThrown { + /** + * Listens for a thrown exception or a thrown error. + * + * @param e the exception or the error that is throwable. + */ + void onExceptionThrown(Throwable e); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java new file mode 100644 index 0000000000..5626d323f8 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2022 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.view.surfaceview; + +import android.view.View; +import com.jme3.app.LegacyApplication; + +/** + * An interface used for dispatching an event when the layout holding the {@link android.opengl.GLSurfaceView} is drawn, + * the event is dispatched on the user activity context thread. + * + * @author pavl_g. + * @deprecated Use {@link com.jme3.app.AndroidHarnessFragment} instead. + */ +@Deprecated +public interface OnLayoutDrawn { + /** + * Dispatched when the layout is drawn on the screen. + * + * @param legacyApplication the application instance. + * @param layout the current layout. + */ + void onLayoutDrawn(LegacyApplication legacyApplication, View layout); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java new file mode 100644 index 0000000000..c7427e9ee1 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2022 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.view.surfaceview; + +import com.jme3.app.LegacyApplication; +import com.jme3.system.AppSettings; + +/** + * An interface used for invoking an event when the user delay finishes, on the first update of the game. + * + * @author pavl_g. + * @see JmeSurfaceView#setOnRendererCompleted(OnRendererCompleted) + * @deprecated Use {@link com.jme3.app.AndroidHarnessFragment} instead. + */ +@Deprecated +public interface OnRendererCompleted { + /** + * Invoked when the user delay finishes, on the first update of the game, the event is dispatched on the + * enclosing Activity context thread. + * + * @param application the current jme game instance. + * @param appSettings the current window settings of the running jme game. + * @see JmeSurfaceView#update() + */ + void onRenderCompletion(LegacyApplication application, AppSettings appSettings); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java new file mode 100644 index 0000000000..21de850713 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2022 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.view.surfaceview; + +import android.view.View; +import com.jme3.app.LegacyApplication; + +/** + * An interface used for invoking an event when the application is started explicitly from {@link JmeSurfaceView#startRenderer(int)}. + * NB : This listener must be utilized before using {@link JmeSurfaceView#startRenderer(int)}, ie : it would be ignored if you try to use {@link JmeSurfaceView#setOnRendererStarted(OnRendererStarted)} after + * {@link JmeSurfaceView#startRenderer(int)}. + * + * @author pavl_g. + * @see JmeSurfaceView#setOnRendererStarted(OnRendererStarted) + * @deprecated Use {@link com.jme3.app.AndroidHarnessFragment} instead. + */ +@Deprecated +public interface OnRendererStarted { + /** + * Invoked when the game application is started by the {@link LegacyApplication#start()}, the event is dispatched on the + * holder Activity context thread. + * + * @param application the game instance. + * @param layout the enclosing layout. + * @see JmeSurfaceView#startRenderer(int) + */ + void onRenderStart(LegacyApplication application, View layout); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java new file mode 100644 index 0000000000..70c6773baa --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2022 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. + */ + +/** + * Holds {@link com.jme3.view.surfaceview.JmeSurfaceView} with some lifecycle interfaces. + *

+ * This package provides the following : + *

+ * {@link com.jme3.view.surfaceview.JmeSurfaceView} : An OpenGL android view wrapper for rendering, updating and destroying a jMonkeyEngine game. + * {@link com.jme3.view.surfaceview.OnRendererStarted} : Provides a method to be invoked when a jMonkeyEngine application starts. + * {@link com.jme3.view.surfaceview.OnLayoutDrawn} : Provides a method to be invoked when the GLSurfaceView draws the content, before OnRendererCompleted. + * {@link com.jme3.view.surfaceview.OnRendererCompleted} : Provides a method to be invoked on the first update of the game. + * {@link com.jme3.view.surfaceview.OnExceptionThrown} : Provides a method to be invoked when an exception is thrown. + *

+ * Deprecated: use {@link com.jme3.app.AndroidHarnessFragment} instead. + */ +@Deprecated +package com.jme3.view.surfaceview; \ No newline at end of file