diff options
Diffstat (limited to 'platform/android')
16 files changed, 358 insertions, 61 deletions
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 94dffd8a84..6d021ad33a 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -766,17 +766,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { uint32_t attr_value = decode_uint32(&p_manifest[iofs + 8]); uint32_t attr_resid = decode_uint32(&p_manifest[iofs + 16]); - String value; - if (attr_value != 0xFFFFFFFF) - value = string_table[attr_value]; - else - value = "Res #" + itos(attr_resid); + const String value = (attr_value != 0xFFFFFFFF) ? string_table[attr_value] : "Res #" + itos(attr_resid); String attrname = string_table[attr_name]; - String nspace; - if (attr_nspace != 0xFFFFFFFF) - nspace = string_table[attr_nspace]; - else - nspace = ""; + const String nspace = (attr_nspace != 0xFFFFFFFF) ? string_table[attr_nspace] : ""; //replace project information if (tname == "manifest" && attrname == "package") { @@ -828,14 +820,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { encode_uint32(min_gles3 ? 0x00030000 : 0x00020000, &p_manifest.write[iofs + 16]); } - if (tname == "meta-data" && attrname == "name" && string_table[attr_value] == "xr_mode_metadata_name") { + // FIXME: `attr_value != 0xFFFFFFFF` below added as a stopgap measure for GH-32553, + // but the issue should be debugged further and properly addressed. + if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") { // Update the meta-data 'android:name' attribute based on the selected XR mode. if (xr_mode_index == 1 /* XRMode.OVR */) { string_table.write[attr_value] = "com.samsung.android.vr.application.mode"; } } - if (tname == "meta-data" && attrname == "value" && string_table[attr_value] == "xr_mode_metadata_value") { + if (tname == "meta-data" && attrname == "value" && value == "xr_mode_metadata_value") { // Update the meta-data 'android:value' attribute based on the selected XR mode. if (xr_mode_index == 1 /* XRMode.OVR */) { string_table.write[attr_value] = "vr_only"; @@ -1290,7 +1284,7 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false)); @@ -1351,7 +1345,7 @@ public: return logo; } - virtual bool poll_devices() { + virtual bool poll_export() { bool dc = devices_changed; if (dc) { @@ -1361,7 +1355,7 @@ public: return dc; } - virtual int get_device_count() const { + virtual int get_options_count() const { device_lock->lock(); int dc = devices.size(); @@ -1370,20 +1364,31 @@ public: return dc; } - virtual String get_device_name(int p_device) const { + virtual String get_options_tooltip() const { - ERR_FAIL_INDEX_V(p_device, devices.size(), ""); + return TTR("Select device from the list"); + } + + virtual String get_option_label(int p_index) const { + + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); device_lock->lock(); - String s = devices[p_device].name; + String s = devices[p_index].name; device_lock->unlock(); return s; } - virtual String get_device_info(int p_device) const { + virtual String get_option_tooltip(int p_index) const { - ERR_FAIL_INDEX_V(p_device, devices.size(), ""); + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); device_lock->lock(); - String s = devices[p_device].description; + String s = devices[p_index].description; + if (devices.size() == 1) { + // Tooltip will be: + // Name + // Description + s = devices[p_index].name + "\n\n" + s; + } device_lock->unlock(); return s; } @@ -1875,7 +1880,7 @@ public: new_file += "<!--CHUNK_" + text + "_BEGIN-->\n"; if (!found) { - ERR_PRINTS("No end marker found in AndroidManifest.conf for chunk: " + text); + ERR_PRINTS("No end marker found in AndroidManifest.xml for chunk: " + text); f->seek(pos); } else { //add chunk lines @@ -1894,7 +1899,7 @@ public: String last_tag = "android:icon=\"@drawable/icon\""; int last_tag_pos = l.find(last_tag); if (last_tag_pos == -1) { - WARN_PRINTS("No adding of application tags because could not find last tag for <application: " + last_tag); + ERR_PRINTS("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()); @@ -1980,9 +1985,9 @@ public: return ERR_CANT_CREATE; } if (p_debug) { - src_apk = build_path.plus_file("build/outputs/apk/debug/build-debug-unsigned.apk"); + src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk"); } else { - src_apk = build_path.plus_file("build/outputs/apk/release/build-release-unsigned.apk"); + src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk"); } if (!FileAccess::exists(src_apk)) { diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index d5f4ba18d6..ba01ec313b 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -26,11 +26,8 @@ <!-- 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. --> - <application - android:label="@string/godot_project_name_string" - android:allowBackup="false" - tools:ignore="GoogleAppIndexingWarning" - android:icon="@drawable/icon" > + <!-- 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="@drawable/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. --> diff --git a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png Binary files differindex 2c246b04a4..f849d8e90d 100644 --- a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png Binary files differindex 8bcd464bed..1dfb28b33a 100644 --- a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png Binary files differindex b458ff3057..302a972049 100644 --- a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png 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 739aa285bf..4dae2dcc53 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -30,7 +30,6 @@ package org.godotengine.godot; -import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; @@ -56,12 +55,14 @@ import android.hardware.SensorManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings.Secure; import android.support.annotation.Keep; -import android.support.v4.content.ContextCompat; +import android.support.annotation.Nullable; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; @@ -95,14 +96,12 @@ 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.utils.PermissionsUtil; import org.godotengine.godot.xr.XRMode; public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient { static final int MAX_SINGLETONS = 64; - static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; - static final int REQUEST_CAMERA_PERMISSION = 2; - static final int REQUEST_VIBRATE_PERMISSION = 3; private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -126,6 +125,9 @@ 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()); + static private Intent mCurrentIntent; @Override @@ -187,6 +189,20 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo 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) { } @@ -306,6 +322,20 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo 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); + } + } + } + }); } }); } @@ -973,32 +1003,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } public boolean requestPermission(String p_name) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Not necessary, asked on install already - return true; - } - - if (p_name.equals("RECORD_AUDIO")) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); - return false; - } - } + return PermissionsUtil.requestPermission(p_name, this); + } - if (p_name.equals("CAMERA")) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); - return false; - } - } + public boolean requestPermissions() { + return PermissionsUtil.requestManifestPermissions(this); + } - if (p_name.equals("VIBRATE")) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); - return false; - } - } - return true; + public String[] getGrantedPermissions() { + return PermissionsUtil.getGrantedPermissions(this); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 067fa6f4b9..67dce172dc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -95,6 +95,11 @@ public class GodotLib { public static native void touch(int what, int pointer, int howmany, int[] arr); /** + * Forward hover events from the main thread to the GL thread. + */ + public static native void hover(int type, int x, int y); + + /** * Forward accelerometer sensor events from the main thread to the GL thread. * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 2beca67922..2756ca6c83 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -188,7 +188,18 @@ public class GodotInputHandler implements InputDeviceListener { } return true; } - }; + } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) { + final int x = Math.round(event.getX()); + final int y = Math.round(event.getY()); + final int type = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.hover(type, x, y); + } + }); + return true; + } return false; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java new file mode 100644 index 0000000000..2c4a444e5a --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -0,0 +1,157 @@ +package org.godotengine.godot.utils; + +import android.Manifest; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import java.util.ArrayList; +import java.util.List; +import org.godotengine.godot.Godot; + +/** + * This class includes utility functions for Android permissions related operations. + * @author Cagdas Caglak <cagdascaglak@gmail.com> + */ +public final class PermissionsUtil { + + static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; + static final int REQUEST_CAMERA_PERMISSION = 2; + static final int REQUEST_VIBRATE_PERMISSION = 3; + static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001; + + private PermissionsUtil() { + } + + /** + * Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a> + * @param name the name of the requested permission. + * @param activity the caller activity for this method. + * @return true/false. "true" if permission was granted otherwise returns "false". + */ + public static boolean requestPermission(String name, Godot activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Not necessary, asked on install already + return true; + } + + if (name.equals("RECORD_AUDIO") && ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); + return false; + } + + if (name.equals("CAMERA") && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); + return false; + } + + if (name.equals("VIBRATE") && ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); + return false; + } + return true; + } + + /** + * Request dangerous permissions which are defined in the Android manifest file from the user. + * @param activity the caller activity for this method. + * @return true/false. "true" if all permissions were granted otherwise returns "false". + */ + public static boolean requestManifestPermissions(Godot activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + String[] manifestPermissions; + try { + manifestPermissions = getManifestPermissions(activity); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + + if (manifestPermissions == null || manifestPermissions.length == 0) + return true; + + List<String> dangerousPermissions = new ArrayList<>(); + for (String manifestPermission : manifestPermissions) { + try { + PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); + int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; + if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) { + dangerousPermissions.add(manifestPermission); + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + if (dangerousPermissions.isEmpty()) { + // If list is empty, all of dangerous permissions were granted. + return true; + } + + String[] requestedPermissions = dangerousPermissions.toArray(new String[0]); + activity.requestPermissions(requestedPermissions, REQUEST_ALL_PERMISSION_REQ_CODE); + return false; + } + + /** + * With this function you can get the list of dangerous permissions that have been granted to the Android application. + * @param activity the caller activity for this method. + * @return granted permissions list + */ + public static String[] getGrantedPermissions(Godot activity) { + String[] manifestPermissions; + try { + manifestPermissions = getManifestPermissions(activity); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return new String[0]; + } + if (manifestPermissions == null || manifestPermissions.length == 0) + return new String[0]; + + List<String> dangerousPermissions = new ArrayList<>(); + for (String manifestPermission : manifestPermissions) { + try { + PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); + int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; + if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) == PackageManager.PERMISSION_GRANTED) { + dangerousPermissions.add(manifestPermission); + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return new String[0]; + } + } + + return dangerousPermissions.toArray(new String[0]); + } + + /** + * Returns the permissions defined in the AndroidManifest.xml file. + * @param activity the caller activity for this method. + * @return manifest permissions list + * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. + */ + private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException { + PackageManager packageManager = activity.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); + return packageInfo.requestedPermissions; + } + + /** + * Returns the information of the desired permission. + * @param activity the caller activity for this method. + * @param permission the name of the permission. + * @return permission info object + * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. + */ + private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException { + PackageManager packageManager = activity.getPackageManager(); + return packageManager.getPermissionInfo(permission, 0); + } +} diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 30676783db..a14e0a1960 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -826,6 +826,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jo */ } +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) { + if (step == 0) + return; + + os_android->process_hover(p_type, Point2(p_x, p_y)); +} + /* * Android Key codes. */ @@ -1386,6 +1393,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu if (permission == "android.permission.RECORD_AUDIO" && p_result) { AudioDriver::get_singleton()->capture_start(); } + + if (os_android->get_main_loop()) { + os_android->get_main_loop()->emit_signal("on_request_permissions_result", permission, p_result == JNI_TRUE); + } } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 11bda94f8d..a564bbd4a1 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -45,6 +45,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 8194ee6ecf..e3e613d30b 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -59,6 +59,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); + _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z"); + _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;"); _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); @@ -199,6 +201,34 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { } } +bool GodotJavaWrapper::request_permissions() { + if (_request_permissions) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallBooleanMethod(godot_instance, _request_permissions); + } else { + return false; + } +} + +Vector<String> GodotJavaWrapper::get_granted_permissions() const { + Vector<String> permissions_list; + if (_get_granted_permissions) { + JNIEnv *env = ThreadAndroid::get_env(); + jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions); + jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object); + + int i = 0; + jsize len = env->GetArrayLength(*arr); + for (i = 0; i < len; i++) { + jstring jstr = (jstring)env->GetObjectArrayElement(*arr, i); + String str = jstring_to_string(jstr, env); + permissions_list.push_back(str); + env->DeleteLocalRef(jstr); + } + } + return permissions_list; +} + void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { JNIEnv *env = ThreadAndroid::get_env(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index b1bd9b7f48..d23ff273cb 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -54,6 +54,8 @@ private: jmethodID _get_clipboard = 0; jmethodID _set_clipboard = 0; jmethodID _request_permission = 0; + jmethodID _request_permissions = 0; + jmethodID _get_granted_permissions = 0; jmethodID _init_input_devices = 0; jmethodID _get_surface = 0; jmethodID _is_activity_resumed = 0; @@ -81,6 +83,8 @@ public: bool has_set_clipboard(); void set_clipboard(const String &p_text); bool request_permission(const String &p_name); + bool request_permissions(); + Vector<String> get_granted_permissions() const; void init_input_devices(); jobject get_surface(); bool is_activity_resumed(); diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex ba2a0e366a..df445f6a9c 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 49ab0ea84a..9156181bbe 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -49,6 +49,9 @@ #include "java_godot_io_wrapper.h" #include "java_godot_wrapper.h" +#define PAN_GESTURE_MIN_DELTA 5 // only advertise PanGesture event with dx and dy greater than this +#define MAGNIFY_GESTURE_MIN_FACTOR 0.1 // only advertise MagnifyGesture event with a factor difference from 1.0 greater than this + class AndroidLogger : public Logger { public: virtual void logv(const char *p_format, va_list p_list, bool p_err) { @@ -220,6 +223,16 @@ bool OS_Android::request_permission(const String &p_name) { return godot_java->request_permission(p_name); } +bool OS_Android::request_permissions() { + + return godot_java->request_permissions(); +} + +Vector<String> OS_Android::get_granted_permissions() const { + + return godot_java->get_granted_permissions(); +} + Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { p_library_handle = dlopen(p_path.utf8().get_data(), RTLD_NOW); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); @@ -396,6 +409,35 @@ void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> ERR_FAIL_COND(touch.size() != p_points.size()); + if (touch.size() == 1) { + Point2 d = (p_points[0].pos - touch.write[0].pos); + if (fabs(d.x) > PAN_GESTURE_MIN_DELTA || fabs(d.y) > PAN_GESTURE_MIN_DELTA) { + Ref<InputEventPanGesture> ev; + ev.instance(); + ev->set_position(p_points[0].pos); + ev->set_delta(d); + input->parse_input_event(ev); + touch.write[0].pos = p_points[0].pos; + } + } else if (touch.size() == 2) { + Point2 v0 = touch[1].pos - touch[0].pos; + float l0 = (v0.x * v0.x) + (v0.y * v0.y); + if (l0 != 0.0) { + Point2 v1 = p_points[1].pos - p_points[0].pos; + float l1 = (v1.x * v1.x) + (v1.y * v1.y); + float f = (l1 / l0); + if (fabs(f - 1.0) > MAGNIFY_GESTURE_MIN_FACTOR) { + Ref<InputEventMagnifyGesture> ev; + ev.instance(); + ev->set_position(p_points[0].pos + v1 / 2); + ev->set_factor(sqrt(f)); + input->parse_input_event(ev); + touch.write[0].pos = p_points[0].pos; + touch.write[1].pos = p_points[1].pos; + } + } + } + for (int i = 0; i < touch.size(); i++) { int idx = -1; @@ -410,7 +452,7 @@ void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> ERR_CONTINUE(idx == -1); if (touch[i].pos == p_points[idx].pos) - continue; //no move unncesearily + continue; //no unnecessary move Ref<InputEventScreenDrag> ev; ev.instance(); @@ -477,6 +519,23 @@ void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> } } +void OS_Android::process_hover(int p_type, Point2 p_pos) { + // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER + switch (p_type) { + case 7: // hover move + case 9: // hover enter + case 10: { // hover exit + Ref<InputEventMouseMotion> ev; + ev.instance(); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_relative(p_pos - hover_prev_pos); + input->parse_input_event(ev); + hover_prev_pos = p_pos; + } break; + } +} + void OS_Android::process_accelerometer(const Vector3 &p_accelerometer) { input->set_accelerometer(p_accelerometer); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index a17941f7c0..a290c0cedd 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -70,6 +70,7 @@ public: private: Vector<TouchPos> touch; + Point2 hover_prev_pos; // needed to calculate the relative position on hover events bool use_gl2; bool use_apk_expansion; @@ -124,6 +125,8 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual bool request_permission(const String &p_name); + virtual bool request_permissions(); + virtual Vector<String> get_granted_permissions() const; virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); @@ -186,6 +189,7 @@ public: void process_magnetometer(const Vector3 &p_magnetometer); void process_gyroscope(const Vector3 &p_gyroscope); void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points); + void process_hover(int p_type, Point2 p_pos); void process_joy_event(JoypadEvent p_event); void process_event(Ref<InputEvent> p_event); void init_video_mode(int p_video_width, int p_video_height); |