summaryrefslogtreecommitdiff
path: root/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/README.md14
-rw-r--r--platform/android/android_input_handler.cpp2
-rw-r--r--platform/android/detect.py2
-rw-r--r--platform/android/dir_access_jandroid.cpp2
-rw-r--r--platform/android/export/export.cpp2
-rw-r--r--platform/android/export/export_plugin.cpp31
-rw-r--r--platform/android/export/export_plugin.h2
-rw-r--r--platform/android/export/gradle_export_util.cpp14
-rw-r--r--platform/android/export/gradle_export_util.h2
-rw-r--r--platform/android/java/app/AndroidManifest.xml5
-rw-r--r--platform/android/java/app/build.gradle3
-rw-r--r--platform/android/java/app/config.gradle14
-rw-r--r--platform/android/java/app/gradle.properties4
-rw-r--r--platform/android/java/gradle.properties4
-rw-r--r--platform/android/java/lib/build.gradle3
-rw-r--r--platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java21
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java31
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java247
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java14
-rw-r--r--platform/android/java_godot_io_wrapper.cpp20
-rw-r--r--platform/android/java_godot_io_wrapper.h6
-rw-r--r--platform/android/logo.pngbin951 -> 968 bytes
-rw-r--r--platform/android/os_android.cpp18
-rw-r--r--platform/android/os_android.h5
26 files changed, 201 insertions, 273 deletions
diff --git a/platform/android/README.md b/platform/android/README.md
new file mode 100644
index 0000000000..343e588553
--- /dev/null
+++ b/platform/android/README.md
@@ -0,0 +1,14 @@
+# Android platform port
+
+This folder contains the Java and C++ (JNI) code for the Android platform port,
+using [Gradle](https://gradle.org/) as a build system.
+
+## Artwork license
+
+[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under
+[Creative Commons Attribution 3.0 Unported](https://developer.android.com/distribute/marketing-tools/brand-guidelines#android_robot)
+per the Android logo usage guidelines:
+
+> The Android robot is reproduced or modified from work created and shared by
+> Google and used according to terms described in the Creative Commons 3.0
+> Attribution License.
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index b9004c4989..e03375e8d9 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -377,7 +377,7 @@ MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int a
if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) {
godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1;
}
- if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) {
+ if (android_button_mask & AMOTION_EVENT_BUTTON_FORWARD) {
godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2;
}
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 7a993e9ca6..61ccad9ac3 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -93,7 +93,7 @@ def configure(env):
install_ndk_if_needed(env)
# Workaround for MinGW. See:
- # http://www.scons.org/wiki/LongCmdLinesOnWin32
+ # https://www.scons.org/wiki/LongCmdLinesOnWin32
if os.name == "nt":
import subprocess
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 0bae090702..0eeee8215d 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -161,7 +161,7 @@ bool DirAccessJAndroid::dir_exists(String p_dir) {
if (current_dir == "")
sd = p_dir;
else {
- if (p_dir.is_rel_path())
+ if (p_dir.is_relative_path())
sd = current_dir.plus_file(p_dir);
else
sd = fix_path(p_dir);
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index fc86abb6f1..8df61831c2 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -48,6 +48,8 @@ void register_android_exporter() {
EDITOR_DEF("export/android/shutdown_adb_on_exit", true);
+ EDITOR_DEF("export/android/one_click_deploy_clear_previous_install", false);
+
Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid));
EditorExport::get_singleton()->add_export_platform(exporter);
}
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 17ee173855..e5422a28af 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -717,6 +717,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared
return OK;
}
+bool EditorExportPlatformAndroid::_has_storage_permission(const Vector<String> &p_permissions) {
+ return p_permissions.find("android.permission.READ_EXTERNAL_STORAGE") != -1 || p_permissions.find("android.permission.WRITE_EXTERNAL_STORAGE") != -1;
+}
+
void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
const char **aperms = android_perms;
while (*aperms) {
@@ -763,12 +767,17 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
Vector<String> perms;
_get_permissions(p_preset, p_give_internet, perms);
for (int i = 0; i < perms.size(); i++) {
- manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
+ String permission = perms.get(i);
+ if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission);
+ } else {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", permission);
+ }
}
manifest_text += _get_xr_features_tag(p_preset);
manifest_text += _get_instrumentation_tag(p_preset);
- manifest_text += _get_application_tag(p_preset);
+ manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms));
manifest_text += "</manifest>\n";
String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
@@ -824,6 +833,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
Vector<String> perms;
// Write permissions into the perms variable.
_get_permissions(p_preset, p_give_internet, perms);
+ bool has_storage_permission = _has_storage_permission(perms);
while (ofs < (uint32_t)p_manifest.size()) {
uint32_t chunk = decode_uint32(&p_manifest[ofs]);
@@ -913,6 +923,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
}
}
+ if (tname == "application" && attrname == "requestLegacyExternalStorage") {
+ encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+ }
+
if (tname == "application" && attrname == "allowBackup") {
encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]);
}
@@ -963,6 +977,11 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
Vector<int> feature_versions;
if (xr_mode_index == 1 /* XRMode.OVR */) {
+ // Set degrees of freedom
+ feature_names.push_back("android.hardware.vr.headtracking");
+ feature_required_list.push_back(true);
+ feature_versions.push_back(1);
+
// Check for hand tracking
int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
if (hand_tracking_index > 0) {
@@ -1632,8 +1651,6 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false));
-
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"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname"));
@@ -1780,7 +1797,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
int rv;
String output;
- bool remove_prev = p_preset->get("one_click_deploy/clear_previous_install");
+ bool remove_prev = EDITOR_GET("export/android/one_click_deploy_clear_previous_install");
String version_name = p_preset->get("version/name");
String package_name = p_preset->get("package/unique_name");
@@ -2597,7 +2614,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
String export_filename = p_path.get_file();
String export_path = p_path.get_base_dir();
- if (export_path.is_rel_path()) {
+ if (export_path.is_relative_path()) {
export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path);
}
export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path();
@@ -2925,7 +2942,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
void EditorExportPlatformAndroid::get_platform_features(List<String> *r_features) {
r_features->push_back("mobile");
- r_features->push_back("Android");
+ r_features->push_back("android");
}
void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 909428c2fe..b061ee4e04 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -134,6 +134,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so);
+ bool _has_storage_permission(const Vector<String> &p_permissions);
+
void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions);
void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index 76512226bf..6fbdf73cd0 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -197,6 +197,8 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
String manifest_xr_features;
bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
if (uses_xr) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
+
int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
if (hand_tracking_index == 1) {
manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
@@ -228,25 +230,29 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
"tools:replace=\"android:screenOrientation\" "
"android:screenOrientation=\"%s\">\n",
orientation);
- if (!uses_xr) {
+ if (uses_xr) {
+ manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n";
+ } else {
manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
}
manifest_activity_text += " </activity>\n";
return manifest_activity_text;
}
-String _get_application_tag(const Ref<EditorExportPreset> &p_preset) {
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission) {
String manifest_application_text = vformat(
" <application android:label=\"@string/godot_project_name_string\"\n"
" android:allowBackup=\"%s\"\n"
" android:icon=\"@mipmap/icon\"\n"
" android:isGame=\"%s\"\n"
" android:hasFragileUserData=\"%s\"\n"
- " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData\"\n"
+ " android:requestLegacyExternalStorage=\"%s\"\n"
+ " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"
" tools:ignore=\"GoogleAppIndexingWarning\">\n\n",
bool_to_string(p_preset->get("user_data_backup/allow")),
bool_to_string(p_preset->get("package/classify_as_game")),
- bool_to_string(p_preset->get("package/retain_data_on_uninstall")));
+ bool_to_string(p_preset->get("package/retain_data_on_uninstall")),
+ bool_to_string(p_has_storage_permission));
manifest_application_text += _get_activity_tag(p_preset);
manifest_application_text += " </application>\n";
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index 44e9a1727d..8a93c25d79 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -81,6 +81,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset);
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset);
-String _get_application_tag(const Ref<EditorExportPreset> &p_preset);
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);
#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 467a0dc3c0..d7bf6cef30 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -22,6 +22,7 @@
android:icon="@mipmap/icon"
android:isGame="true"
android:hasFragileUserData="false"
+ android:requestLegacyExternalStorage="false"
tools:ignore="GoogleAppIndexingWarning" >
<!-- Records the version of the Godot editor used for building -->
@@ -48,6 +49,10 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
+
+ <!-- Enable access to OpenXR on Oculus mobile devices, no-op on other Android
+ platforms. -->
+ <category android:name="com.oculus.intent.category.VR" />
</intent-filter>
</activity>
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 18e07c3762..9640887399 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -34,9 +34,8 @@ allprojects {
}
dependencies {
- implementation libraries.supportCoreUtils
implementation libraries.kotlinStdLib
- implementation libraries.v4Support
+ implementation libraries.androidxFragment
if (rootProject.findProject(":lib")) {
implementation project(":lib")
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 81fc87b7ef..fcee54e493 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,12 +1,11 @@
ext.versions = [
- androidGradlePlugin: '4.2.1',
- compileSdk : 29,
- minSdk : 18,
- targetSdk : 29,
+ androidGradlePlugin: '4.2.2',
+ compileSdk : 30,
+ minSdk : 19,
+ targetSdk : 30,
buildTools : '30.0.3',
- supportCoreUtils : '1.0.0',
kotlinVersion : '1.5.10',
- v4Support : '1.0.0',
+ fragmentVersion : '1.3.6',
javaVersion : 1.8,
ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated.
@@ -14,10 +13,9 @@ ext.versions = [
ext.libraries = [
androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin",
- supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils",
kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion",
kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion",
- v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support"
+ androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion",
]
ext.getExportPackageName = { ->
diff --git a/platform/android/java/app/gradle.properties b/platform/android/java/app/gradle.properties
index 19587bd81f..0ad8e611ca 100644
--- a/platform/android/java/app/gradle.properties
+++ b/platform/android/java/app/gradle.properties
@@ -4,7 +4,7 @@
# where otherwise specified.
# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
+# https://www.gradle.org/docs/current/userguide/build_environment.html
android.enableJetifier=true
android.useAndroidX=true
@@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx4536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.warning.mode=all
diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties
index b51a19a005..5cd94e85d9 100644
--- a/platform/android/java/gradle.properties
+++ b/platform/android/java/gradle.properties
@@ -7,7 +7,7 @@
# any settings specified in this file.
# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
+# https://www.gradle.org/docs/current/userguide/build_environment.html
android.enableJetifier=true
android.useAndroidX=true
@@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx4536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.warning.mode=all
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 663ba73d40..fbed4ed078 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -2,9 +2,8 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
dependencies {
- implementation libraries.supportCoreUtils
implementation libraries.kotlinStdLib
- implementation libraries.v4Support
+ implementation libraries.androidxFragment
}
def pathToRootDir = "../../../../"
diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java
index 2a72c9818d..9aa65fd786 100644
--- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java
+++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java
@@ -54,7 +54,7 @@ public class Helpers {
/*
* Parse the Content-Disposition HTTP Header. The format of the header is defined here:
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
+ * https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
* content that is going to be downloaded to the file system. We only support the attachment
* type.
*/
diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
index ad7048cbf3..3600706c7c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -30,6 +30,7 @@
package org.godotengine.godot;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
@@ -86,6 +87,26 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
}
@Override
+ public final void onGodotRestartRequested(Godot instance) {
+ if (instance == godotFragment) {
+ // HACK:
+ //
+ // Currently it's very hard to properly deinitialize Godot on Android to restart the game
+ // from scratch. Therefore, we need to kill the whole app process and relaunch it.
+ //
+ // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
+ // releasing and reloading native libs or resetting their state somehow and clearing statics).
+ //
+ // Using instrumentation is a way of making the whole app process restart, because Android
+ // will kill any process of the same package which was already running.
+ //
+ Bundle args = new Bundle();
+ args.putParcelable("intent", getIntent());
+ startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args);
+ }
+ }
+
+ @Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (godotFragment != null) {
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 76751a886c..70bc73b9ad 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -49,7 +49,6 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -68,15 +67,12 @@ import android.os.Environment;
import android.os.Messenger;
import android.os.VibrationEffect;
import android.os.Vibrator;
-import android.provider.Settings.Secure;
import android.view.Display;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
@@ -299,7 +295,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
View pluginView = plugin.onMainCreate(activity);
if (pluginView != null) {
- containerLayout.addView(pluginView);
+ if (plugin.shouldBeOnTop()) {
+ containerLayout.addView(pluginView);
+ } else {
+ containerLayout.addView(pluginView, 0);
+ }
}
}
}
@@ -321,7 +321,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@SuppressLint("MissingPermission")
@Keep
private void vibrate(int durationMs) {
- if (requestPermission("VIBRATE")) {
+ if (durationMs > 0 && requestPermission("VIBRATE")) {
Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -335,22 +335,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
public void restart() {
- // HACK:
- //
- // Currently it's very hard to properly deinitialize Godot on Android to restart the game
- // from scratch. Therefore, we need to kill the whole app process and relaunch it.
- //
- // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
- // releasing and reloading native libs or resetting their state somehow and clearing statics).
- //
- // Using instrumentation is a way of making the whole app process restart, because Android
- // will kill any process of the same package which was already running.
- //
- final Activity activity = getActivity();
- if (activity != null) {
- Bundle args = new Bundle();
- args.putParcelable("intent", mCurrentIntent);
- activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args);
+ if (godotHost != null) {
+ godotHost.onGodotRestartRequested(this);
}
}
@@ -471,7 +457,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
io = new GodotIO(activity);
- io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);
GodotLib.io = io;
netUtils = new GodotNetUtils(activity);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
index 58e982c569..7b22895994 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -58,4 +58,10 @@ public interface GodotHost {
* Invoked on the UI thread as the last step of the Godot instance clean up phase.
*/
default void onGodotForceQuit(Godot instance) {}
+
+ /**
+ * Invoked on the GL thread when the Godot instance wants to be restarted. It's up to the host
+ * to perform the appropriate action(s).
+ */
+ default void onGodotRestartRequested(Godot instance) {}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 66882e8e72..d85d88ec6c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -30,15 +30,19 @@
package org.godotengine.godot;
-import org.godotengine.godot.input.*;
+import org.godotengine.godot.input.GodotEditText;
import android.app.Activity;
-import android.content.*;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Point;
import android.net.Uri;
-import android.os.*;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
@@ -47,14 +51,16 @@ import android.view.DisplayCutout;
import android.view.WindowInsets;
import java.io.IOException;
-import java.io.InputStream;
import java.util.Locale;
// Wrapper for native library
public class GodotIO {
- AssetManager am;
- final Activity activity;
+ private static final String TAG = GodotIO.class.getSimpleName();
+
+ private final AssetManager am;
+ private final Activity activity;
+ private final String uniqueId;
GodotEditText edit;
final int SCREEN_LANDSCAPE = 0;
@@ -66,167 +72,6 @@ public class GodotIO {
final int SCREEN_SENSOR = 6;
/////////////////////////
- /// FILES
- /////////////////////////
-
- public int last_file_id = 1;
-
- static class AssetData {
- public boolean eof = false;
- public String path;
- public InputStream is;
- public int len;
- public int pos;
- }
-
- SparseArray<AssetData> streams;
-
- public int file_open(String path, boolean write) {
- //System.out.printf("file_open: Attempt to Open %s\n",path);
-
- //Log.v("MyApp", "TRYING TO OPEN FILE: " + path);
- if (write)
- return -1;
-
- AssetData ad = new AssetData();
-
- try {
- ad.is = am.open(path);
-
- } catch (Exception e) {
- //System.out.printf("Exception on file_open: %s\n",path);
- return -1;
- }
-
- try {
- ad.len = ad.is.available();
- } catch (Exception e) {
- System.out.printf("Exception availabling on file_open: %s\n", path);
- return -1;
- }
-
- ad.path = path;
- ad.pos = 0;
- ++last_file_id;
- streams.put(last_file_id, ad);
-
- return last_file_id;
- }
- public int file_get_size(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_get_size: Invalid file id: %d\n", id);
- return -1;
- }
-
- return streams.get(id).len;
- }
- public void file_seek(int id, int bytes) {
- if (streams.get(id) == null) {
- System.out.printf("file_get_size: Invalid file id: %d\n", id);
- return;
- }
- //seek sucks
- AssetData ad = streams.get(id);
- if (bytes > ad.len)
- bytes = ad.len;
- if (bytes < 0)
- bytes = 0;
-
- try {
- if (bytes > (int)ad.pos) {
- int todo = bytes - (int)ad.pos;
- while (todo > 0) {
- todo -= ad.is.skip(todo);
- }
- ad.pos = bytes;
- } else if (bytes < (int)ad.pos) {
- ad.is = am.open(ad.path);
-
- ad.pos = bytes;
- int todo = bytes;
- while (todo > 0) {
- todo -= ad.is.skip(todo);
- }
- }
-
- ad.eof = false;
- } catch (IOException e) {
- System.out.printf("Exception on file_seek: %s\n", e);
- return;
- }
- }
-
- public int file_tell(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id);
- return 0;
- }
-
- AssetData ad = streams.get(id);
- return ad.pos;
- }
- public boolean file_eof(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id);
- return false;
- }
-
- AssetData ad = streams.get(id);
- return ad.eof;
- }
-
- public byte[] file_read(int id, int bytes) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't read invalid file id: %d\n", id);
- return new byte[0];
- }
-
- AssetData ad = streams.get(id);
-
- if (ad.pos + bytes > ad.len) {
- bytes = ad.len - ad.pos;
- ad.eof = true;
- }
-
- if (bytes == 0) {
- return new byte[0];
- }
-
- byte[] buf1 = new byte[bytes];
- int r = 0;
- try {
- r = ad.is.read(buf1);
- } catch (IOException e) {
- System.out.printf("Exception on file_read: %s\n", e);
- return new byte[bytes];
- }
-
- if (r == 0) {
- return new byte[0];
- }
-
- ad.pos += r;
-
- if (r < bytes) {
- byte[] buf2 = new byte[r];
- for (int i = 0; i < r; i++)
- buf2[i] = buf1[i];
- return buf2;
- } else {
- return buf1;
- }
- }
-
- public void file_close(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_close: Can't close invalid file id: %d\n", id);
- return;
- }
-
- streams.remove(id);
- }
-
- /////////////////////////
/// DIRECTORIES
/////////////////////////
@@ -236,9 +81,9 @@ public class GodotIO {
public String path;
}
- public int last_dir_id = 1;
+ private int last_dir_id = 1;
- SparseArray<AssetDir> dirs;
+ private final SparseArray<AssetDir> dirs;
public int dir_open(String path) {
AssetDir ad = new AssetDir();
@@ -257,7 +102,6 @@ public class GodotIO {
return -1;
}
- //System.out.printf("Opened dir: %s\n",path);
++last_dir_id;
dirs.put(last_dir_id, ad);
@@ -320,9 +164,14 @@ public class GodotIO {
GodotIO(Activity p_activity) {
am = p_activity.getAssets();
activity = p_activity;
- //streams = new HashMap<Integer, AssetData>();
- streams = new SparseArray<>();
dirs = new SparseArray<>();
+ String androidId = Settings.Secure.getString(activity.getContentResolver(),
+ Settings.Secure.ANDROID_ID);
+ if (androidId == null) {
+ androidId = "";
+ }
+
+ uniqueId = androidId;
}
/////////////////////////
@@ -331,7 +180,6 @@ public class GodotIO {
public int openURI(String p_uri) {
try {
- Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);
String path = p_uri;
String type = "";
if (path.startsWith("/")) {
@@ -357,12 +205,12 @@ public class GodotIO {
}
}
- public String getDataDir() {
- return activity.getFilesDir().getAbsolutePath();
+ public String getCacheDir() {
+ return activity.getCacheDir().getAbsolutePath();
}
- public String getExternalDataDir() {
- return activity.getExternalFilesDir(null).getAbsolutePath();
+ public String getDataDir() {
+ return activity.getFilesDir().getAbsolutePath();
}
public String getLocale() {
@@ -456,51 +304,58 @@ public class GodotIO {
public static final int SYSTEM_DIR_PICTURES = 6;
public static final int SYSTEM_DIR_RINGTONES = 7;
- public String getSystemDir(int idx) {
- String what = "";
+ public String getSystemDir(int idx, boolean shared_storage) {
+ String what;
switch (idx) {
- case SYSTEM_DIR_DESKTOP: {
- //what=Environment.DIRECTORY_DOCUMENTS;
- what = Environment.DIRECTORY_DOWNLOADS;
+ case SYSTEM_DIR_DESKTOP:
+ default: {
+ what = null; // This leads to the app specific external root directory.
} break;
+
case SYSTEM_DIR_DCIM: {
what = Environment.DIRECTORY_DCIM;
-
} break;
+
case SYSTEM_DIR_DOCUMENTS: {
- what = Environment.DIRECTORY_DOWNLOADS;
- //what=Environment.DIRECTORY_DOCUMENTS;
+ what = Environment.DIRECTORY_DOCUMENTS;
} break;
+
case SYSTEM_DIR_DOWNLOADS: {
what = Environment.DIRECTORY_DOWNLOADS;
-
} break;
+
case SYSTEM_DIR_MOVIES: {
what = Environment.DIRECTORY_MOVIES;
-
} break;
+
case SYSTEM_DIR_MUSIC: {
what = Environment.DIRECTORY_MUSIC;
} break;
+
case SYSTEM_DIR_PICTURES: {
what = Environment.DIRECTORY_PICTURES;
} break;
+
case SYSTEM_DIR_RINGTONES: {
what = Environment.DIRECTORY_RINGTONES;
-
} break;
}
- if (what.equals(""))
- return "";
- return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ if (shared_storage) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Log.w(TAG, "Shared storage access is limited on Android 10 and higher.");
+ }
+ if (TextUtils.isEmpty(what)) {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ } else {
+ return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ }
+ } else {
+ return activity.getExternalFilesDir(what).getAbsolutePath();
+ }
}
- protected static final String PREFS_FILE = "device_id.xml";
- protected static final String PREFS_DEVICE_ID = "device_id";
-
- public static String unique_id = "";
public String getUniqueID() {
- return unique_id;
+ return uniqueId;
}
}
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
index 2dc8359615..4536c21ed3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -191,6 +191,9 @@ public abstract class GodotPlugin {
* The plugin can return a non-null {@link View} layout in order to add it to the Godot view
* hierarchy.
*
+ * Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind
+ * the main Godot view.
+ *
* @see Activity#onCreate(Bundle)
* @return the plugin's view to be included; null if no views should be included.
*/
@@ -311,6 +314,17 @@ public abstract class GodotPlugin {
}
/**
+ * Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on
+ * top of the main Godot view.
+ *
+ * Returning false causes the plugin's {@link View} to be placed behind, which can be useful
+ * when used with transparency in order to let the Godot view handle inputs.
+ */
+ public boolean shouldBeOnTop() {
+ return true;
+ }
+
+ /**
* 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.
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 5e99135498..5cd2c382d2 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -48,8 +48,8 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
}
_open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I");
+ _get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;");
_get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;");
- _get_external_data_dir = p_env->GetMethodID(cls, "getExternalDataDir", "()Ljava/lang/String;");
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
@@ -59,7 +59,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
- _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;");
+ _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
}
}
@@ -82,22 +82,22 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
}
}
-String GodotIOJavaWrapper::get_user_data_dir() {
- if (_get_data_dir) {
+String GodotIOJavaWrapper::get_cache_dir() {
+ if (_get_cache_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String());
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir);
return jstring_to_string(s, env);
} else {
return String();
}
}
-String GodotIOJavaWrapper::get_external_data_dir() {
- if (_get_external_data_dir) {
+String GodotIOJavaWrapper::get_user_data_dir() {
+ if (_get_data_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String());
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_external_data_dir);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);
return jstring_to_string(s, env);
} else {
return String();
@@ -200,11 +200,11 @@ int GodotIOJavaWrapper::get_screen_orientation() {
}
}
-String GodotIOJavaWrapper::get_system_dir(int p_dir) {
+String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
if (_get_system_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String("."));
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage);
return jstring_to_string(s, env);
} else {
return String(".");
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index e4c0a4b2c7..8f6d7f813f 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -46,8 +46,8 @@ private:
jclass cls;
jmethodID _open_URI = 0;
+ jmethodID _get_cache_dir = 0;
jmethodID _get_data_dir = 0;
- jmethodID _get_external_data_dir = 0;
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
@@ -66,8 +66,8 @@ public:
jobject get_instance();
Error open_uri(const String &p_uri);
+ String get_cache_dir();
String get_user_data_dir();
- String get_external_data_dir();
String get_locale();
String get_model();
int get_screen_dpi();
@@ -80,7 +80,7 @@ public:
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
int get_screen_orientation();
- String get_system_dir(int p_dir);
+ String get_system_dir(int p_dir, bool p_shared_storage);
};
#endif /* !JAVA_GODOT_IO_WRAPPER_H */
diff --git a/platform/android/logo.png b/platform/android/logo.png
index f44d360a25..9c8be93646 100644
--- a/platform/android/logo.png
+++ b/platform/android/logo.png
Binary files differ
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index c2e12442b3..21fb31d991 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -213,6 +213,10 @@ String OS_Android::get_model_name() const {
return OS_Unix::get_model_name();
}
+String OS_Android::get_data_path() const {
+ return get_user_data_dir();
+}
+
String OS_Android::get_user_data_dir() const {
if (data_dir_cache != String())
return data_dir_cache;
@@ -225,11 +229,11 @@ String OS_Android::get_user_data_dir() const {
return ".";
}
-String OS_Android::get_external_data_dir() const {
- String data_dir = godot_io_java->get_external_data_dir();
- if (data_dir != "") {
- data_dir = _remove_symlink(data_dir);
- return data_dir;
+String OS_Android::get_cache_path() const {
+ String cache_dir = godot_io_java->get_cache_dir();
+ if (cache_dir != "") {
+ cache_dir = _remove_symlink(cache_dir);
+ return cache_dir;
}
return ".";
}
@@ -242,8 +246,8 @@ String OS_Android::get_unique_id() const {
return OS::get_unique_id();
}
-String OS_Android::get_system_dir(SystemDir p_dir) const {
- return godot_io_java->get_system_dir(p_dir);
+String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
+ return godot_io_java->get_system_dir(p_dir, p_shared_storage);
}
void OS_Android::set_display_size(const Size2i &p_size) {
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index a5b995a775..c938297821 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -110,14 +110,15 @@ public:
virtual Error shell_open(String p_uri) override;
virtual String get_user_data_dir() const override;
- virtual String get_external_data_dir() const override;
+ virtual String get_data_path() const override;
+ virtual String get_cache_path() const override;
virtual String get_resource_dir() const override;
virtual String get_locale() const override;
virtual String get_model_name() const override;
virtual String get_unique_id() const override;
- virtual String get_system_dir(SystemDir p_dir) const override;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
void vibrate_handheld(int p_duration_ms) override;