diff options
Diffstat (limited to 'platform/android/java')
23 files changed, 1403 insertions, 314 deletions
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 4d2eb1ef65..cc480d1c84 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -6,9 +6,6 @@ android:versionName="1.0" android:installLocation="auto" > - <!-- Adding custom text to the manifest is fine, but do it outside the custom USER and APPLICATION BEGIN/END comments, --> - <!-- as that gets rewritten. --> - <supports-screens android:smallScreens="true" android:normalScreens="true" @@ -19,14 +16,11 @@ android:glEsVersion="0x00020000" android:required="true" /> -<!-- Custom user permissions XML added by add-ons. It's recommended to add them from the export preset, though. --> -<!--CHUNK_USER_PERMISSIONS_BEGIN--> -<!--CHUNK_USER_PERMISSIONS_END--> - - <!-- Any tag in this line after android:icon will be erased when doing custom builds. --> - <!-- If you want to add tags manually, do before it. --> - <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. --> - <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@mipmap/icon" > + <application + android:label="@string/godot_project_name_string" + android:allowBackup="false" + tools:ignore="GoogleAppIndexingWarning" + android:icon="@mipmap/icon" > <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> <!-- Do these changes in the export preset. Adding new ones is fine. --> @@ -36,6 +30,11 @@ android:name="xr_mode_metadata_name" android:value="xr_mode_metadata_value" /> + <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. --> + <meta-data + android:name="custom_template_plugins" + android:value="custom_template_plugins_value"/> + <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" @@ -52,10 +51,6 @@ </intent-filter> </activity> -<!-- Custom application XML added by add-ons. --> -<!--CHUNK_APPLICATION_BEGIN--> -<!--CHUNK_APPLICATION_END--> - </application> </manifest> diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 2e4f2ffab0..5e37f538e9 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -1,7 +1,4 @@ // Gradle build config for Godot Engine's Android port. -// -// Do not remove/modify comments ending with BEGIN/END, they are used to inject -// addon-specific configuration. apply from: 'config.gradle' buildscript { @@ -10,13 +7,10 @@ buildscript { repositories { google() jcenter() -//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN -//CHUNK_BUILDSCRIPT_REPOSITORIES_END } dependencies { classpath libraries.androidGradlePlugin -//CHUNK_BUILDSCRIPT_DEPENDENCIES_BEGIN -//CHUNK_BUILDSCRIPT_DEPENDENCIES_END + classpath libraries.kotlinGradlePlugin } } @@ -27,24 +21,35 @@ allprojects { mavenCentral() google() jcenter() -//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN -//CHUNK_ALLPROJECTS_REPOSITORIES_END } } dependencies { implementation libraries.supportCoreUtils + implementation libraries.kotlinStdLib + implementation libraries.v4Support if (rootProject.findProject(":lib")) { implementation project(":lib") + } else if (rootProject.findProject(":godot:lib")) { + implementation project(":godot:lib") } else { // Custom build mode. In this scenario this project is the only one around and the Godot // library is available through the pre-generated godot-lib.*.aar android archive files. debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar']) releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar']) } -//CHUNK_DEPENDENCIES_BEGIN -//CHUNK_DEPENDENCIES_END + + // Godot prebuilt plugins + implementation fileTree(dir: 'libs/plugins', include: ["GodotPayment*.aar"]) + + // Godot user plugins dependencies + String pluginsDir = getGodotPluginsDirectory() + String[] pluginsBinaries = getGodotPluginsBinaries() + if (pluginsDir != null && !pluginsDir.isEmpty() && + pluginsBinaries != null && pluginsBinaries.size() > 0) { + implementation fileTree(dir: pluginsDir, include: pluginsBinaries) + } } android { @@ -56,8 +61,6 @@ android { applicationId getExportPackageName() minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk -//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN -//CHUNK_ANDROID_DEFAULTCONFIG_END } lintOptions { @@ -79,37 +82,13 @@ android { sourceSets { main { manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = [ - 'src' -//DIR_SRC_BEGIN -//DIR_SRC_END - ] - res.srcDirs = [ - 'res' -//DIR_RES_BEGIN -//DIR_RES_END - ] - aidl.srcDirs = [ - 'aidl' -//DIR_AIDL_BEGIN -//DIR_AIDL_END - ] - assets.srcDirs = [ - 'assets' -//DIR_ASSETS_BEGIN -//DIR_ASSETS_END - ] + java.srcDirs = ['src'] + res.srcDirs = ['res'] + aidl.srcDirs = ['aidl'] + assets.srcDirs = ['assets'] } - debug.jniLibs.srcDirs = [ - 'libs/debug' -//DIR_JNI_DEBUG_BEGIN -//DIR_JNI_DEBUG_END - ] - release.jniLibs.srcDirs = [ - 'libs/release' -//DIR_JNI_RELEASE_BEGIN -//DIR_JNI_RELEASE_END - ] + debug.jniLibs.srcDirs = ['libs/debug'] + release.jniLibs.srcDirs = ['libs/release'] } applicationVariants.all { variant -> @@ -118,6 +97,3 @@ android { } } } - -//CHUNK_GLOBAL_BEGIN -//CHUNK_GLOBAL_END diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 5550d3099d..eaaefcbccb 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,24 +1,66 @@ ext.versions = [ - androidGradlePlugin : '3.4.2', - compileSdk : 28, - minSdk : 18, - targetSdk : 28, - buildTools : '28.0.3', - supportCoreUtils : '28.0.0' + androidGradlePlugin: '3.6.0', + compileSdk : 29, + minSdk : 18, + targetSdk : 29, + buildTools : '29.0.1', + supportCoreUtils : '28.0.0', + kotlinVersion : '1.3.61', + v4Support : '28.0.0' ] ext.libraries = [ - androidGradlePlugin : "com.android.tools.build:gradle:$versions.androidGradlePlugin", - supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils" + androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", + supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils", + kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", + kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion", + v4Support : "com.android.support:support-v4:$versions.v4Support" ] ext.getExportPackageName = { -> - // Retrieve the app id from the project property set by the Godot build command. - String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : "" - // Check if the app id is valid, otherwise use the default. - if (appId == null || appId.isEmpty()) { - appId = "com.godot.game" - } - return appId + // Retrieve the app id from the project property set by the Godot build command. + String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : "" + // Check if the app id is valid, otherwise use the default. + if (appId == null || appId.isEmpty()) { + appId = "com.godot.game" + } + return appId +} + +/** + * Parse the project properties for the 'custom_template_plugins' property and return + * their binaries for inclusion in the build dependencies. + * + * The listed plugins must have their binaries in the project plugins directory. + */ +ext.getGodotPluginsBinaries = { -> + String[] binDeps = [] + + // Retrieve the list of enabled plugins. + if (project.hasProperty("custom_template_plugins")) { + String pluginsList = project.property("custom_template_plugins") + if (pluginsList != null && !pluginsList.trim().isEmpty()) { + for (String plugin : pluginsList.split(",")) { + binDeps += plugin.trim() + "*.aar" + } + } + } + + return binDeps +} + +/** + * Parse the project properties for the 'custom_template_plugins_dir' property and return + * its value. + * + * The returned value is the directory containing user plugins. + */ +ext.getGodotPluginsDirectory = { -> + // The plugins directory is provided by the 'custom_template_plugins_dir' property. + String pluginsDir = project.hasProperty("custom_template_plugins_dir") + ? project.property("custom_template_plugins_dir") + : "" + + return pluginsDir } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 2052017888..976a5bda99 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -9,6 +9,7 @@ buildscript { } dependencies { classpath libraries.androidGradlePlugin + classpath libraries.kotlinGradlePlugin } } @@ -24,7 +25,7 @@ ext { sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] - supportedTargets = ['release':"release", 'debug':"release_debug"] + supportedTargets = ['release': "release", 'debug': "release_debug"] // Used by gradle to specify which architecture to build for by default when running `./gradlew build`. // This command is usually used by Android Studio. @@ -64,10 +65,10 @@ task copyReleaseBinaryToBin(type: Copy) { } /** - * Copy the Godot android library archive debug file into the app debug libs directory. + * Copy the Godot android library archive debug file into the app module debug libs directory. * Depends on the library build task to ensure the AAR file is generated prior to copying. */ -task copyDebugAAR(type: Copy) { +task copyDebugAARToAppModule(type: Copy) { dependsOn ':lib:assembleDebug' from('lib/build/outputs/aar') into('app/libs/debug') @@ -75,16 +76,45 @@ task copyDebugAAR(type: Copy) { } /** - * Copy the Godot android library archive release file into the app release libs directory. + * Copy the Godot android library archive debug file into the root bin directory. * Depends on the library build task to ensure the AAR file is generated prior to copying. */ -task copyReleaseAAR(type: Copy) { +task copyDebugAARToBin(type: Copy) { + dependsOn ':lib:assembleDebug' + from('lib/build/outputs/aar') + into(binDir) + include('godot-lib.debug.aar') +} + +/** + * Copy the Godot android library archive release file into the app module release libs directory. + * Depends on the library build task to ensure the AAR file is generated prior to copying. + */ +task copyReleaseAARToAppModule(type: Copy) { dependsOn ':lib:assembleRelease' from('lib/build/outputs/aar') into('app/libs/release') include('godot-lib.release.aar') } +task copyGodotPaymentPluginToAppModule(type: Copy) { + dependsOn ':plugins:godotpayment:assembleRelease' + from('plugins/godotpayment/build/outputs/aar') + into('app/libs/plugins') + include('GodotPayment.release.aar') +} + +/** + * Copy the Godot android library archive release file into the root bin directory. + * Depends on the library build task to ensure the AAR file is generated prior to copying. + */ +task copyReleaseAARToBin(type: Copy) { + dependsOn ':lib:assembleRelease' + from('lib/build/outputs/aar') + into(binDir) + include('godot-lib.release.aar') +} + /** * Generate Godot custom build template by zipping the source files from the app directory, as well * as the AAR files generated by 'copyDebugAAR' and 'copyReleaseAAR'. @@ -95,7 +125,7 @@ task zipCustomBuild(type: Zip) { doFirst { logger.lifecycle("Generating Godot custom build template") } - from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties','gradlew', 'gradlew.bat', 'gradle/**'])) + from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties', 'gradlew', 'gradlew.bat', 'gradle/**'])) include '**/*' archiveName 'android_source.zip' destinationDir(file(binDir)) @@ -110,19 +140,24 @@ task generateGodotTemplates(type: GradleBuild) { startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) } - tasks = [] + tasks = ["copyGodotPaymentPluginToAppModule"] // Only build the apks and aar files for which we have native shared libraries. for (String target : supportedTargets.keySet()) { File targetLibs = new File("lib/libs/" + target) - if (targetLibs != null && targetLibs.isDirectory()) { - File[] targetLibsContents = targetLibs.listFiles() - if (targetLibsContents != null && targetLibsContents.length > 0) { - // Copy the generated aar library files to the custom build directory. - tasks += "copy" + target.capitalize() + "AAR" - // Copy the prebuilt binary templates to the bin directory. - tasks += "copy" + target.capitalize() + "BinaryToBin" - } + if (targetLibs != null + && targetLibs.isDirectory() + && targetLibs.listFiles() != null + && targetLibs.listFiles().length > 0) { + String capitalizedTarget = target.capitalize() + // Copy the generated aar library files to the custom build directory. + tasks += "copy" + capitalizedTarget + "AARToAppModule" + // Copy the generated aar library files to the bin directory. + tasks += "copy" + capitalizedTarget + "AARToBin" + // Copy the prebuilt binary templates to the bin directory. + tasks += "copy" + capitalizedTarget + "BinaryToBin" + } else { + logger.lifecycle("No native shared libs for target $target. Skipping build.") } } @@ -133,20 +168,28 @@ task generateGodotTemplates(type: GradleBuild) { * Clean the generated artifacts. */ task cleanGodotTemplates(type: Delete) { - // Delete the generated native libs - delete("lib/libs") + // Delete the generated native libs + delete("lib/libs") + + // Delete the library generated AAR files + delete("lib/build/outputs/aar") + + // Delete the godotpayment libs directory contents + delete("plugins/godotpayment/libs") - // Delete the library generated AAR files - delete("lib/build/outputs/aar") + // Delete the generated godotpayment aar + delete("plugins/godotpayment/build/outputs/aar") - // Delete the app libs directory contents - delete("app/libs") + // Delete the app libs directory contents + delete("app/libs") - // Delete the generated binary apks - delete("app/build/outputs/apk") + // Delete the generated binary apks + delete("app/build/outputs/apk") - // Delete the Godot templates in the Godot bin directory - delete("$binDir/android_debug.apk") - delete("$binDir/android_release.apk") - delete("$binDir/android_source.zip") + // Delete the Godot templates in the Godot bin directory + delete("$binDir/android_debug.apk") + delete("$binDir/android_release.apk") + delete("$binDir/android_source.zip") + delete("$binDir/godot-lib.debug.aar") + delete("$binDir/godot-lib.release.aar") } diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index bf50265715..f56b0f6a5e 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index eb97484b9c..ca8aaf8af0 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -2,6 +2,8 @@ apply plugin: 'com.android.library' dependencies { implementation libraries.supportCoreUtils + implementation libraries.kotlinStdLib + implementation libraries.v4Support } def pathToRootDir = "../../../../" diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 021214b627..7db4aa6597 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -61,8 +61,11 @@ import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings.Secure; +import android.support.annotation.CallSuper; import android.support.annotation.Keep; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; @@ -87,22 +90,20 @@ import com.google.android.vending.expansion.downloader.IStub; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.lang.reflect.Method; import java.security.MessageDigest; -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Locale; -import javax.microedition.khronos.opengles.GL10; import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.payments.PaymentsManager; +import org.godotengine.godot.plugin.GodotPlugin; +import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.utils.GodotNetUtils; import org.godotengine.godot.utils.PermissionsUtil; import org.godotengine.godot.xr.XRMode; -public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient { +public abstract class Godot extends FragmentActivity implements SensorEventListener, IDownloaderClient { - static final int MAX_SINGLETONS = 64; private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -126,8 +127,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo private boolean activityResumed; private int mState; - // Used to dispatch events to the main thread. - private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + private GodotPluginRegistry pluginRegistry; static private Intent mCurrentIntent; @@ -154,83 +154,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo mPauseButton.setText(stringResourceID); } - static public class SingletonBase { - - protected void registerClass(String p_name, String[] p_methods) { - - GodotLib.singleton(p_name, this); - - Class clazz = getClass(); - Method[] methods = clazz.getDeclaredMethods(); - for (Method method : methods) { - boolean found = false; - - for (String s : p_methods) { - if (s.equals(method.getName())) { - found = true; - break; - } - } - if (!found) - continue; - - List<String> ptr = new ArrayList<String>(); - - Class[] paramTypes = method.getParameterTypes(); - for (Class c : paramTypes) { - ptr.add(c.getName()); - } - - String[] pt = new String[ptr.size()]; - ptr.toArray(pt); - - GodotLib.method(p_name, method.getName(), method.getReturnType().getName(), pt); - } - - Godot.singletons[Godot.singleton_count++] = this; - } - - /** - * Invoked once during the Godot Android initialization process after creation of the - * {@link GodotView} view. - * <p> - * This method should be overridden by descendants of this class that would like to add - * their view/layout to the Godot view hierarchy. - * - * @return the view to be included; null if no views should be included. - */ - @Nullable - protected View onMainCreateView(Activity activity) { - return null; - } - - protected void onMainActivityResult(int requestCode, int resultCode, Intent data) { - } - - protected void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - } - - protected void onMainPause() {} - protected void onMainResume() {} - protected void onMainDestroy() {} - protected boolean onMainBackPressed() { return false; } - - protected void onGLDrawFrame(GL10 gl) {} - protected void onGLSurfaceChanged(GL10 gl, int width, int height) {} // singletons will always miss first onGLSurfaceChanged call - //protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step() - - public void registerMethods() {} - } - - /* - protected List<SingletonBase> singletons = new ArrayList<SingletonBase>(); - protected void instanceSingleton(SingletonBase s) { - - s.registerMethods(); - singletons.add(s); - } - */ - private String[] command_line; private boolean use_apk_expansion; @@ -246,9 +169,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo public static GodotIO io; public static GodotNetUtils netUtils; - static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS]; - static int singleton_count = 0; - public interface ResultCallback { public void callback(int requestCode, int resultCode, Intent data); } @@ -265,16 +185,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo result_callback = null; }; - for (int i = 0; i < singleton_count; i++) { - - singletons[i].onMainActivityResult(requestCode, resultCode, data); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainActivityResult(requestCode, resultCode, data); } }; @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainRequestPermissionsResult(requestCode, permissions, grantResults); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults); } for (int i = 0; i < permissions.length; i++) { @@ -283,6 +202,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo }; /** + * Invoked on the GL thread when the Godot main loop has started. + */ + @CallSuper + protected void onGLGodotMainLoopStarted() { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLGodotMainLoopStarted(); + } + } + + /** * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer. */ @Keep @@ -302,14 +231,13 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo edittext.setView(mView); io.setEdit(edittext); - final Godot godot = this; mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Point fullSize = new Point(); - godot.getWindowManager().getDefaultDisplay().getSize(fullSize); + getWindowManager().getDefaultDisplay().getSize(fullSize); Rect gameSize = new Rect(); - godot.mView.getWindowVisibleDisplayFrame(gameSize); + mView.getWindowVisibleDisplayFrame(gameSize); final int keyboardHeight = fullSize.y - gameSize.bottom; GodotLib.setVirtualKeyboardHeight(keyboardHeight); @@ -321,23 +249,23 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo @Override public void run() { GodotLib.setup(current_command_line); - setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); - // The Godot Android plugins are setup on completion of GodotLib.setup - mainThreadHandler.post(new Runnable() { - @Override - public void run() { - // Include the non-null views returned in the Godot view hierarchy. - for (int i = 0; i < singleton_count; i++) { - View view = singletons[i].onMainCreateView(Godot.this); - if (view != null) { - layout.addView(view); - } - } - } - }); + // Must occur after GodotLib.setup has completed. + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLRegisterPluginWithGodotNative(); + } + + setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); } }); + + // Include the returned non-null views in the Godot view hierarchy. + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + View pluginView = plugin.onMainCreateView(this); + if (pluginView != null) { + layout.addView(pluginView); + } + } } public void setKeepScreenOn(final boolean p_enabled) { @@ -535,6 +463,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); + pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); //check for apk expansion API if (true) { @@ -675,8 +604,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo protected void onDestroy() { if (mPaymentsManager != null) mPaymentsManager.destroy(); - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainDestroy(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainDestroy(); } GodotLib.ondestroy(this); @@ -703,8 +632,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo mSensorManager.unregisterListener(this); - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainPause(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainPause(); } } @@ -755,9 +684,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } - for (int i = 0; i < singleton_count; i++) { - - singletons[i].onMainResume(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainResume(); } } @@ -850,8 +778,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo public void onBackPressed() { boolean shouldQuit = true; - for (int i = 0; i < singleton_count; i++) { - if (singletons[i].onMainBackPressed()) { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + if (plugin.onMainBackPressed()) { shouldQuit = false; } } @@ -866,6 +794,17 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } } + /** + * Queue a runnable to be run on the GL thread. + * <p> + * This must be called after the GL thread has started. + */ + public final void runOnGLThread(@NonNull Runnable action) { + if (mView != null) { + mView.queueEvent(action); + } + } + private void forceQuit() { System.exit(0); } @@ -990,8 +929,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo int keyCode; if ((keyCode = cc[i]) != 0) { // Simulate key down and up... - GodotLib.key(0, keyCode, true); - GodotLib.key(0, keyCode, false); + GodotLib.key(0, 0, keyCode, true); + GodotLib.key(0, 0, keyCode, false); } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index e0b46673ba..89a65aea24 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -136,7 +136,7 @@ public class GodotLib { /** * Forward regular key events from the main thread to the GL thread. */ - public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed); + public static native void key(int p_keycode, int p_scancode, int p_unicode_char, boolean p_pressed); /** * Forward game device's key events from the main thread to the GL thread. @@ -176,22 +176,6 @@ public class GodotLib { public static native void audio(); /** - * Used to setup a {@link org.godotengine.godot.Godot.SingletonBase} instance. - * @param p_name Name of the instance. - * @param p_object Reference to the singleton instance. - */ - public static native void singleton(String p_name, Object p_object); - - /** - * Used to complete registration of the {@link org.godotengine.godot.Godot.SingletonBase} instance's methods. - * @param p_sname Name of the instance - * @param p_name Name of the method to register - * @param p_ret Return type of the registered method - * @param p_params Method parameters types - */ - public static native void method(String p_sname, String p_name, String p_ret, String[] p_params); - - /** * Used to access Godot global properties. * @param p_key Property key * @return String value of the property diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java index 26fa033f12..ee9a2aee4f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java @@ -30,9 +30,12 @@ package org.godotengine.godot; +import android.content.Context; import android.opengl.GLSurfaceView; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.plugin.GodotPlugin; +import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.utils.GLUtils; /** @@ -40,8 +43,13 @@ import org.godotengine.godot.utils.GLUtils; */ class GodotRenderer implements GLSurfaceView.Renderer { + private final GodotPluginRegistry pluginRegistry; private boolean activityJustResumed = false; + GodotRenderer() { + this.pluginRegistry = GodotPluginRegistry.getPluginRegistry(); + } + public void onDrawFrame(GL10 gl) { if (activityJustResumed) { GodotLib.onRendererResumed(); @@ -49,21 +57,23 @@ class GodotRenderer implements GLSurfaceView.Renderer { } GodotLib.step(); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLDrawFrame(gl); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLDrawFrame(gl); } } public void onSurfaceChanged(GL10 gl, int width, int height) { - GodotLib.resize(width, height); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLSurfaceChanged(gl, width, height); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLSurfaceChanged(gl, width, height); } } public void onSurfaceCreated(GL10 gl, EGLConfig config) { GodotLib.newcontext(GLUtils.use_32); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLSurfaceCreated(gl, config); + } } void onActivityResumed() { diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index b2b88088e8..e00ca86c41 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -98,11 +98,12 @@ public class GodotInputHandler implements InputDeviceListener { }); } } else { + final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); queueEvent(new Runnable() { @Override public void run() { - GodotLib.key(keyCode, chr, false); + GodotLib.key(keyCode, scanCode, chr, false); } }); }; @@ -143,11 +144,12 @@ public class GodotInputHandler implements InputDeviceListener { }); } } else { + final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); queueEvent(new Runnable() { @Override public void run() { - GodotLib.key(keyCode, chr, true); + GodotLib.key(keyCode, scanCode, chr, true); } }); }; diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 3a154f1bf3..8d9b5461a1 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -91,8 +91,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void run() { for (int i = 0; i < count; ++i) { - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true); - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false); + GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true); + GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false); } } }); @@ -110,8 +110,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void run() { for (int i = 0; i < count; ++i) { - GodotLib.key(0, newChars[i], true); - GodotLib.key(0, newChars[i], false); + GodotLib.key(0, 0, newChars[i], true); + GodotLib.key(0, 0, newChars[i], false); } } }); @@ -127,8 +127,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene public void run() { for (int i = 0; i < characters.length(); i++) { final int ch = characters.codePointAt(i); - GodotLib.key(0, ch, true); - GodotLib.key(0, ch, false); + GodotLib.key(0, 0, ch, true); + GodotLib.key(0, 0, ch, false); } } }); diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java new file mode 100644 index 0000000000..6ac7338b30 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java @@ -0,0 +1,97 @@ +/*************************************************************************/ +/* GodotPaymentInterface.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.payments; + +public interface GodotPaymentInterface { + void purchase(String sku, String transactionId); + + void consumeUnconsumedPurchases(); + + String getSignature(); + + void callbackSuccess(String ticket, String signature, String sku); + + void callbackSuccessProductMassConsumed(String ticket, String signature, String sku); + + void callbackSuccessNoUnconsumedPurchases(); + + void callbackFailConsume(String message); + + void callbackFail(String message); + + void callbackCancel(); + + void callbackAlreadyOwned(String sku); + + int getPurchaseCallbackId(); + + void setPurchaseCallbackId(int purchaseCallbackId); + + String getPurchaseValidationUrlPrefix(); + + void setPurchaseValidationUrlPrefix(String url); + + String getAccessToken(); + + void setAccessToken(String accessToken); + + void setTransactionId(String transactionId); + + String getTransactionId(); + + // request purchased items are not consumed + void requestPurchased(); + + // callback for requestPurchased() + void callbackPurchased(String receipt, String signature, String sku); + + void callbackDisconnected(); + + void callbackConnected(); + + // true if connected, false otherwise + boolean isConnected(); + + // consume item automatically after purchase. default is true. + void setAutoConsume(boolean autoConsume); + + // consume a specific item + void consume(String sku); + + // query in app item detail info + void querySkuDetails(String[] list); + + void addSkuDetail(String itemJson); + + void completeSkuDetail(); + + void errorSkuDetail(String errorMessage); +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java index 90b958266b..9bf6650f84 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java +++ b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java @@ -43,7 +43,6 @@ import android.util.Log; import com.android.vending.billing.IInAppBillingService; import java.util.ArrayList; import java.util.Arrays; -import org.godotengine.godot.GodotPaymentV3; import org.json.JSONException; import org.json.JSONObject; @@ -90,9 +89,9 @@ public class PaymentsManager { public void onServiceDisconnected(ComponentName name) { mService = null; - // At this stage, godotPaymentV3 might not have been initialized yet. - if (godotPaymentV3 != null) { - godotPaymentV3.callbackDisconnected(); + // At this stage, godotPayment might not have been initialized yet. + if (godotPayment != null) { + godotPayment.callbackDisconnected(); } } @@ -100,9 +99,9 @@ public class PaymentsManager { public void onServiceConnected(ComponentName name, IBinder service) { mService = IInAppBillingService.Stub.asInterface(service); - // At this stage, godotPaymentV3 might not have been initialized yet. - if (godotPaymentV3 != null) { - godotPaymentV3.callbackConnected(); + // At this stage, godotPayment might not have been initialized yet. + if (godotPayment != null) { + godotPayment.callbackConnected(); } } }; @@ -111,17 +110,17 @@ public class PaymentsManager { new PurchaseTask(mService, activity) { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } @Override protected void canceled() { - godotPaymentV3.callbackCancel(); + godotPayment.callbackCancel(); } @Override protected void alreadyOwned() { - godotPaymentV3.callbackAlreadyOwned(sku); + godotPayment.callbackAlreadyOwned(sku); } } .purchase(sku, transactionId); @@ -135,19 +134,19 @@ public class PaymentsManager { new ReleaseAllConsumablesTask(mService, activity) { @Override protected void success(String sku, String receipt, String signature, String token) { - godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku); + godotPayment.callbackSuccessProductMassConsumed(receipt, signature, sku); } @Override protected void error(String message) { Log.d("godot", "consumeUnconsumedPurchases :" + message); - godotPaymentV3.callbackFailConsume(message); + godotPayment.callbackFailConsume(message); } @Override protected void notRequired() { Log.d("godot", "callbackSuccessNoUnconsumedPurchases :"); - godotPaymentV3.callbackSuccessNoUnconsumedPurchases(); + godotPayment.callbackSuccessNoUnconsumedPurchases(); } } .consumeItAll(); @@ -168,7 +167,7 @@ public class PaymentsManager { final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); if (myPurchases == null || myPurchases.size() == 0) { - godotPaymentV3.callbackPurchased("", "", ""); + godotPayment.callbackPurchased("", "", ""); return; } @@ -186,7 +185,7 @@ public class PaymentsManager { pc.setConsumableFlag("block", sku, true); pc.setConsumableValue("token", sku, token); - godotPaymentV3.callbackPurchased(receipt, signature, sku); + godotPayment.callbackPurchased(receipt, signature, sku); } catch (JSONException e) { } } @@ -203,7 +202,7 @@ public class PaymentsManager { new HandlePurchaseTask(activity) { @Override protected void success(final String sku, final String signature, final String ticket) { - godotPaymentV3.callbackSuccess(ticket, signature, sku); + godotPayment.callbackSuccess(ticket, signature, sku); if (auto_consume) { new ConsumeTask(mService, activity) { @@ -213,7 +212,7 @@ public class PaymentsManager { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } } .consume(sku); @@ -222,12 +221,12 @@ public class PaymentsManager { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } @Override protected void canceled() { - godotPaymentV3.callbackCancel(); + godotPayment.callbackCancel(); } } .handlePurchaseRequest(resultCode, data); @@ -235,19 +234,19 @@ public class PaymentsManager { public void validatePurchase(String purchaseToken, final String sku) { - new ValidateTask(activity, godotPaymentV3) { + new ValidateTask(activity, godotPayment) { @Override protected void success() { new ConsumeTask(mService, activity) { @Override protected void success(String ticket) { - godotPaymentV3.callbackSuccess(ticket, null, sku); + godotPayment.callbackSuccess(ticket, null, sku); } @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } } .consume(sku); @@ -255,12 +254,12 @@ public class PaymentsManager { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } @Override protected void canceled() { - godotPaymentV3.callbackCancel(); + godotPayment.callbackCancel(); } } .validatePurchase(sku); @@ -274,12 +273,12 @@ public class PaymentsManager { new ConsumeTask(mService, activity) { @Override protected void success(String ticket) { - godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku); + godotPayment.callbackSuccessProductMassConsumed(ticket, "", sku); } @Override protected void error(String message) { - godotPaymentV3.callbackFailConsume(message); + godotPayment.callbackFailConsume(message); } } .consume(sku); @@ -387,9 +386,9 @@ public class PaymentsManager { if (!skuDetails.containsKey("DETAILS_LIST")) { int response = getResponseCodeFromBundle(skuDetails); if (response != BILLING_RESPONSE_RESULT_OK) { - godotPaymentV3.errorSkuDetail(getResponseDesc(response)); + godotPayment.errorSkuDetail(getResponseDesc(response)); } else { - godotPaymentV3.errorSkuDetail("No error but no detail list."); + godotPayment.errorSkuDetail("No error but no detail list."); } return; } @@ -398,22 +397,22 @@ public class PaymentsManager { for (String thisResponse : responseList) { Log.d("godot", "response = " + thisResponse); - godotPaymentV3.addSkuDetail(thisResponse); + godotPayment.addSkuDetail(thisResponse); } } catch (RemoteException e) { e.printStackTrace(); - godotPaymentV3.errorSkuDetail("RemoteException error!"); + godotPayment.errorSkuDetail("RemoteException error!"); } } - godotPaymentV3.completeSkuDetail(); + godotPayment.completeSkuDetail(); } })) .start(); } - private GodotPaymentV3 godotPaymentV3; + private GodotPaymentInterface godotPayment; - public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) { - this.godotPaymentV3 = godotPaymentV3; + public void setBaseSingleton(GodotPaymentInterface godotPaymentInterface) { + this.godotPayment = godotPaymentInterface; } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java index dbb6b8a783..10c314aecf 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java +++ b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java @@ -34,7 +34,6 @@ import android.app.Activity; import android.app.ProgressDialog; import android.os.AsyncTask; import java.lang.ref.WeakReference; -import org.godotengine.godot.GodotPaymentV3; import org.godotengine.godot.utils.HttpRequester; import org.godotengine.godot.utils.RequestParams; import org.json.JSONException; @@ -43,7 +42,7 @@ import org.json.JSONObject; abstract public class ValidateTask { private Activity context; - private GodotPaymentV3 godotPaymentsV3; + private GodotPaymentInterface godotPayments; private ProgressDialog dialog; private String mSku; @@ -80,9 +79,9 @@ abstract public class ValidateTask { } } - public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3) { + public ValidateTask(Activity context, GodotPaymentInterface godotPayments) { this.context = context; - this.godotPaymentsV3 = godotPaymentsV3; + this.godotPayments = godotPayments; } public void validatePurchase(final String sku) { @@ -96,7 +95,7 @@ abstract public class ValidateTask { private String doInBackground(String... params) { PaymentsCache pc = new PaymentsCache(context); - String url = godotPaymentsV3.getPurchaseValidationUrlPrefix(); + String url = godotPayments.getPurchaseValidationUrlPrefix(); RequestParams param = new RequestParams(); param.setUrl(url); param.put("ticket", pc.getConsumableValue("ticket", mSku)); diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java new file mode 100644 index 0000000000..d5bf4fc70e --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -0,0 +1,256 @@ +/*************************************************************************/ +/* GodotPlugin.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.plugin; + +import android.app.Activity; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.Godot; + +/** + * Base class for the Godot Android plugins. + * <p> + * A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats: + * <p> + * - The library must have a dependency on the Godot Android library (godot-lib.aar). + * A stable version is available for each release. + * <p> + * - The library must include a <meta-data> tag in its manifest file setup as follow: + * <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" /> + * Where: + * - 'PluginName' is the name of the plugin. + * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class + * extending {@link GodotPlugin}. + * + * A plugin can also define and provide c/c++ gdnative libraries and nativescripts for the target + * app/game to leverage. + * The shared library for the gdnative library will be automatically bundled by the aar build + * system. + * Godot '*.gdnlib' and '*.gdns' resource files must however be manually defined in the project + * 'assets' directory. The recommended path for these resources in the 'assets' directory should be: + * 'godot/plugin/v1/[PluginName]/' + */ +public abstract class GodotPlugin { + + private final Godot godot; + + public GodotPlugin(Godot godot) { + this.godot = godot; + } + + /** + * Provides access to the Godot engine. + */ + protected Godot getGodot() { + return godot; + } + + /** + * Register the plugin with Godot native code. + */ + public final void onGLRegisterPluginWithGodotNative() { + nativeRegisterSingleton(getPluginName()); + + Class clazz = getClass(); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + boolean found = false; + + for (String s : getPluginMethods()) { + if (s.equals(method.getName())) { + found = true; + break; + } + } + if (!found) + continue; + + List<String> ptr = new ArrayList<String>(); + + Class[] paramTypes = method.getParameterTypes(); + for (Class c : paramTypes) { + ptr.add(c.getName()); + } + + String[] pt = new String[ptr.size()]; + ptr.toArray(pt); + + nativeRegisterMethod(getPluginName(), method.getName(), method.getReturnType().getName(), pt); + } + + // Get the list of gdnative libraries to register. + Set<String> gdnativeLibrariesPaths = getPluginGDNativeLibrariesPaths(); + if (!gdnativeLibrariesPaths.isEmpty()) { + nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0])); + } + } + + /** + * Invoked once during the Godot Android initialization process after creation of the + * {@link org.godotengine.godot.GodotView} view. + * <p> + * This method should be overridden by descendants of this class that would like to add + * their view/layout to the Godot view hierarchy. + * + * @return the view to be included; null if no views should be included. + */ + @Nullable + public View onMainCreateView(Activity activity) { + return null; + } + + /** + * @see Activity#onActivityResult(int, int, Intent) + */ + public void onMainActivityResult(int requestCode, int resultCode, Intent data) { + } + + /** + * @see Activity#onRequestPermissionsResult(int, String[], int[]) + */ + public void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + } + + /** + * @see Activity#onPause() + */ + public void onMainPause() {} + + /** + * @see Activity#onResume() + */ + public void onMainResume() {} + + /** + * @see Activity#onDestroy() + */ + public void onMainDestroy() {} + + /** + * @see Activity#onBackPressed() + */ + public boolean onMainBackPressed() { return false; } + + /** + * Invoked on the GL thread when the Godot main loop has started. + */ + public void onGLGodotMainLoopStarted() {} + + /** + * Invoked once per frame on the GL thread after the frame is drawn. + */ + public void onGLDrawFrame(GL10 gl) {} + + /** + * Called on the GL thread after the surface is created and whenever the OpenGL ES surface size + * changes. + */ + public void onGLSurfaceChanged(GL10 gl, int width, int height) {} + + /** + * Called on the GL thread when the surface is created or recreated. + */ + public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} + + /** + * Returns the name of the plugin. + * <p> + * This value must match the one listed in the plugin '<meta-data>' manifest entry. + */ + @NonNull + public abstract String getPluginName(); + + /** + * Returns the list of methods to be exposed to Godot. + */ + @NonNull + public abstract List<String> getPluginMethods(); + + /** + * Returns the paths for the plugin's gdnative libraries. + * + * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. + */ + @NonNull + protected Set<String> getPluginGDNativeLibrariesPaths() { + return Collections.emptySet(); + } + + /** + * Runs the specified action on the UI thread. If the current thread is the UI + * thread, then the action is executed immediately. If the current thread is + * not the UI thread, the action is posted to the event queue of the UI thread. + * + * @param action the action to run on the UI thread + */ + protected void runOnUiThread(Runnable action) { + godot.runOnUiThread(action); + } + + /** + * Queue the specified action to be run on the GL thread. + * + * @param action the action to run on the GL thread + */ + protected void runOnGLThread(Runnable action) { + godot.runOnGLThread(action); + } + + /** + * Used to setup a {@link GodotPlugin} instance. + * @param p_name Name of the instance. + */ + private native void nativeRegisterSingleton(String p_name); + + /** + * Used to complete registration of the {@link GodotPlugin} instance's methods. + * @param p_sname Name of the instance + * @param p_name Name of the method to register + * @param p_ret Return type of the registered method + * @param p_params Method parameters types + */ + private native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params); + + /** + * Used to register gdnative libraries bundled by the plugin. + * @param gdnlibPaths Paths to the libraries relative to the 'assets' directory. + */ + private native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths); +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java new file mode 100644 index 0000000000..b6d949b7bf --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -0,0 +1,199 @@ +/*************************************************************************/ +/* GodotPluginRegistry.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.plugin; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.godotengine.godot.Godot; + +/** + * Registry used to load and access the registered Godot Android plugins. + */ +public final class GodotPluginRegistry { + + private static final String TAG = GodotPluginRegistry.class.getSimpleName(); + + private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1."; + + /** + * Name for the metadata containing the list of Godot plugins to enable. + */ + private static final String GODOT_ENABLED_PLUGINS_LABEL = "custom_template_plugins"; + + private static GodotPluginRegistry instance; + private final ConcurrentHashMap<String, GodotPlugin> registry; + + private GodotPluginRegistry(Godot godot) { + registry = new ConcurrentHashMap<>(); + loadPlugins(godot); + } + + /** + * Retrieve the plugin tied to the given plugin name. + * @param pluginName Name of the plugin + * @return {@link GodotPlugin} handle if it exists, null otherwise. + */ + @Nullable + public GodotPlugin getPlugin(String pluginName) { + return registry.get(pluginName); + } + + /** + * Retrieve the full set of loaded plugins. + */ + public Collection<GodotPlugin> getAllPlugins() { + return registry.values(); + } + + /** + * Parse the manifest file and load all included Godot Android plugins. + * <p> + * A plugin manifest entry is a '<meta-data>' tag setup as described in the {@link GodotPlugin} + * documentation. + * + * @param godot Godot instance + * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance + * of each Godot Android plugins is available at runtime. + */ + public static GodotPluginRegistry initializePluginRegistry(Godot godot) { + if (instance == null) { + instance = new GodotPluginRegistry(godot); + } + + return instance; + } + + /** + * Return the plugin registry if it's initialized. + * Throws a {@link IllegalStateException} exception if not. + * + * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method. + */ + public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException { + if (instance == null) { + throw new IllegalStateException("Plugin registry hasn't been initialized."); + } + + return instance; + } + + private void loadPlugins(Godot godot) { + try { + ApplicationInfo appInfo = godot + .getPackageManager() + .getApplicationInfo(godot.getPackageName(), PackageManager.GET_META_DATA); + Bundle metaData = appInfo.metaData; + if (metaData == null || metaData.isEmpty()) { + return; + } + + // When using the Godot editor for building and exporting the apk, this is used to check + // which plugins to enable since the custom build template may contain prebuilt plugins. + // When using a custom process to generate the apk, the metadata is not needed since + // it's assumed that the developer is aware of the dependencies included in the apk. + final Set<String> enabledPluginsSet; + if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) { + String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, ""); + String[] enabledPluginsList = enabledPlugins.split(","); + if (enabledPluginsList.length == 0) { + // No plugins to enable. Aborting early. + return; + } + + enabledPluginsSet = new HashSet<>(); + for (String enabledPlugin : enabledPluginsList) { + enabledPluginsSet.add(enabledPlugin.trim()); + } + } else { + enabledPluginsSet = null; + } + + int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length(); + for (String metaDataName : metaData.keySet()) { + // Parse the meta-data looking for entry with the Godot plugin name prefix. + if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { + String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim(); + if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) { + Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled."); + continue; + } + + // Retrieve the plugin class full name. + String pluginHandleClassFullName = metaData.getString(metaDataName); + if (!TextUtils.isEmpty(pluginHandleClassFullName)) { + try { + // Attempt to create the plugin init class via reflection. + @SuppressWarnings("unchecked") + Class<GodotPlugin> pluginClass = (Class<GodotPlugin>)Class + .forName(pluginHandleClassFullName); + Constructor<GodotPlugin> pluginConstructor = pluginClass + .getConstructor(Godot.class); + GodotPlugin pluginHandle = pluginConstructor.newInstance(godot); + + // Load the plugin initializer into the registry using the plugin name + // as key. + if (!pluginName.equals(pluginHandle.getPluginName())) { + Log.w(TAG, + "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); + } + registry.put(pluginName, pluginHandle); + } catch (ClassNotFoundException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (IllegalAccessException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (InstantiationException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (NoSuchMethodException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (InvocationTargetException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } + } else { + Log.w(TAG, "Invalid plugin loader class for " + pluginName); + } + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e); + } + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt new file mode 100644 index 0000000000..67faad8ddd --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* VkRenderer.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +@file:JvmName("VkRenderer") +package org.godotengine.godot.vulkan + +import android.view.Surface + +/** + * Responsible to setting up and driving the Vulkan rendering logic. + * + * <h3>Threading</h3> + * The renderer will be called on a separate thread, so that rendering + * performance is decoupled from the UI thread. Clients typically need to + * communicate with the renderer from the UI thread, because that's where + * input events are received. Clients can communicate using any of the + * standard Java techniques for cross-thread communication, or they can + * use the [VkSurfaceView.queueOnVkThread] convenience method. + * + * @see [VkSurfaceView.startRenderer] + */ +internal class VkRenderer { + + /** + * Called when the surface is created and signals the beginning of rendering. + */ + fun onVkSurfaceCreated(surface: Surface) { + nativeOnVkSurfaceCreated(surface) + } + + /** + * Called after the surface is created and whenever its size changes. + */ + fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) { + nativeOnVkSurfaceChanged(surface, width, height) + } + + /** + * Called to draw the current frame. + */ + fun onVkDrawFrame() { + nativeOnVkDrawFrame() + } + + /** + * Called when the rendering thread is resumed. + */ + fun onVkResume() { + nativeOnVkResume() + } + + /** + * Called when the rendering thread is paused. + */ + fun onVkPause() { + nativeOnVkPause() + } + + /** + * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic. + */ + fun onVkDestroy() { + nativeOnVkDestroy() + } + + private external fun nativeOnVkSurfaceCreated(surface: Surface) + private external fun nativeOnVkSurfaceChanged(surface: Surface, width: Int, height: Int) + private external fun nativeOnVkResume() + private external fun nativeOnVkDrawFrame() + private external fun nativeOnVkPause() + private external fun nativeOnVkDestroy() +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt new file mode 100644 index 0000000000..1c594f3201 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -0,0 +1,136 @@ +/*************************************************************************/ +/* VkSurfaceView.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +@file:JvmName("VkSurfaceView") +package org.godotengine.godot.vulkan + +import android.content.Context +import android.view.SurfaceHolder +import android.view.SurfaceView + +/** + * An implementation of SurfaceView that uses the dedicated surface for + * displaying Vulkan rendering. + * <p> + * A [VkSurfaceView] provides the following features: + * <p> + * <ul> + * <li>Manages a surface, which is a special piece of memory that can be + * composited into the Android view system. + * <li>Accepts a user-provided [VkRenderer] object that does the actual rendering. + * <li>Renders on a dedicated [VkThread] thread to decouple rendering performance from the + * UI thread. + * </ul> + */ +internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { + + companion object { + fun checkState(expression: Boolean, errorMessage: Any) { + check(expression) { errorMessage.toString() } + } + } + + /** + * Thread used to drive the vulkan logic. + */ + private val vkThread: VkThread by lazy { + VkThread(this, renderer) + } + + /** + * Performs the actual rendering. + */ + private lateinit var renderer: VkRenderer + + init { + isClickable = true + holder.addCallback(this) + } + + /** + * Set the [VkRenderer] associated with the view, and starts the thread that will drive the vulkan + * rendering. + * + * This method should be called once and only once in the life-cycle of [VkSurfaceView]. + */ + fun startRenderer(renderer: VkRenderer) { + checkState(!this::renderer.isInitialized, "startRenderer must only be invoked once") + this.renderer = renderer + vkThread.start() + } + + /** + * Queues a runnable to be run on the Vulkan rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun queueOnVkThread(runnable: Runnable) { + vkThread.queueEvent(runnable) + } + + /** + * Resumes the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun onResume() { + vkThread.onResume() + } + + /** + * Pauses the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun onPause() { + vkThread.onPause() + } + + /** + * Tear down the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun onDestroy() { + vkThread.blockingExit() + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + vkThread.onSurfaceChanged(width, height) + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + vkThread.onSurfaceDestroyed() + } + + override fun surfaceCreated(holder: SurfaceHolder) { + vkThread.onSurfaceCreated() + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt new file mode 100644 index 0000000000..2e332840bf --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt @@ -0,0 +1,230 @@ +/*************************************************************************/ +/* VkThread.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +@file:JvmName("VkThread") +package org.godotengine.godot.vulkan + +import android.util.Log +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * Thread implementation for the [VkSurfaceView] onto which the vulkan logic is ran. + * + * The implementation is modeled after [android.opengl.GLSurfaceView]'s GLThread. + */ +internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vkRenderer: VkRenderer) : Thread(TAG) { + + companion object { + private val TAG = VkThread::class.java.simpleName + } + + /** + * Used to run events scheduled on the thread. + */ + private val eventQueue = ArrayList<Runnable>() + + /** + * Used to synchronize interaction with other threads (e.g: main thread). + */ + private val lock = ReentrantLock() + private val lockCondition = lock.newCondition() + + private var shouldExit = false + private var exited = false + private var rendererInitialized = false + private var rendererResumed = false + private var resumed = false + private var hasSurface = false + private var width = 0 + private var height = 0 + + /** + * Determine when drawing can occur on the thread. This usually occurs after the + * [android.view.Surface] is available, the app is in a resumed state. + */ + private val readyToDraw + get() = hasSurface && resumed + + private fun threadExiting() { + lock.withLock { + exited = true + lockCondition.signalAll() + } + } + + /** + * Queue an event on the [VkThread]. + */ + fun queueEvent(event: Runnable) { + lock.withLock { + eventQueue.add(event) + lockCondition.signalAll() + } + } + + /** + * Request the thread to exit and block until it's done. + */ + fun blockingExit() { + lock.withLock { + shouldExit = true + lockCondition.signalAll() + while (!exited) { + try { + Log.i(TAG, "Waiting on exit for $name") + lockCondition.await() + } catch (ex: InterruptedException) { + currentThread().interrupt() + } + } + } + } + + /** + * Invoked when the app resumes. + */ + fun onResume() { + lock.withLock { + resumed = true + lockCondition.signalAll() + } + } + + /** + * Invoked when the app pauses. + */ + fun onPause() { + lock.withLock { + resumed = false + lockCondition.signalAll() + } + } + + /** + * Invoked when the [android.view.Surface] has been created. + */ + fun onSurfaceCreated() { + // This is a no op because surface creation will always be followed by surfaceChanged() + // which provide all the needed information. + } + + /** + * Invoked following structural updates to [android.view.Surface]. + */ + fun onSurfaceChanged(width: Int, height: Int) { + lock.withLock { + hasSurface = true + this.width = width + this.height = height + lockCondition.signalAll() + } + } + + /** + * Invoked when the [android.view.Surface] is no longer available. + */ + fun onSurfaceDestroyed() { + lock.withLock { + hasSurface = false + lockCondition.signalAll() + } + } + + /** + * Thread loop modeled after [android.opengl.GLSurfaceView]'s GLThread. + */ + override fun run() { + try { + while (true) { + var event: Runnable? = null + lock.withLock { + while (true) { + // Code path for exiting the thread loop. + if (shouldExit) { + vkRenderer.onVkDestroy() + return + } + + // Check for events and execute them outside of the loop if found to avoid + // blocking the thread lifecycle by holding onto the lock. + if (eventQueue.isNotEmpty()) { + event = eventQueue.removeAt(0) + break; + } + + if (readyToDraw) { + if (!rendererResumed) { + rendererResumed = true + vkRenderer.onVkResume() + + if (!rendererInitialized) { + rendererInitialized = true + vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface) + } + + vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height) + } + + // Break out of the loop so drawing can occur without holding onto the lock. + break; + } else if (rendererResumed) { + // If we aren't ready to draw but are resumed, that means we either lost a surface + // or the app was paused. + rendererResumed = false + vkRenderer.onVkPause() + } + // We only reach this state if we are not ready to draw and have no queued events, so + // we wait. + // On state change, the thread will be awoken using the [lock] and [lockCondition], and + // we will resume execution. + lockCondition.await() + } + } + + // Run queued event. + if (event != null) { + event?.run() + continue + } + + // Draw only when there no more queued events. + vkRenderer.onVkDrawFrame() + } + } catch (ex: InterruptedException) { + Log.i(TAG, ex.message) + } catch (ex: IllegalStateException) { + Log.i(TAG, ex.message) + } finally { + threadExiting() + } + } + +} diff --git a/platform/android/java/plugins/godotpayment/build.gradle b/platform/android/java/plugins/godotpayment/build.gradle new file mode 100644 index 0000000000..4f376c4587 --- /dev/null +++ b/platform/android/java/plugins/godotpayment/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + } + + libraryVariants.all { variant -> + variant.outputs.all { output -> + output.outputFileName = "GodotPayment.${variant.name}.aar" + } + } + +} + +dependencies { + implementation libraries.supportCoreUtils + implementation libraries.v4Support + + if (rootProject.findProject(":lib")) { + compileOnly project(":lib") + } else if (rootProject.findProject(":godot:lib")) { + compileOnly project(":godot:lib") + } else { + compileOnly fileTree(dir: 'libs', include: ['godot-lib*.aar']) + } +} diff --git a/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml b/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..61afa03799 --- /dev/null +++ b/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.godotengine.godot.plugin.payment"> + + <application> + + <meta-data + android:name="org.godotengine.plugin.v1.GodotPayment" + android:value="org.godotengine.godot.plugin.payment.GodotPayment" /> + + </application> +</manifest> diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java index 93265d509f..4a6b611c4d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* GodotPaymentV3.java */ +/* GodotPayment.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot; +package org.godotengine.godot.plugin.payment; -import android.app.Activity; +import android.support.annotation.NonNull; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.godotengine.godot.Dictionary; +import org.godotengine.godot.Godot; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.payments.GodotPaymentInterface; import org.godotengine.godot.payments.PaymentsManager; +import org.godotengine.godot.plugin.GodotPlugin; import org.json.JSONException; import org.json.JSONObject; -public class GodotPaymentV3 extends Godot.SingletonBase { +public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { - private Godot activity; private Integer purchaseCallbackId = 0; private String accessToken; private String purchaseValidationUrlPrefix; @@ -49,8 +53,15 @@ public class GodotPaymentV3 extends Godot.SingletonBase { private PaymentsManager mPaymentManager; private Dictionary mSkuDetails = new Dictionary(); + public GodotPayment(Godot godot) { + super(godot); + mPaymentManager = godot.getPaymentsManager(); + mPaymentManager.setBaseSingleton(this); + } + + @Override public void purchase(final String sku, final String transactionId) { - activity.runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { mPaymentManager.requestPurchase(sku, transactionId); @@ -58,21 +69,9 @@ public class GodotPaymentV3 extends Godot.SingletonBase { }); } - static public Godot.SingletonBase initialize(Activity p_activity) { - - return new GodotPaymentV3(p_activity); - } - - public GodotPaymentV3(Activity p_activity) { - - registerClass("GodotPayments", new String[] { "purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected" }); - activity = (Godot)p_activity; - mPaymentManager = activity.getPaymentsManager(); - mPaymentManager.setBaseSingleton(this); - } - + @Override public void consumeUnconsumedPurchases() { - activity.runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { mPaymentManager.consumeUnconsumedPurchases(); @@ -82,74 +81,91 @@ public class GodotPaymentV3 extends Godot.SingletonBase { private String signature; + @Override public String getSignature() { return this.signature; } + @Override public void callbackSuccess(String ticket, String signature, String sku) { GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); } + @Override public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) { Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku); GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku }); } + @Override public void callbackSuccessNoUnconsumedPurchases() { GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {}); } + @Override public void callbackFailConsume(String message) { GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message }); } + @Override public void callbackFail(String message) { GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message }); } + @Override public void callbackCancel() { GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {}); } + @Override public void callbackAlreadyOwned(String sku) { GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku }); } + @Override public int getPurchaseCallbackId() { return purchaseCallbackId; } + @Override public void setPurchaseCallbackId(int purchaseCallbackId) { this.purchaseCallbackId = purchaseCallbackId; } + @Override public String getPurchaseValidationUrlPrefix() { return this.purchaseValidationUrlPrefix; } + @Override public void setPurchaseValidationUrlPrefix(String url) { this.purchaseValidationUrlPrefix = url; } + @Override public String getAccessToken() { return accessToken; } + @Override public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + @Override public void setTransactionId(String transactionId) { this.transactionId = transactionId; } + @Override public String getTransactionId() { return this.transactionId; } // request purchased items are not consumed + @Override public void requestPurchased() { - activity.runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { mPaymentManager.requestPurchased(); @@ -158,34 +174,41 @@ public class GodotPaymentV3 extends Godot.SingletonBase { } // callback for requestPurchased() + @Override public void callbackPurchased(String receipt, String signature, String sku) { GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku }); } + @Override public void callbackDisconnected() { GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {}); } + @Override public void callbackConnected() { GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); } // true if connected, false otherwise + @Override public boolean isConnected() { return mPaymentManager.isConnected(); } // consume item automatically after purchase. default is true. + @Override public void setAutoConsume(boolean autoConsume) { mPaymentManager.setAutoConsume(autoConsume); } // consume a specific item + @Override public void consume(String sku) { mPaymentManager.consume(sku); } // query in app item detail info + @Override public void querySkuDetails(String[] list) { List<String> nKeys = Arrays.asList(list); List<String> cKeys = Arrays.asList(mSkuDetails.get_keys()); @@ -202,6 +225,7 @@ public class GodotPaymentV3 extends Godot.SingletonBase { } } + @Override public void addSkuDetail(String itemJson) { JSONObject o = null; try { @@ -220,11 +244,25 @@ public class GodotPaymentV3 extends Godot.SingletonBase { } } + @Override public void completeSkuDetail() { GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails }); } + @Override public void errorSkuDetail(String errorMessage) { GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage }); } + + @NonNull + @Override + public String getPluginName() { + return "GodotPayment"; + } + + @NonNull + @Override + public List<String> getPluginMethods() { + return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected"); + } } diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index f6921c70aa..9536d3de6d 100644 --- a/platform/android/java/settings.gradle +++ b/platform/android/java/settings.gradle @@ -3,3 +3,4 @@ rootProject.name = "Godot" include ':app' include ':lib' +include ':plugins:godotpayment' |