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

Add request defined permissions in AndroidManifest.xml
This commit is contained in:
Rémi Verschelde 2019-10-23 08:02:21 +02:00 committed by GitHub
commit 4e29faaeea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 258 additions and 29 deletions

View file

@ -1136,6 +1136,16 @@ bool _OS::request_permission(const String &p_name) {
return OS::get_singleton()->request_permission(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; _OS *_OS::singleton = NULL;
void _OS::_bind_methods() { 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("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_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::STRING, "clipboard"), "set_clipboard", "get_clipboard");
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen");

View file

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

View file

@ -65,6 +65,8 @@ void MainLoop::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_OS_IME_UPDATE); BIND_CONSTANT(NOTIFICATION_OS_IME_UPDATE);
BIND_CONSTANT(NOTIFICATION_APP_RESUMED); BIND_CONSTANT(NOTIFICATION_APP_RESUMED);
BIND_CONSTANT(NOTIFICATION_APP_PAUSED); 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) { void MainLoop::set_init_script(const Ref<Script> &p_init_script) {

View file

@ -530,6 +530,8 @@ public:
List<String> get_restart_on_exit_arguments() const; List<String> get_restart_on_exit_arguments() const;
virtual bool request_permission(const String &p_name) { return true; } 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() {} virtual void process_and_drop_events() {}
OS(); OS();

View file

@ -167,6 +167,17 @@
</description> </description>
</method> </method>
</methods> </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> <constants>
<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002"> <constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
Notification received from the OS when the mouse enters the game window. Notification received from the OS when the mouse enters the game window.

View file

@ -217,6 +217,13 @@
Returns the path to the current engine executable. Returns the path to the current engine executable.
</description> </description>
</method> </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"> <method name="get_ime_selection" qualifiers="const">
<return type="Vector2"> <return type="Vector2">
</return> </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. At the moment this function is only used by [code]AudioDriverOpenSL[/code] to request permission for [code]RECORD_AUDIO[/code] on Android.
</description> </description>
</method> </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"> <method name="set_icon">
<return type="void"> <return type="void">
</return> </return>

View file

@ -30,7 +30,6 @@
package org.godotengine.godot; package org.godotengine.godot;
import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
@ -64,7 +63,6 @@ import android.os.Vibrator;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.support.annotation.Keep; import android.support.annotation.Keep;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.view.Display; import android.view.Display;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -98,14 +96,12 @@ import java.util.Locale;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.input.GodotEditText;
import org.godotengine.godot.payments.PaymentsManager; import org.godotengine.godot.payments.PaymentsManager;
import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode; import org.godotengine.godot.xr.XRMode;
public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient { public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient {
static final int MAX_SINGLETONS = 64; 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 IStub mDownloaderClientStub;
private TextView mStatusText; private TextView mStatusText;
private TextView mProgressFraction; private TextView mProgressFraction;
@ -1007,32 +1003,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
} }
public boolean requestPermission(String p_name) { public boolean requestPermission(String p_name) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return PermissionsUtil.requestPermission(p_name, this);
// Not necessary, asked on install already }
return true;
}
if (p_name.equals("RECORD_AUDIO")) { public boolean requestPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { return PermissionsUtil.requestManifestPermissions(this);
requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); }
return false;
}
}
if (p_name.equals("CAMERA")) { public String[] getGrantedPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return PermissionsUtil.getGrantedPermissions(this);
requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
return false;
}
}
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;
} }
/** /**

View file

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

View file

@ -1393,6 +1393,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
if (permission == "android.permission.RECORD_AUDIO" && p_result) { if (permission == "android.permission.RECORD_AUDIO" && p_result) {
AudioDriver::get_singleton()->capture_start(); 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) { JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) {

View file

@ -59,6 +59,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V");
_request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); _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"); _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V");
_get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;");
_is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); _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() { void GodotJavaWrapper::init_input_devices() {
if (_init_input_devices) { if (_init_input_devices) {
JNIEnv *env = ThreadAndroid::get_env(); JNIEnv *env = ThreadAndroid::get_env();

View file

@ -54,6 +54,8 @@ private:
jmethodID _get_clipboard = 0; jmethodID _get_clipboard = 0;
jmethodID _set_clipboard = 0; jmethodID _set_clipboard = 0;
jmethodID _request_permission = 0; jmethodID _request_permission = 0;
jmethodID _request_permissions = 0;
jmethodID _get_granted_permissions = 0;
jmethodID _init_input_devices = 0; jmethodID _init_input_devices = 0;
jmethodID _get_surface = 0; jmethodID _get_surface = 0;
jmethodID _is_activity_resumed = 0; jmethodID _is_activity_resumed = 0;
@ -81,6 +83,8 @@ public:
bool has_set_clipboard(); bool has_set_clipboard();
void set_clipboard(const String &p_text); void set_clipboard(const String &p_text);
bool request_permission(const String &p_name); bool request_permission(const String &p_name);
bool request_permissions();
Vector<String> get_granted_permissions() const;
void init_input_devices(); void init_input_devices();
jobject get_surface(); jobject get_surface();
bool is_activity_resumed(); bool is_activity_resumed();

View file

@ -220,6 +220,16 @@ bool OS_Android::request_permission(const String &p_name) {
return godot_java->request_permission(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) { 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); 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() + "."); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");

View file

@ -125,6 +125,8 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
virtual bool request_permission(const String &p_name); 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); virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);