Merge pull request #33682 from m4gr3d/rearch_godot_android_plugin

Re-architecture of the Godot Android plugin.
This commit is contained in:
Rémi Verschelde 2020-03-05 22:14:38 +01:00 committed by GitHub
commit 93f7c63ba5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1085 additions and 636 deletions

View File

@ -1215,9 +1215,6 @@ ProjectSettings::ProjectSettings() {
Compression::gzip_level = GLOBAL_DEF("compression/formats/gzip/compression_level", Z_DEFAULT_COMPRESSION);
custom_prop_info["compression/formats/gzip/compression_level"] = PropertyInfo(Variant::INT, "compression/formats/gzip/compression_level", PROPERTY_HINT_RANGE, "-1,9,1");
// Would ideally be defined in an Android-specific file, but then it doesn't appear in the docs
GLOBAL_DEF("android/modules", "");
using_datapack = false;
}

View File

@ -179,9 +179,6 @@
</method>
</methods>
<members>
<member name="android/modules" type="String" setter="" getter="" default="&quot;&quot;">
Comma-separated list of custom Android modules (which must have been built in the Android export templates) using their Java package path, e.g. [code]org/godotengine/org/GodotPaymentV3,org/godotengine/godot/MyCustomSingleton"[/code].
</member>
<member name="application/boot_splash/bg_color" type="Color" setter="" getter="" default="Color( 0.14, 0.14, 0.14, 1 )">
Background color for the boot splash.
</member>

View File

@ -560,13 +560,6 @@ Error ExportTemplateManager::install_android_template() {
// Make res://android dir (if it does not exist).
da->make_dir("android");
{
// Add an empty .gdignore file to avoid scan.
FileAccessRef f = FileAccess::open("res://android/.gdignore", FileAccess::WRITE);
ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
f->store_line("");
f->close();
}
{
// Add version, to ensure building won't work if template and Godot version don't match.
FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::WRITE);
@ -575,9 +568,20 @@ Error ExportTemplateManager::install_android_template() {
f->close();
}
Error err = da->make_dir_recursive("android/build");
// Create the android plugins directory.
Error err = da->make_dir_recursive("android/plugins");
ERR_FAIL_COND_V(err != OK, err);
err = da->make_dir_recursive("android/build");
ERR_FAIL_COND_V(err != OK, err);
{
// Add an empty .gdignore file to avoid scan.
FileAccessRef f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE);
ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
f->store_line("");
f->close();
}
// Uncompress source template.
const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);

View File

@ -20,7 +20,8 @@ android_files = [
'java_godot_io_wrapper.cpp',
'jni_utils.cpp',
'android_keys_utils.cpp',
'vulkan/vk_renderer_jni.cpp'
'vulkan/vk_renderer_jni.cpp',
'plugin/godot_plugin_jni.cpp'
]
env_android = env.Clone()

View File

