Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -182,17 +182,56 @@ task configureAndroidNDK {
ndkBuildFile = "ndk-build.cmd"
}

// ndkPath is defined in the root project gradle.properties file
String ndkBuildPath = ndkPath + File.separator + ndkBuildFile
//Use the environment variable for the NDK location if defined
if (System.env.ANDROID_NDK != null) {
ndkBuildPath = System.env.ANDROID_NDK + File.separator + ndkBuildFile
def ndkCandidates = []
if (System.env.ANDROID_NDK) {
ndkCandidates << file(System.env.ANDROID_NDK)
}
if (System.env.ANDROID_NDK_HOME) {
ndkCandidates << file(System.env.ANDROID_NDK_HOME)
}
if (project.hasProperty('ndkPath') && ndkPath) {
ndkCandidates << file(ndkPath)
}

if (new File(ndkBuildPath).exists()) {
def localProperties = file('local.properties')
if (localProperties.isFile()) {
Properties properties = new Properties()
localProperties.withInputStream { properties.load(it) }
if (properties.getProperty('ndk.dir')) {
ndkCandidates << file(properties.getProperty('ndk.dir'))
}
if (properties.getProperty('sdk.dir')) {
ndkCandidates.addAll(findAndroidNdkDirs(file(properties.getProperty('sdk.dir'))))
}
}

if (System.env.ANDROID_HOME) {
ndkCandidates.addAll(findAndroidNdkDirs(file(System.env.ANDROID_HOME)))
}
if (System.env.ANDROID_SDK_ROOT) {
ndkCandidates.addAll(findAndroidNdkDirs(file(System.env.ANDROID_SDK_ROOT)))
}
ndkCandidates.addAll(findAndroidNdkDirs(file("${System.properties['user.home']}/Android/Sdk")))
Comment on lines +196 to +214
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic for finding the NDK by searching through various SDK directories can be made more efficient and readable. It may perform redundant searches if multiple environment variables or properties point to the same SDK location.

A better approach would be to first collect all potential SDK directory paths, deduplicate them, and then iterate over the unique paths to find NDKs. This avoids unnecessary file system operations and makes the logic clearer. A similar pattern is already used in jme3-android-examples/build.gradle for locating adb.

    def sdkDirs = []
    def localProperties = file('local.properties')
    if (localProperties.isFile()) {
        Properties properties = new Properties()
        localProperties.withInputStream { properties.load(it) }
        if (properties.getProperty('ndk.dir')) {
            ndkCandidates << file(properties.getProperty('ndk.dir'))
        }
        if (properties.getProperty('sdk.dir')) {
            sdkDirs << file(properties.getProperty('sdk.dir'))
        }
    }

    if (System.env.ANDROID_HOME) {
        sdkDirs << file(System.env.ANDROID_HOME)
    }
    if (System.env.ANDROID_SDK_ROOT) {
        sdkDirs << file(System.env.ANDROID_SDK_ROOT)
    }
    sdkDirs << file("${System.properties['user.home']}/Android/Sdk")

    sdkDirs.unique { it.canonicalPath }.each { sdkDir ->
        ndkCandidates.addAll(findAndroidNdkDirs(sdkDir))
    }


def ndkBuildPath = ndkCandidates.collect { new File(it, ndkBuildFile) }.find { it.isFile() }
if (ndkBuildPath != null) {
ndkExists = true
ndkCommandPath = ndkBuildPath
ndkCommandPath = ndkBuildPath.absolutePath
}
}

def findAndroidNdkDirs(File sdkDir) {
def ndkDirs = []
def nestedSdkDir = new File(sdkDir, 'Sdk')
if (nestedSdkDir.isDirectory()) {
ndkDirs.addAll(findAndroidNdkDirs(nestedSdkDir))
}
def sideBySideDir = new File(sdkDir, 'ndk')
if (sideBySideDir.isDirectory()) {
ndkDirs.addAll(sideBySideDir.listFiles()?.findAll { it.isDirectory() }?.sort { a, b -> b.name <=> a.name } ?: [])
}
ndkDirs << new File(sdkDir, 'ndk-bundle')
return ndkDirs
}

gradle.rootProject.ext.set("usePrebuildNatives", buildNativeProjects!="true");
Expand Down
2 changes: 1 addition & 1 deletion common-android-app.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ version = jmeFullVersion
repositories {
mavenCentral()
maven {
url "http://nifty-gui.sourceforge.net/nifty-maven-repo"
url "https://nifty-gui.sourceforge.net/nifty-maven-repo"
}
}
4 changes: 3 additions & 1 deletion gradle/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ reporting {

def jacocoSubprojects = subprojects.findAll { p ->
(p.file("src/main/java").exists() || p.file("src/main/groovy").exists()) &&
(p.file("src/test/java").exists() || p.file("src/test/groovy").exists())
(p.file("src/test/java").exists() || p.file("src/test/groovy").exists()) &&
p.tasks.findByName('test') &&
p.tasks.findByName('jacocoTestReport')
}

dependencies {
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ spotbugs = "4.9.8"
[libraries]

androidx-annotation = "androidx.annotation:annotation:1.7.1"
androidx-fragment = "androidx.fragment:fragment:1.8.9"
androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.7.0"
android-build-gradle = "com.android.tools.build:gradle:9.1.0"
android-support-appcompat = "com.android.support:appcompat-v7:28.0.0"
androidx-test-runner = "androidx.test:runner:1.7.0"
gradle-git = "org.ajoberstar:gradle-git:1.2.0"
groovy-test = "org.apache.groovy:groovy-test:4.0.31"
gson = "com.google.code.gson:gson:2.13.2"
Expand All @@ -23,6 +25,7 @@ jinput = "net.java.jinput:jinput:2.0.9"
jna = "net.java.dev.jna:jna:5.18.1"
jnaerator-runtime = "com.nativelibs4java:jnaerator-runtime:0.12"
junit-bom = "org.junit:junit-bom:5.13.4"
junit4 = "junit:junit:4.13.2"
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
lwjgl2 = "org.jmonkeyengine:lwjgl:2.9.5"
Expand Down
96 changes: 91 additions & 5 deletions jme3-android-examples/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
apply plugin: 'com.android.application'

def examplesJar = project(':jme3-examples').tasks.named('jar')

android {
namespace "org.jmonkeyengine.jme3androidexamples"
compileSdk 34
Expand All @@ -16,12 +18,13 @@ android {
targetSdk 34 // Android 14
versionCode 1
versionName "1.0" // TODO: from settings.gradle
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

Expand All @@ -40,23 +43,106 @@ android {
srcDir '../jme3-testdata/src/main/resources'
srcDir '../jme3-examples/src/main/resources'
}
jniLibs {
srcDir '../build/native/openalsoft'
srcDir '../build/native/decode'
srcDir '../build/native/allocator'
}
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation libs.junit4
implementation libs.androidx.appcompat

testImplementation platform(libs.junit.bom)
testImplementation libs.junit.jupiter
testRuntimeOnly libs.junit.platform.launcher
androidTestImplementation libs.junit4
androidTestImplementation libs.androidx.test.runner
implementation libs.androidx.fragment
implementation project(':jme3-core')
implementation project(':jme3-android')
implementation project(':jme3-android-native')
implementation project(':jme3-effects')
implementation project(':jme3-jbullet')
implementation project(':jme3-jogg')
implementation project(':jme3-networking')
implementation project(':jme3-niftygui')
implementation project(':jme3-plugins')
implementation project(':jme3-plugins-json')
implementation project(':jme3-plugins-json-gson')
implementation project(':jme3-terrain')
implementation fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*'])
implementation files(examplesJar.flatMap { it.archiveFile })
}

tasks.named('preBuild') {
dependsOn examplesJar
if (buildNativeProjects == "true") {
dependsOn ':jme3-android-native:updatePreCompiledOpenAlSoftLibs'
dependsOn ':jme3-android-native:updatePreCompiledLibs'
dependsOn ':jme3-android-native:updatePreCompiledLibsBufferAllocator'
} else if (skipPrebuildLibraries != "true") {
dependsOn ':jme3-android-native:copyPreCompiledOpenAlSoftLibs'
dependsOn ':jme3-android-native:copyPreCompiledLibs'
dependsOn ':jme3-android-native:copyPreCompiledLibsBufferAllocator'
}
}

tasks.register('installAndroidExamples', Exec) {
group = 'application'
description = 'Install the Android examples selector on a connected emulator or device.'

dependsOn tasks.named('assembleDebug')

doFirst {
def adbExecutable = findAdbExecutable()
if (adbExecutable == null) {
throw new GradleException("ADB not found. Set -Pandroid.sdk.path, ANDROID_HOME, or ANDROID_SDK_ROOT.")
}
def apkFile = layout.buildDirectory.file('outputs/apk/debug/jme3-android-examples-debug.apk').get().asFile
if (!apkFile.isFile()) {
throw new GradleException("Debug APK not found at ${apkFile}.")
}
executable adbExecutable.absolutePath
args 'install', '-r', apkFile.absolutePath
}
}

tasks.register('runAndroidExamples', Exec) {
group = 'application'
description = 'Install and launch the Android examples selector on a connected emulator or device.'

dependsOn tasks.named('installAndroidExamples')

doFirst {
def adbExecutable = findAdbExecutable()
if (adbExecutable == null) {
throw new GradleException("ADB not found. Set -Pandroid.sdk.path, ANDROID_HOME, or ANDROID_SDK_ROOT.")
}
executable adbExecutable.absolutePath
}

args 'shell', 'am', 'start',
'-n', 'org.jmonkeyengine.jme3androidexamples/.MainActivity'
}

def findAdbExecutable() {
def adbName = System.properties['os.name'].toLowerCase().contains('windows') ? 'adb.exe' : 'adb'
def sdkDirs = []
if (project.findProperty('android.sdk.path')) {
sdkDirs << file(project.findProperty('android.sdk.path'))
}
if (System.env.ANDROID_HOME) {
sdkDirs << file(System.env.ANDROID_HOME)
}
if (System.env.ANDROID_SDK_ROOT) {
sdkDirs << file(System.env.ANDROID_SDK_ROOT)
}
sdkDirs << file("${System.properties['user.home']}/Android/Sdk")

def expandedSdkDirs = sdkDirs.collectMany { sdkDir ->
[sdkDir, new File(sdkDir, 'Sdk')]
}.unique { it.absolutePath }

return expandedSdkDirs.collect { new File(new File(it, 'platform-tools'), adbName) }.find { it.isFile() }
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package org.jmonkeyengine.jme3androidexamples;

import android.app.Application;
import android.test.ApplicationTestCase;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
public class ApplicationTest {
@Test
public void testApplicationPackage() {
String packageName = InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.getPackageName();
assertEquals("org.jmonkeyengine.jme3androidexamples", packageName);
}
}
}
6 changes: 4 additions & 2 deletions jme3-android-examples/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
Expand All @@ -25,9 +26,9 @@
</activity>
</application>

<!-- Tell the system that you need ES 2.0. -->
<!-- Tell the system that you need ES 3.0. -->
<uses-feature
android:glEsVersion="0x00020000"
android:glEsVersion="0x00030000"
android:required="true"/>

<!-- Tell the system that you need distinct touches (for the zoom gesture). -->
Expand All @@ -41,6 +42,7 @@
android:normalScreens="true"
android:smallScreens="true"/>

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,23 @@

import android.os.Bundle;
import com.jme3.app.AndroidHarnessFragment;
import com.jme3.app.LegacyApplication;
import com.jme3.system.AppSettings;
import java.util.logging.Level;
import java.util.logging.LogManager;

/**
* A placeholder fragment containing a jME GLSurfaceView.
*/
public class JmeFragment extends AndroidHarnessFragment {
private String appClass;
private boolean joystickEventsEnabled;
private boolean keyEventsEnabled = true;
private boolean mouseEventsEnabled = true;

public JmeFragment() {
// Set the desired EGL configuration
eglBitsPerPixel = 24;
eglAlphaBits = 0;
eglDepthBits = 16;
eglSamples = 0;
eglStencilBits = 0;

// Set the maximum framerate
// (default = -1 for unlimited)
frameRate = -1;

// Set the maximum resolution dimension
// (the smaller side, height or width, is set automatically
// to maintain the original device screen aspect ratio)
// (default = -1 to match device screen resolution)
maxResolutionDimension = -1;

/*
Skip these settings and use the settings stored in the Bundle retrieved during onCreate.

// Set main project class (fully qualified path)
appClass = "";

// Set input configuration settings
joystickEventsEnabled = false;
keyEventsEnabled = true;
mouseEventsEnabled = true;
*/

// Set application exit settings
finishOnAppStop = true;
handleExitHook = true;
exitDialogTitle = "Do you want to exit?";
exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";

// Set splash screen resource id, if used
// (default = 0, no splash screen)
// For example, if the image file name is "splash"...
// splashPicID = R.drawable.splash;
splashPicID = 0;
// splashPicID = R.drawable.android_splash;

// Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info)
LogManager.getLogManager().getLogger("").setLevel(Level.INFO);

}

@Override
Expand All @@ -82,4 +45,25 @@ public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
}

@Override
protected LegacyApplication createApplication() throws Exception {
Class<?> clazz = Class.forName(appClass);
return (LegacyApplication) clazz.getDeclaredConstructor().newInstance();
}

@Override
protected void configureSettings(AppSettings settings) {
settings.setEmulateMouse(mouseEventsEnabled);
settings.setUseJoysticks(joystickEventsEnabled);
settings.setEmulateKeyboard(keyEventsEnabled);

settings.setBitsPerPixel(24);
settings.setAlphaBits(0);
settings.setGammaCorrection(true);
settings.setDepthBits(16);
settings.setSamples(4);
settings.setStencilBits(0);
settings.setFrameRate(-1);
}
}
Loading
Loading