Browse Source

Merge pull request #32600 from cagdasc/3.2-auto-permission-manager

Add request defined permissions in AndroidManifest.xml
Rémi Verschelde 5 years ago
parent
commit
4e29faaeea

+ 12 - 0
core/bind/core_bind.cpp

@@ -1136,6 +1136,16 @@ bool _OS::request_permission(const String &p_name) {
 	return OS::get_singleton()->request_permission(p_name);
 }
 
+bool _OS::request_permissions() {
+
+	return OS::get_singleton()->request_permissions();
+}
+
+Vector<String> _OS::get_granted_permissions() const {
+
+	return OS::get_singleton()->get_granted_permissions();
+}
+
 _OS *_OS::singleton = NULL;
 
 void _OS::_bind_methods() {
@@ -1319,6 +1329,8 @@ void _OS::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_power_percent_left"), &_OS::get_power_percent_left);
 
 	ClassDB::bind_method(D_METHOD("request_permission", "name"), &_OS::request_permission);
+	ClassDB::bind_method(D_METHOD("request_permissions"), &_OS::request_permissions);
+	ClassDB::bind_method(D_METHOD("get_granted_permissions"), &_OS::get_granted_permissions);
 
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "clipboard"), "set_clipboard", "get_clipboard");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen");

+ 2 - 0
core/bind/core_bind.h

@@ -349,6 +349,8 @@ public:
 	bool has_feature(const String &p_feature) const;
 
 	bool request_permission(const String &p_name);
+	bool request_permissions();
+	Vector<String> get_granted_permissions() const;
 
 	static _OS *get_singleton() { return singleton; }
 

+ 2 - 0
core/os/main_loop.cpp

@@ -65,6 +65,8 @@ void MainLoop::_bind_methods() {
 	BIND_CONSTANT(NOTIFICATION_OS_IME_UPDATE);
 	BIND_CONSTANT(NOTIFICATION_APP_RESUMED);
 	BIND_CONSTANT(NOTIFICATION_APP_PAUSED);
+
+	ADD_SIGNAL(MethodInfo("on_request_permissions_result", PropertyInfo(Variant::STRING, "permission"), PropertyInfo(Variant::BOOL, "granted")));
 };
 
 void MainLoop::set_init_script(const Ref<Script> &p_init_script) {

+ 2 - 0
core/os/os.h

@@ -530,6 +530,8 @@ public:
 	List<String> get_restart_on_exit_arguments() const;
 
 	virtual bool request_permission(const String &p_name) { return true; }
+	virtual bool request_permissions() { return true; }
+	virtual Vector<String> get_granted_permissions() const { return Vector<String>(); }
 
 	virtual void process_and_drop_events() {}
 	OS();

+ 11 - 0
doc/classes/MainLoop.xml

@@ -167,6 +167,17 @@
 			</description>
 		</method>
 	</methods>
+	<signals>
+		<signal name="on_request_permissions_result">
+			<argument index="0" name="permission" type="String">
+			</argument>
+			<argument index="1" name="granted" type="bool">
+			</argument>
+			<description>
+				Emitted when an user responds to permission request.
+			</description>
+		</signal>
+	</signals>
 	<constants>
 		<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
 			Notification received from the OS when the mouse enters the game window.

+ 14 - 0
doc/classes/OS.xml

@@ -217,6 +217,13 @@
 				Returns the path to the current engine executable.
 			</description>
 		</method>
+		<method name="get_granted_permissions">
+			<return type="PoolStringArray">
+			</return>
+			<description>
+				With this function you can get the list of dangerous permissions that have been granted to the Android application.
+			</description>
+		</method>
 		<method name="get_ime_selection" qualifiers="const">
 			<return type="Vector2">
 			</return>
@@ -744,6 +751,13 @@
 				At the moment this function is only used by [code]AudioDriverOpenSL[/code] to request permission for [code]RECORD_AUDIO[/code] on Android.
 			</description>
 		</method>
+		<method name="request_permissions">
+			<return type="bool">
+			</return>
+			<description>
+				With this function you can request dangerous permissions since normal permissions are automatically granted at install time in Android application.
+			</description>
+		</method>
 		<method name="set_icon">
 			<return type="void">
 			</return>

+ 8 - 29
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;
@@ -64,7 +63,6 @@ import android.os.Vibrator;
 import android.provider.Settings.Secure;
 import android.support.annotation.Keep;
 import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -98,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;
@@ -1007,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);
 	}
 
 	/**

+ 157 - 0
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 <[email protected]>
+ */
+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);
+	}
+}

+ 4 - 0
platform/android/java_godot_lib_jni.cpp

@@ -1393,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) {

+ 30 - 0
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();

+ 4 - 0
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();

+ 10 - 0
platform/android/os_android.cpp

@@ -220,6 +220,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() + ".");

+ 2 - 0
platform/android/os_android.h

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