@ -688,6 +688,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int xr_mode_index = p_preset->get("xr_features/xr_mode");
String plugins = p_preset->get("custom_template/plugins");
Vector<String> perms;
const char **aperms = android_perms;
@ -851,6 +853,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") {
// Update the meta-data 'android:value' attribute with the list of enabled plugins.
string_table.write[attr_value] = plugins;
}
iofs += 20;
}
@ -1363,6 +1370,7 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/plugins", PROPERTY_HINT_PLACEHOLDER_TEXT, "Plugin1,Plugin2,..."), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
@ -1743,260 +1751,6 @@ public:
return list;
}
void _update_custom_build_project() {
DirAccessRef da = DirAccess::open("res://android");
ERR_FAIL_COND_MSG(!da, "Cannot open directory 'res://android'.");
Map<String, List<String> > directory_paths;
Map<String, List<String> > manifest_sections;
Map<String, List<String> > gradle_sections;
da->list_dir_begin();
String d = da->get_next();
while (d != String()) {
if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir
//add directories found
DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d));
if (ds) {
ds->list_dir_begin();
String sd = ds->get_next();
while (sd != String()) {
if (!sd.begins_with(".") && ds->current_is_dir()) {
String key = sd.to_upper();
if (!directory_paths.has(key)) {
directory_paths[key] = List<String>();
}
String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd);
directory_paths[key].push_back(path);
print_line("Add: " + sd + ":" + path);
}
sd = ds->get_next();
}
ds->list_dir_end();
}
//parse manifest
{
FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ);
if (f) {
String section;
while (!f->eof_reached()) {
String l = f->get_line();
String k = l.strip_edges();
if (k.begins_with("[")) {
section = k.substr(1, k.length() - 2).strip_edges().to_upper();
print_line("Section: " + section);
} else if (k != String()) {
if (!manifest_sections.has(section)) {
manifest_sections[section] = List<String>();
}
manifest_sections[section].push_back(l);
}
}
f->close();
}
}
//parse gradle
{
FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ);
if (f) {
String section;
while (!f->eof_reached()) {
String l = f->get_line().strip_edges();
String k = l.strip_edges();
if (k.begins_with("[")) {
section = k.substr(1, k.length() - 2).strip_edges().to_upper();
print_line("Section: " + section);
} else if (k != String()) {
if (!gradle_sections.has(section)) {
gradle_sections[section] = List<String>();
}
gradle_sections[section].push_back(l);
}
}
}
}
}
d = da->get_next();
}
da->list_dir_end();
{ //fix gradle build
String new_file;
{
FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ);
if (f) {
while (!f->eof_reached()) {
String l = f->get_line();
if (l.begins_with("//CHUNK_")) {
String text = l.replace_first("//CHUNK_", "");
int begin_pos = text.find("_BEGIN");
if (begin_pos != -1) {
text = text.substr(0, begin_pos);
text = text.to_upper(); //just in case
String end_marker = "//CHUNK_" + text + "_END";
size_t pos = f->get_position();
bool found = false;
while (!f->eof_reached()) {
l = f->get_line();
if (l.begins_with(end_marker)) {
found = true;
break;
}
}
new_file += "//CHUNK_" + text + "_BEGIN\n";
if (!found) {
ERR_PRINT("No end marker found in build.gradle for chunk: " + text);
f->seek(pos);
} else {
//add chunk lines
if (gradle_sections.has(text)) {
for (List<String>::Element *E = gradle_sections[text].front(); E; E = E->next()) {
new_file += E->get() + "\n";
}
}
new_file += end_marker + "\n";
}
} else {
new_file += l + "\n"; //pass line by
}
} else if (l.begins_with("//DIR_")) {
String text = l.replace_first("//DIR_", "");
int begin_pos = text.find("_BEGIN");
if (begin_pos != -1) {
text = text.substr(0, begin_pos);
text = text.to_upper(); //just in case
String end_marker = "//DIR_" + text + "_END";
size_t pos = f->get_position();
bool found = false;
while (!f->eof_reached()) {
l = f->get_line();
if (l.begins_with(end_marker)) {
found = true;
break;
}
}
new_file += "//DIR_" + text + "_BEGIN\n";
if (!found) {
ERR_PRINT("No end marker found in build.gradle for dir: " + text);
f->seek(pos);
} else {
//add chunk lines
if (directory_paths.has(text)) {
for (List<String>::Element *E = directory_paths[text].front(); E; E = E->next()) {
new_file += ",'" + E->get().replace("'", "\'") + "'";
new_file += "\n";
}
}
new_file += end_marker + "\n";
}
} else {
new_file += l + "\n"; //pass line by
}
} else {
new_file += l + "\n";
}
}
}
}
FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE);
f->store_string(new_file);
f->close();
}
{ //fix manifest
String new_file;
{
FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ);
if (f) {
while (!f->eof_reached()) {
String l = f->get_line();
if (l.begins_with("<!--CHUNK_")) {
String text = l.replace_first("<!--CHUNK_", "");
int begin_pos = text.find("_BEGIN-->");
if (begin_pos != -1) {
text = text.substr(0, begin_pos);
text = text.to_upper(); //just in case
String end_marker = "<!--CHUNK_" + text + "_END-->";
size_t pos = f->get_position();
bool found = false;
while (!f->eof_reached()) {
l = f->get_line();
if (l.begins_with(end_marker)) {
found = true;
break;
}
}
new_file += "<!--CHUNK_" + text + "_BEGIN-->\n";
if (!found) {
ERR_PRINT("No end marker found in AndroidManifest.xml for chunk: " + text);
f->seek(pos);
} else {
//add chunk lines
if (manifest_sections.has(text)) {
for (List<String>::Element *E = manifest_sections[text].front(); E; E = E->next()) {
new_file += E->get() + "\n";
}
}
new_file += end_marker + "\n";
}
} else {
new_file += l + "\n"; //pass line by
}
} else if (l.strip_edges().begins_with("<application")) {
String last_tag = "android:icon=\"@mipmap/icon\"";
int last_tag_pos = l.find(last_tag);
if (last_tag_pos == -1) {
ERR_PRINT("Not adding application attributes as the expected tag was not found in '<application': " + last_tag);
new_file += l + "\n";
} else {
String base = l.substr(0, last_tag_pos + last_tag.length());
if (manifest_sections.has("application_attribs")) {
for (List<String>::Element *E = manifest_sections["application_attribs"].front(); E; E = E->next()) {
String to_add = E->get().strip_edges();
base += " " + to_add + " ";
}
}
base += ">\n";
new_file += base;
}
} else {
new_file += l + "\n";
}
}
}
}
FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE);
f->store_string(new_file);
f->close();
}
}
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
@ -2025,8 +1779,6 @@ public:
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
_update_custom_build_project();
OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
String build_command;
@ -2037,14 +1789,18 @@ public:
#endif
String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
String plugins = p_preset->get("custom_template/plugins");
List<String> cmdline;
cmdline.push_back("build");
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
cmdline.push_back("-Pcustom_template_plugins_dir=" + plugins_dir); // argument to specify the plugins directory.
cmdline.push_back("-Pcustom_template_plugins=" + plugins); // argument to specify the list of plugins to enable.
cmdline.push_back("-p"); // argument to specify the start directory.
cmdline.push_back(build_path); // start directory.
/*{ used for debug

View File

@ -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>

View File

@ -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,14 +7,10 @@ buildscript {
repositories {
google()
jcenter()
//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN
//CHUNK_BUILDSCRIPT_REPOSITORIES_END
}
dependencies {
classpath libraries.androidGradlePlugin
classpath libraries.kotlinGradlePlugin
//CHUNK_BUILDSCRIPT_DEPENDENCIES_BEGIN
//CHUNK_BUILDSCRIPT_DEPENDENCIES_END
}
}
@ -28,25 +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 {
@ -58,8 +61,6 @@ android {
applicationId getExportPackageName()
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN
//CHUNK_ANDROID_DEFAULTCONFIG_END
}
lintOptions {
@ -81,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 ->
@ -120,6 +97,3 @@ android {
}
}
}
//CHUNK_GLOBAL_BEGIN
//CHUNK_GLOBAL_END

View File

@ -5,7 +5,8 @@ ext.versions = [
targetSdk : 29,
buildTools : '29.0.1',
supportCoreUtils : '28.0.0',
kotlinVersion : '1.3.61'
kotlinVersion : '1.3.61',
v4Support : '28.0.0'
]
@ -13,7 +14,8 @@ ext.libraries = [
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"
kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion",
v4Support : "com.android.support:support-v4:$versions.v4Support"
]
ext.getExportPackageName = { ->
@ -25,3 +27,40 @@ ext.getExportPackageName = { ->
}
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 + "*.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
}

View File

@ -65,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')
@ -76,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'.
@ -111,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.")
}
}
@ -140,6 +174,12 @@ task cleanGodotTemplates(type: Delete) {
// Delete the library generated AAR files
delete("lib/build/outputs/aar")
// Delete the godotpayment libs directory contents
delete("plugins/godotpayment/libs")
// Delete the generated godotpayment aar
delete("plugins/godotpayment/build/outputs/aar")
// Delete the app libs directory contents
delete("app/libs")
@ -150,4 +190,6 @@ task cleanGodotTemplates(type: Delete) {
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")
}

View File

@ -3,6 +3,7 @@ apply plugin: 'com.android.library'
dependencies {
implementation libraries.supportCoreUtils
implementation libraries.kotlinStdLib
implementation libraries.v4Support
}
def pathToRootDir = "../../../../"

View File

@ -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++) {
@ -282,6 +201,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.
*/
@ -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);
}

