#include "permission.h"
#include "../platforms/platform.h"
#include "../libraries/stref.h"
#include "../sk_memory.h"
namespace sk {
#if defined(SK_OS_ANDROID)
#include <android/native_activity.h>
enum xr_runtime_ {
xr_runtime_none,
xr_runtime_unknown,
xr_runtime_meta,
xr_runtime_android_xr,
xr_runtime_vive,
xr_runtime_pico,
xr_runtime_monado,
};
struct permission_state_t {
permission_state_ present [permission_type_max];
bool requires_interaction[permission_type_max];
const char* permission_str [permission_type_max];
jclass class_activity;
jmethodID activity_checkSelfPermission;
jmethodID activity_requestPermissions;
};
static permission_state_t local;
const int32_t PERMISSION_DENIED = -1;
const int32_t PERMISSION_GRANTED = 0;
const int32_t PERMISSION_PROTECTION_DANGEROUS = 0x1;
const int32_t PROTECTION_MASK_BASE = 0x0000000f;
bool _permission_check_app_permission (const char* permission);
void _permission_check_manifest_permissions(void);
void _permission_request_permission (const char* permission);
xr_runtime_ _permission_check_runtime (void);
bool _permission_manifest_has (JNIEnv* env, jobjectArray permission_list, jsize list_count, jmethodID string_equals, const char* permission_str);
const char* _permission_check_string (permission_type_ type, xr_runtime_ runtime, JNIEnv* env, jobjectArray permission_list, jsize list_count, jmethodID string_equals);
const char* _permission_check_string(permission_type_ type, xr_runtime_ runtime, JNIEnv* env, jobjectArray permission_list, jsize list_count, jmethodID string_equals) {
const char* result = nullptr;
#define PERMISSION_CHECK(r, s) if (runtime == r && !result && _permission_manifest_has(env, permission_list, list_count, string_equals, s)) { result = s; }
#define PERMISSION_SET(r, s) if (runtime == r && !result) { result = s; }
switch (type) {
case permission_type_microphone: result = "android.permission.RECORD_AUDIO"; break;
case permission_type_camera: result = "android.permission.CAMERA"; break;
case permission_type_eye_input:
PERMISSION_SET (xr_runtime_android_xr, "android.permission.EYE_TRACKING_FINE");
PERMISSION_CHECK(xr_runtime_meta, "com.oculus.permission.EYE_TRACKING");
PERMISSION_SET (xr_runtime_meta, "horizonos.permission.EYE_TRACKING");
PERMISSION_SET (xr_runtime_pico, "com.picovr.permission.EYE_TRACKING");
break;
case permission_type_hand_tracking:
PERMISSION_SET (xr_runtime_android_xr, "android.permission.HAND_TRACKING");
PERMISSION_CHECK(xr_runtime_meta, "com.oculus.permission.HAND_TRACKING");
PERMISSION_SET (xr_runtime_meta, "horizonos.permission.HAND_TRACKING");
PERMISSION_SET (xr_runtime_pico, "com.picovr.permission.HAND_TRACKING");
break;
case permission_type_face_tracking:
PERMISSION_SET (xr_runtime_android_xr, "android.permission.FACE_TRACKING");
PERMISSION_CHECK(xr_runtime_meta, "com.oculus.permission.FACE_TRACKING");
PERMISSION_SET (xr_runtime_meta, "horizonos.permission.FACE_TRACKING");
PERMISSION_SET (xr_runtime_pico, "com.picovr.permission.FACE_TRACKING");
break;
case permission_type_scene:
PERMISSION_SET (xr_runtime_android_xr, "android.permission.SCENE_UNDERSTANDING_COARSE");
PERMISSION_CHECK(xr_runtime_meta, "com.oculus.permission.USE_SCENE");
PERMISSION_SET (xr_runtime_meta, "horizonos.permission.USE_SCENE");
PERMISSION_SET (xr_runtime_pico, "com.picovr.permission.SPATIAL_DATA");
break;
default:
break;
}
#undef PERMISSION_CHECK
#undef PERMISSION_SET
return result;
}
xr_runtime_ _permission_check_runtime() {
const char* runtime_name = device_get_runtime();
xr_runtime_ result = xr_runtime_unknown;
if (string_startswith(runtime_name, "Meta" )) result = xr_runtime_meta;
else if (string_startswith(runtime_name, "Oculus")) result = xr_runtime_meta;
else if (string_startswith(runtime_name, "Monado")) result = xr_runtime_monado;
else if (string_startswith(runtime_name, "Moohan")) result = xr_runtime_android_xr;
else if (string_startswith(runtime_name, "Vive" )) result = xr_runtime_vive;
return result;
}
bool permission_init() {
local = {};
JNIEnv* env = (JNIEnv*)backend_android_get_jni_env();
jobject activity = (jobject)backend_android_get_activity();
jclass local_class_activity = env->GetObjectClass(activity);
local.class_activity = (jclass)env->NewGlobalRef(local_class_activity);
env->DeleteLocalRef(local_class_activity);
local.activity_checkSelfPermission = env->GetMethodID(local.class_activity, "checkSelfPermission", "(Ljava/lang/String;)I");
local.activity_requestPermissions = env->GetMethodID(local.class_activity, "requestPermissions", "([Ljava/lang/String;I)V");
_permission_check_manifest_permissions();
return true;
}
void permission_shutdown() {
JNIEnv* env = (JNIEnv*)backend_android_get_jni_env();
env->DeleteGlobalRef(local.class_activity);
local = {};
}
bool32_t permission_is_interactive(permission_type_ permission) {
return local.requires_interaction[permission];
}
permission_state_ permission_state(permission_type_ permission) {
if (local.present[permission] == permission_state_capable) {
if (_permission_check_app_permission(local.permission_str[permission]))
local.present[permission] = permission_state_granted;
}
return local.present[permission];
}
void permission_request(permission_type_ permission) {
if (local.permission_str[permission] == NULL) {
log_warnf("Permission string for 0x%X unknown on current platform", permission);
return;
}
_permission_request_permission(local.permission_str[permission]);
}
bool _permission_check_app_permission(const char* permission) {
JNIEnv* env = (JNIEnv*)backend_android_get_jni_env();
jobject activity = (jobject)backend_android_get_activity();
jstring jobj_permission = env->NewStringUTF(permission);
jint result = env->CallIntMethod(activity, local.activity_checkSelfPermission, jobj_permission);
env->DeleteLocalRef(jobj_permission);
return result == PERMISSION_GRANTED;
}
bool _permission_manifest_has(JNIEnv* env, jobjectArray permission_list, jsize list_count, jmethodID string_equals, const char* permission_str) {
bool result = false;
jstring jobj_permission_str = env->NewStringUTF(permission_str);
for (jsize i = 0; i < list_count; i++) {
jstring jobj_curr_permission = (jstring)env->GetObjectArrayElement(permission_list, i);
result = env->CallBooleanMethod(jobj_permission_str, string_equals, jobj_curr_permission);
env->DeleteLocalRef(jobj_curr_permission);
if (result) break;
}
env->DeleteLocalRef(jobj_permission_str);
return result;
}
void _permission_check_manifest_permissions() {
JNIEnv* env = (JNIEnv*)backend_android_get_jni_env ();
jobject activity = (jobject)backend_android_get_activity();
jmethodID activity_getPackageManager = env->GetMethodID (local.class_activity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jmethodID activity_getPackageName = env->GetMethodID (local.class_activity, "getPackageName", "()Ljava/lang/String;");
jmethodID activity_shouldShowRationale = env->GetMethodID (local.class_activity, "shouldShowRequestPermissionRationale", "(Ljava/lang/String;)Z");
jobject jobj_packageManager = env->CallObjectMethod(activity, activity_getPackageManager);
jclass class_packageManager = env->GetObjectClass (jobj_packageManager);
jmethodID packageManager_getPackageInfo = env->GetMethodID (class_packageManager, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jmethodID packageManager_getPermissionInfo = env->GetMethodID (class_packageManager, "getPermissionInfo", "(Ljava/lang/String;I)Landroid/content/pm/PermissionInfo;");
jstring jobj_packageName = (jstring)env->CallObjectMethod(activity, activity_getPackageName);
jint flags = 4096; jobject jobj_packageInfo = env->CallObjectMethod(jobj_packageManager, packageManager_getPackageInfo, jobj_packageName, flags);
jclass class_packageInfo = env->GetObjectClass (jobj_packageInfo);
jfieldID packageInfo_requestedPermissions = env->GetFieldID (class_packageInfo, "requestedPermissions", "[Ljava/lang/String;");
jobjectArray jobj_requestedPermissions = (jobjectArray)env->GetObjectField(jobj_packageInfo, packageInfo_requestedPermissions);
jclass class_string = env->FindClass ("java/lang/String");
jmethodID string_equals = env->GetMethodID(class_string, "equals", "(Ljava/lang/Object;)Z");
jsize length = jobj_requestedPermissions ? env->GetArrayLength(jobj_requestedPermissions) : 0;
xr_runtime_ runtime = _permission_check_runtime();
for (int32_t p = 0; p < permission_type_max; p++) {
local.permission_str[p] = _permission_check_string((permission_type_)p, runtime, env, jobj_requestedPermissions, length, string_equals);
if (local.permission_str[p] == nullptr) {
local.present[p] = permission_state_unknown;
continue;
}
if (!_permission_manifest_has(env, jobj_requestedPermissions, length, string_equals, local.permission_str[p])) {
local.present[p] = permission_state_unavailable;
continue;
}
jstring jstr_permission = env->NewStringUTF(local.permission_str[p]);
jboolean is_interactive = env->CallBooleanMethod(activity, activity_shouldShowRationale, jstr_permission);
jobject permission_info = env->CallObjectMethod(jobj_packageManager, packageManager_getPermissionInfo, jstr_permission, 0);
if (env->ExceptionCheck()) { env->ExceptionClear(); }
env->DeleteLocalRef(jstr_permission);
if (permission_info == nullptr) {
local.present[p] = permission_state_unavailable;
continue;
}
jclass class_permissionInfo = env->GetObjectClass(permission_info);
jmethodID permissionInfo_getProtection = env->GetMethodID (class_permissionInfo, "getProtection", "()I");
jint protection_level_core;
if (permissionInfo_getProtection) {
protection_level_core = env->CallIntMethod(permission_info, permissionInfo_getProtection);
} else {
jfieldID permissionInfo_protectionLevel = env->GetFieldID (class_permissionInfo, "protectionLevel", "I");
jint protection_level = env->GetIntField(permission_info, permissionInfo_protectionLevel);
protection_level_core = (protection_level & PROTECTION_MASK_BASE);
}
local.requires_interaction[p] = (is_interactive == JNI_TRUE) || (protection_level_core == PERMISSION_PROTECTION_DANGEROUS);
local.present [p] = permission_state_capable;
log_infof("Found manifest permission for %s", local.permission_str[p]);
env->DeleteLocalRef(permission_info);
env->DeleteLocalRef(class_permissionInfo);
}
env->DeleteLocalRef(class_string);
env->DeleteLocalRef(class_packageInfo);
env->DeleteLocalRef(class_packageManager);
env->DeleteLocalRef(jobj_packageManager);
env->DeleteLocalRef(jobj_packageName);
env->DeleteLocalRef(jobj_packageInfo);
if (jobj_requestedPermissions) env->DeleteLocalRef(jobj_requestedPermissions);
}
void _permission_request_permission(const char* permission) {
JNIEnv* env = (JNIEnv*)backend_android_get_jni_env ();
jobject activity = (jobject)backend_android_get_activity();
jclass class_string = env->FindClass("java/lang/String");
jstring jobj_permission = env->NewStringUTF (permission);
jobjectArray jobj_permission_list = env->NewObjectArray(1, class_string, NULL);
static const jint SK_REQUEST_CODE_PERMS = 0x534B;
env->SetObjectArrayElement(jobj_permission_list, 0, jobj_permission);
env->CallVoidMethod (activity, local.activity_requestPermissions, jobj_permission_list, SK_REQUEST_CODE_PERMS);
env->DeleteLocalRef(jobj_permission);
env->DeleteLocalRef(jobj_permission_list);
env->DeleteLocalRef(class_string);
}
#else
bool permission_init() {
return true;
}
void permission_shutdown() {
}
permission_state_ permission_state(permission_type_ permission) {
return permission_state_granted;
}
bool32_t permission_is_interactive(permission_type_ permission) {
return false;
}
void permission_request(permission_type_ permission) {
}
#endif
}