diff options
Diffstat (limited to 'platform/android')
10 files changed, 128 insertions, 100 deletions
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 587caf81bf..8cc2b1eb97 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -252,6 +252,7 @@ static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets"; static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets"; static const int DEFAULT_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' +static const int VULKAN_MIN_SDK_VERSION = 24; static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' #ifndef ANDROID_ENABLED @@ -899,10 +900,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p bool screen_support_large = p_preset->get("screen/support_large"); bool screen_support_xlarge = p_preset->get("screen/support_xlarge"); - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); - int hand_tracking_frequency_index = p_preset->get("xr_features/hand_tracking_frequency"); - bool backup_allowed = p_preset->get("user_data_backup/allow"); int app_category = p_preset->get("package/app_category"); bool retain_data_on_uninstall = p_preset->get("package/retain_data_on_uninstall"); @@ -1027,6 +1024,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p encode_uint32(is_resizeable, &p_manifest.write[iofs + 16]); } + if (tname == "provider" && attrname == "authorities") { + string_table.write[attr_value] = get_package_name(package_name) + String(".fileprovider"); + } + if (tname == "supports-screens") { if (attrname == "smallScreens") { encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); @@ -1042,25 +1043,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } } - // Hand tracking related configurations - if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) { - if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") { - string_table.write[attr_value] = "com.oculus.handtracking.frequency"; - } - - if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_metadata_value") { - string_table.write[attr_value] = (hand_tracking_frequency_index == XR_HAND_TRACKING_FREQUENCY_LOW ? "LOW" : "HIGH"); - } - - if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_name") { - string_table.write[attr_value] = "com.oculus.handtracking.version"; - } - - if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_version_value") { - string_table.write[attr_value] = "V2.0"; - } - } - iofs += 20; } @@ -1075,26 +1057,13 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p Vector<bool> feature_required_list; Vector<int> feature_versions; - if (xr_mode_index == XR_MODE_OPENXR) { - // Set degrees of freedom - feature_names.push_back("android.hardware.vr.headtracking"); + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool has_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile"; + if (has_vulkan) { + // Require vulkan hardware level 1 support + feature_names.push_back("android.hardware.vulkan.level"); feature_required_list.push_back(true); feature_versions.push_back(1); - - // Check for hand tracking - if (hand_tracking_index > XR_HAND_TRACKING_NONE) { - feature_names.push_back("oculus.software.handtracking"); - feature_required_list.push_back(hand_tracking_index == XR_HAND_TRACKING_REQUIRED); - feature_versions.push_back(-1); // no version attribute should be added. - } - - // Check for passthrough - int passthrough_mode = p_preset->get("xr_features/passthrough"); - if (passthrough_mode > XR_PASSTHROUGH_NONE) { - feature_names.push_back("com.oculus.feature.PASSTHROUGH"); - feature_required_list.push_back(passthrough_mode == XR_PASSTHROUGH_REQUIRED); - feature_versions.push_back(-1); - } } if (feature_names.size() > 0) { @@ -1716,6 +1685,7 @@ Vector<EditorExportPlatformAndroid::ABI> EditorExportPlatformAndroid::get_enable void EditorExportPlatformAndroid::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { r_features->push_back("etc2"); + r_features->push_back("astc"); Vector<ABI> abis = get_enabled_abis(p_preset); for (int i = 0; i < abis.size(); ++i) { @@ -2334,6 +2304,12 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit int xr_mode_index = p_preset->get("xr_features/xr_mode"); int hand_tracking = p_preset->get("xr_features/hand_tracking"); int passthrough_mode = p_preset->get("xr_features/passthrough"); + if (xr_mode_index == XR_MODE_OPENXR && !custom_build_enabled) { + valid = false; + err += TTR("OpenXR requires \"Use Custom Build\" to be enabled"); + err += "\n"; + } + if (xr_mode_index != XR_MODE_OPENXR) { if (hand_tracking > XR_HAND_TRACKING_NONE) { valid = false; @@ -2407,6 +2383,19 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit err += "\n"; } + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool uses_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile"; + if (current_renderer == "forward_plus") { + // Warning only, so don't override `valid`. + err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer); + err += "\n"; + } + if (uses_vulkan && min_sdk_int < VULKAN_MIN_SDK_VERSION) { + // Warning only, so don't override `valid`. + err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer); + err += "\n"; + } + r_error = err; return valid; } diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index e450f3edb3..7eb595f48d 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -259,8 +259,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode")); bool uses_xr = xr_mode_index == XR_MODE_OPENXR; 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 == XR_HAND_TRACKING_OPTIONAL) { manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; @@ -275,27 +273,48 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"com.oculus.feature.PASSTHROUGH\" android:required=\"true\" />\n"; } } + + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool has_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile"; + if (has_vulkan) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"true\" android:version=\"1\" />\n"; + } return manifest_xr_features; } -String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { - int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode")); - bool uses_xr = xr_mode_index == XR_MODE_OPENXR; +String _get_activity_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_xr) { String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation")))); String manifest_activity_text = vformat( " <activity android:name=\"com.godot.game.GodotApp\" " "tools:replace=\"android:screenOrientation,android:excludeFromRecents,android:resizeableActivity\" " + "tools:node=\"mergeOnlyAttributes\" " "android:excludeFromRecents=\"%s\" " "android:screenOrientation=\"%s\" " "android:resizeableActivity=\"%s\">\n", bool_to_string(p_preset->get("package/exclude_from_recents")), orientation, bool_to_string(bool(GLOBAL_GET("display/window/size/resizable")))); - if (uses_xr) { - manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n"; + + if (p_uses_xr) { + manifest_activity_text += " <intent-filter>\n" + " <action android:name=\"android.intent.action.MAIN\" />\n" + " <category android:name=\"android.intent.category.LAUNCHER\" />\n" + "\n" + " <!-- Enable access to OpenXR on Oculus mobile devices, no-op on other Android\n" + " platforms. -->\n" + " <category android:name=\"com.oculus.intent.category.VR\" />\n" + "\n" + " <!-- OpenXR category tag to indicate the activity starts in an immersive OpenXR mode. \n" + " See https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#android-runtime-category. -->\n" + " <category android:name=\"org.khronos.openxr.intent.category.IMMERSIVE_HMD\" />\n" + " </intent-filter>\n"; } else { - manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; + manifest_activity_text += " <intent-filter>\n" + " <action android:name=\"android.intent.action.MAIN\" />\n" + " <category android:name=\"android.intent.category.LAUNCHER\" />\n" + " </intent-filter>\n"; } + manifest_activity_text += " </activity>\n"; return manifest_activity_text; } @@ -316,9 +335,7 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_ " android:hasFragileUserData=\"%s\"\n" " android:requestLegacyExternalStorage=\"%s\"\n" " tools:replace=\"android:allowBackup,android:appCategory,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n" - " tools:ignore=\"GoogleAppIndexingWarning\">\n\n" - " <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_version_name\" />\n" - " <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_metadata_name\" />\n", + " tools:ignore=\"GoogleAppIndexingWarning\">\n\n", bool_to_string(p_preset->get("user_data_backup/allow")), _get_app_category_label(app_category_index), bool_to_string(is_game), @@ -335,10 +352,8 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_ hand_tracking_frequency); manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />\n"; } - } else { - manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n"; } - manifest_application_text += _get_activity_tag(p_preset); + manifest_application_text += _get_activity_tag(p_preset, uses_xr); manifest_application_text += " </application>\n"; return manifest_application_text; } diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 0fa857cb75..fe5888e11c 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -118,7 +118,7 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset); String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset); -String _get_activity_tag(const Ref<EditorExportPreset> &p_preset); +String _get_activity_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_xr); String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission); diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 8c8608cbbb..ce4a2ecfe4 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -31,29 +31,6 @@ android:name="org.godotengine.editor.version" android:value="${godotEditorVersion}" /> - <!-- 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. --> - - <!-- XR hand tracking metadata --> - <!-- This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. --> - <!-- Removed at export time if the xr mode is not VR or hand tracking is disabled. --> - <meta-data - android:name="xr_hand_tracking_metadata_name" - android:value="xr_hand_tracking_metadata_value"/> - - <!-- XR hand tracking version --> - <!-- This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. --> - <!-- Removed at export time if the xr mode is not VR or hand tracking is disabled. --> - <meta-data - android:name="xr_hand_tracking_version_name" - android:value="xr_hand_tracking_version_value"/> - - <!-- Supported Meta devices --> - <!-- This is removed by the exporter if the xr mode is not VR. --> - <meta-data - android:name="com.oculus.supportedDevices" - android:value="all" /> - <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" @@ -66,16 +43,9 @@ android:resizeableActivity="false" tools:ignore="UnusedAttribute" > - <!-- Focus awareness metadata is removed at export time if the xr mode is not VR. --> - <meta-data android:name="com.oculus.vr.focusaware" android:value="true" /> - <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/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml index 1f77e2fc34..f03a1dd47a 100644 --- a/platform/android/java/lib/AndroidManifest.xml +++ b/platform/android/java/lib/AndroidManifest.xml @@ -20,6 +20,16 @@ android:exported="false" /> + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.fileprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/godot_provider_paths" /> + </provider> + </application> </manifest> diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml index 7efac4ce71..f76f597140 100644 --- a/platform/android/java/lib/res/values/strings.xml +++ b/platform/android/java/lib/res/values/strings.xml @@ -14,6 +14,7 @@ <string name="text_button_cancel_verify">Cancel Verification</string> <string name="text_error_title">Error!</string> <string name="error_engine_setup_message">Unable to setup the Godot Engine! Aborting…</string> + <string name="error_missing_vulkan_requirements_message">This device does not meet the requirements for Vulkan support! Aborting…</string> <!-- APK Expansion Strings --> diff --git a/platform/android/java/lib/res/xml/godot_provider_paths.xml b/platform/android/java/lib/res/xml/godot_provider_paths.xml new file mode 100644 index 0000000000..1255f576bf --- /dev/null +++ b/platform/android/java/lib/res/xml/godot_provider_paths.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + + <external-path + name="public" + path="." /> + + <external-files-path + name="app" + path="." /> +</paths> 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 65032d6a68..677c9d8f13 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -99,7 +99,7 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God // 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). + // releasing and reloading native libs or resetting their state somehow and clearing static data). Log.v(TAG, "Restarting Godot instance..."); ProcessPhoenix.triggerRebirth(FullScreenGodotApp.this); } 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 50263bc392..6296ee2c22 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -258,13 +258,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC */ @Keep private boolean onVideoInit() { - final Activity activity = getActivity(); + final Activity activity = requireActivity(); containerLayout = new FrameLayout(activity); containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // GodotEditText layout GodotEditText editText = new GodotEditText(activity); - editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, + editText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, (int)getResources().getDimension(R.dimen.text_edit_height))); // ...add to FrameLayout containerLayout.addView(editText); @@ -279,6 +279,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC if (renderer.equals("gl_compatibility")) { mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl); } else { + if (!meetsVulkanRequirements(activity.getPackageManager())) { + Log.e(TAG, "Missing requirements for vulkan support! Aborting..."); + alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit); + return false; + } mRenderView = new GodotVulkanRenderView(activity, this); } @@ -317,6 +322,17 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC return true; } + /** + * Returns true if the device meets the base requirements for Vulkan support, false otherwise. + */ + private boolean meetsVulkanRequirements(@Nullable PackageManager packageManager) { + if (packageManager == null) { + return false; + } + + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1); + } + public void setKeepScreenOn(final boolean p_enabled) { runOnUiThread(() -> { if (p_enabled) { 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 41d06a6458..edcd9c4d1f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -49,6 +49,9 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.WindowInsets; +import androidx.core.content.FileProvider; + +import java.io.File; import java.util.List; import java.util.Locale; @@ -84,29 +87,42 @@ public class GodotIO { // MISCELLANEOUS OS IO ///////////////////////// - public int openURI(String p_uri) { + public int openURI(String uriString) { try { - String path = p_uri; - String type = ""; - if (path.startsWith("/")) { - //absolute path to filesystem, prepend file:// - path = "file://" + path; - if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) { - type = "image/*"; + Uri dataUri; + String dataType = ""; + boolean grantReadUriPermission = false; + + if (uriString.startsWith("/") || uriString.startsWith("file://")) { + String filePath = uriString; + // File uris needs to be provided via the FileProvider + grantReadUriPermission = true; + if (filePath.startsWith("file://")) { + filePath = filePath.replace("file://", ""); } + + File targetFile = new File(filePath); + dataUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", targetFile); + dataType = activity.getContentResolver().getType(dataUri); + } else { + dataUri = Uri.parse(uriString); } Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); - if (!type.equals("")) { - intent.setDataAndType(Uri.parse(path), type); + if (TextUtils.isEmpty(dataType)) { + intent.setData(dataUri); } else { - intent.setData(Uri.parse(path)); + intent.setDataAndType(dataUri, dataType); + } + if (grantReadUriPermission) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } activity.startActivity(intent); return 0; } catch (ActivityNotFoundException e) { + Log.e(TAG, "Unable to open uri " + uriString, e); return 1; } } |