View File

@ -175,22 +175,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

View File

@ -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() {

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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);
}

View File

@ -0,0 +1,196 @@
/*************************************************************************/
/* 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<>(Arrays.asList(enabledPluginsList));
} 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);
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);
}
}
}

View File

@ -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'])
}
}

View File

@ -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>

View File

@ -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,16 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
private PaymentsManager mPaymentManager;
private Dictionary mSkuDetails = new Dictionary();
public GodotPayment(Godot godot) {
super(godot);
onGLRegisterPluginWithGodotNative();
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 +70,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 +82,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 +175,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 +226,7 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
}
}
@Override
public void addSkuDetail(String itemJson) {
JSONObject o = null;
try {
@ -220,11 +245,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");
}
}

View File

@ -3,3 +3,4 @@ rootProject.name = "Godot"
include ':app'
include ':lib'
include ':plugins:godotpayment'

View File

@ -63,47 +63,6 @@ static Vector3 accelerometer;
static Vector3 gravity;
static Vector3 magnetometer;
static Vector3 gyroscope;
static HashMap<String, JNISingleton *> jni_singletons;
static void _initialize_java_modules() {
if (!ProjectSettings::get_singleton()->has_setting("android/modules")) {
return;
}
String modules = ProjectSettings::get_singleton()->get("android/modules");
modules = modules.strip_edges();
if (modules == String()) {
return;
}
Vector<String> mods = modules.split(",", false);
if (mods.size()) {
jobject cls = godot_java->get_class_loader();
// TODO create wrapper for class loader
JNIEnv *env = ThreadAndroid::get_env();
jclass classLoader = env->FindClass("java/lang/ClassLoader");
jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
for (int i = 0; i < mods.size(); i++) {
String m = mods[i];
print_line("Loading Android module: " + m);
jstring strClassName = env->NewStringUTF(m.utf8().get_data());
jclass singletonClass = (jclass)env->CallObjectMethod(cls, findClass, strClassName);
ERR_CONTINUE_MSG(!singletonClass, "Couldn't find singleton for class: " + m + ".");
jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;");
ERR_CONTINUE_MSG(!initialize, "Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: " + m + ".");
jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, godot_java->get_activity());
env->NewGlobalRef(obj);
}
}
}
extern "C" {
@ -199,7 +158,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
}
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
_initialize_java_modules();
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jint width, jint height) {
@ -249,6 +207,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl
}
os_android->main_loop_begin();
godot_java->on_gl_godot_main_loop_started(env);
++step;
}
@ -433,17 +392,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jc
AudioDriverAndroid::thread_func(env);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jclass clazz, jstring name, jobject p_object) {
String singname = jstring_to_string(name, env);
JNISingleton *s = memnew(JNISingleton);
s->set_instance(env->NewGlobalRef(p_object));
jni_singletons[singname] = s;
Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
ProjectSettings::get_singleton()->set(singname, s);
}
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {
String js = jstring_to_string(path, env);
@ -451,41 +399,6 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *
return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data());
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args) {
String singname = jstring_to_string(sname, env);
ERR_FAIL_COND(!jni_singletons.has(singname));
JNISingleton *s = jni_singletons.get(singname);
String mname = jstring_to_string(name, env);
String retval = jstring_to_string(ret, env);
Vector<Variant::Type> types;
String cs = "(";
int stringCount = env->GetArrayLength(args);
for (int i = 0; i < stringCount; i++) {
jstring string = (jstring)env->GetObjectArrayElement(args, i);
const String rawString = jstring_to_string(string, env);
types.push_back(get_jni_type(rawString));
cs += get_jni_sig(rawString);
}
cs += ")";
cs += get_jni_sig(retval);
jclass cls = env->GetObjectClass(s->get_instance());
jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
if (!mid) {
print_line("Failed getting method ID " + mname);
}
s->add_method(mname, mid, types, get_jni_type(retval));
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) {
Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID));

View File

@ -60,8 +60,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jclass clazz, jstring name, jobject p_object);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args);
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params);

View File

@ -66,6 +66,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z");
_vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V");
_get_input_fallback_mapping = p_env->GetMethodID(cls, "getInputFallbackMapping", "()Ljava/lang/String;");
_on_gl_godot_main_loop_started = p_env->GetMethodID(cls, "onGLGodotMainLoopStarted", "()V");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@ -107,6 +108,15 @@ void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
p_env->CallVoidMethod(godot_instance, _on_video_init);
}
void GodotJavaWrapper::on_gl_godot_main_loop_started(JNIEnv *p_env) {
if (_on_gl_godot_main_loop_started) {
if (p_env == NULL) {
p_env = ThreadAndroid::get_env();
}
}
p_env->CallVoidMethod(godot_instance, _on_gl_godot_main_loop_started);
}
void GodotJavaWrapper::restart(JNIEnv *p_env) {
if (_restart)
if (p_env == NULL)

View File

@ -61,6 +61,7 @@ private:
jmethodID _is_activity_resumed = 0;
jmethodID _vibrate = 0;
jmethodID _get_input_fallback_mapping = 0;
jmethodID _on_gl_godot_main_loop_started = 0;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance);
@ -72,6 +73,7 @@ public:
jobject get_class_loader();
void on_video_init(JNIEnv *p_env = NULL);
void on_gl_godot_main_loop_started(JNIEnv *p_env = NULL);
void restart(JNIEnv *p_env = NULL);
void force_quit(JNIEnv *p_env = NULL);
void set_keep_screen_on(bool p_enabled);

View File

@ -0,0 +1,115 @@
/*************************************************************************/
/* godot_plugin_jni.cpp */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#include "godot_plugin_jni.h"
#include <core/engine.h>
#include <core/error_macros.h>
#include <core/project_settings.h>
#include <platform/android/jni_utils.h>
#include <platform/android/string_android.h>
static HashMap<String, JNISingleton *> jni_singletons;
extern "C" {
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name) {
String singname = jstring_to_string(name, env);
JNISingleton *s = memnew(JNISingleton);
s->set_instance(env->NewGlobalRef(obj));
jni_singletons[singname] = s;
Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
ProjectSettings::get_singleton()->set(singname, s);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) {
String singname = jstring_to_string(sname, env);
ERR_FAIL_COND(!jni_singletons.has(singname));
JNISingleton *s = jni_singletons.get(singname);
String mname = jstring_to_string(name, env);
String retval = jstring_to_string(ret, env);
Vector<Variant::Type> types;
String cs = "(";
int stringCount = env->GetArrayLength(args);
for (int i = 0; i < stringCount; i++) {
jstring string = (jstring)env->GetObjectArrayElement(args, i);
const String rawString = jstring_to_string(string, env);
types.push_back(get_jni_type(rawString));
cs += get_jni_sig(rawString);
}
cs += ")";
cs += get_jni_sig(retval);
jclass cls = env->GetObjectClass(s->get_instance());
jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
if (!mid) {
print_line("Failed getting method ID " + mname);
}
s->add_method(mname, mid, types, get_jni_type(retval));
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths) {
int gdnlib_count = env->GetArrayLength(gdnlib_paths);
if (gdnlib_count == 0) {
return;
}
// Retrieve the current list of gdnative libraries.
Array singletons = Array();
if (ProjectSettings::get_singleton()->has_setting("gdnative/singletons")) {
singletons = ProjectSettings::get_singleton()->get("gdnative/singletons");
}
// Insert the libraries provided by the plugin
for (int i = 0; i < gdnlib_count; i++) {
jstring relative_path = (jstring)env->GetObjectArrayElement(gdnlib_paths, i);
String path = "res://" + jstring_to_string(relative_path, env);
if (!singletons.has(path)) {
singletons.push_back(path);
}
env->DeleteLocalRef(relative_path);
}
// Insert the updated list back into project settings.
ProjectSettings::get_singleton()->set("gdnative/singletons", singletons);
}
}

View File

@ -0,0 +1,43 @@
/*************************************************************************/
/* godot_plugin_jni.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef GODOT_PLUGIN_JNI_H
#define GODOT_PLUGIN_JNI_H
#include <android/log.h>
#include <jni.h>
extern "C" {
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name);
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths);
}
#endif // GODOT_PLUGIN_JNI_H