// Copyright Epic Games, Inc. All Rights Reserved. #include "Android/AndroidJNI.h" #if USE_ANDROID_JNI #include "Android/AndroidApplication.h" #include "Android/AndroidInputInterface.h" #include "Android/AndroidPlatformCrashContext.h" #include "Android/AndroidStats.h" #include "Async/TaskGraphInterfaces.h" #include "HAL/ExceptionHandling.h" #include "HAL/PlatformStackWalk.h" #include "Math/Vector.h" #include "Misc/ConfigCacheIni.h" #include "Misc/CoreDelegates.h" #include "Misc/DateTime.h" #include "Misc/EmbeddedCommunication.h" #include "Misc/FeedbackContext.h" #include "UnrealEngine.h" #include "Widgets/Input/IVirtualKeyboardEntry.h" THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END #define JNI_CURRENT_VERSION JNI_VERSION_1_6 JavaVM* GJavaVM = nullptr; jclass GGameActivityClassID; jobject GGameActivityThis; // Pointer to target widget for virtual keyboard contents static TWeakPtr VirtualKeyboardWidget; //virtualKeyboard shown static volatile bool GVirtualKeyboardShown = false; //WebView shown static volatile bool GWebViewShown = false; extern FString GFilePathBase; extern FString GInternalFilePath; extern FString GExternalFilePath; extern FString GFontPathBase; extern bool GOBBinAPK; extern bool GOverrideAndroidLogDir; extern FString GOBBFilePathBase; extern FString GOBBMainFilePath; extern FString GOBBPatchFilePath; extern FString GOBBOverflow1FilePath; extern FString GOBBOverflow2FilePath; extern FString GAPKFilename; FOnActivityResult FJavaWrapper::OnActivityResultDelegate; FOnSafetyNetAttestationResult FJavaWrapper::OnSafetyNetAttestationResultDelegate; FOnRouteServiceIntent FJavaWrapper::OnRouteServiceIntentDelegate; ////////////////////////////////////////////////////////////////////////// #if UE_BUILD_SHIPPING // always clear any exceptions in Shipping #define CHECK_JNI_RESULT(Id) if (Id == 0) { Env->ExceptionClear(); } #else #define CHECK_JNI_RESULT(Id) \ if (Id == 0) \ { \ if (bIsOptional) { Env->ExceptionClear(); } \ else { Env->ExceptionDescribe(); checkf(Id != 0, TEXT("Failed to find " #Id)); } \ } #endif #define CHECK_JNI_METHOD(Id) checkf(Id != nullptr, TEXT("Failed to find " #Id)); void FJavaWrapper::FindClassesAndMethods(JNIEnv* Env) { auto bIsOptional = false; FString classPath = ANDROID_GAMEACTIVITY_CLASSPATH; FString classPathBase = ANDROID_GAMEACTIVITY_BASE_CLASSPATH; STANDALONE_DEBUG_LOGf(LogAndroid, TEXT("[JNI] - FindClassesAndMethods called for: %s"), *classPath); jclass foundGameActivityClassID = FindClassGlobalRef(Env, TCHAR_TO_ANSI(*classPath), bIsOptional); if (!foundGameActivityClassID) { STANDALONE_DEBUG_LOGf(LogAndroid, TEXT("[JNI] - FindClassesAndMethods FAILED to find GameActivity ClassID for: %s"), *classPath); return; } GGameActivityClassID = GameActivityClassID = foundGameActivityClassID; AndroidThunkJava_ShowConsoleWindow = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ShowConsoleWindow", "(Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_ShowVirtualKeyboardInputDialog = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ShowVirtualKeyboardInputDialog", "(ILjava/lang/String;Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_HideVirtualKeyboardInputDialog = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_HideVirtualKeyboardInputDialog", "()V", bIsOptional); AndroidThunkJava_ShowVirtualKeyboardInput = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ShowVirtualKeyboardInput", "(ILjava/lang/String;Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_HideVirtualKeyboardInput = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_HideVirtualKeyboardInput", "()V", bIsOptional); AndroidThunkJava_LaunchURL = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LaunchURL", "(Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_GetAssetManager = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetAssetManager", "()Landroid/content/res/AssetManager;", bIsOptional); AndroidThunkJava_Minimize = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_Minimize", "()V", bIsOptional); AndroidThunkJava_ClipboardCopy = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ClipboardCopy", "(Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_ClipboardPaste = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ClipboardPaste", "()Ljava/lang/String;", bIsOptional); AndroidThunkJava_ForceQuit = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ForceQuit", "()V", bIsOptional); AndroidThunkJava_GetFontDirectory = FindStaticMethod(Env, GameActivityClassID, "AndroidThunkJava_GetFontDirectory", "()Ljava/lang/String;", bIsOptional); AndroidThunkJava_Vibrate = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_Vibrate", "(II)V", bIsOptional); AndroidThunkJava_IsMusicActive = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsMusicActive", "()Z", bIsOptional); AndroidThunkJava_IsScreensaverEnabled = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsScreensaverEnabled", "()Z", bIsOptional); AndroidThunkJava_KeepScreenOn = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_KeepScreenOn", "(Z)V", bIsOptional); AndroidThunkJava_InitHMDs = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_InitHMDs", "()V", bIsOptional); AndroidThunkJava_DismissSplashScreen = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_DismissSplashScreen", "()V", bIsOptional); AndroidThunkJava_ShowProgressDialog = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ShowProgressDialog", "(ZLjava/lang/String;ZI)V", bIsOptional); AndroidThunkJava_UpdateProgressDialog = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_UpdateProgressDialog", "(I)V", bIsOptional); AndroidThunkJava_GetInputDeviceInfo = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetInputDeviceInfo", "(I)Lcom/epicgames/unreal/GameActivity$InputDeviceInfo;", bIsOptional); AndroidThunkJava_SetInputDeviceVibrators = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetInputDeviceVibrators", "(IIIII)Z", bIsOptional); AndroidThunkJava_IsGamepadAttached = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsGamepadAttached", "()Z", bIsOptional); AndroidThunkJava_HasMetaDataKey = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_HasMetaDataKey", "(Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_GetMetaDataBoolean = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetMetaDataBoolean", "(Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_GetMetaDataInt = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetMetaDataInt", "(Ljava/lang/String;)I", bIsOptional); AndroidThunkJava_GetMetaDataLong = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetMetaDataLong", "(Ljava/lang/String;)J", bIsOptional); AndroidThunkJava_GetMetaDataFloat = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetMetaDataFloat", "(Ljava/lang/String;)F", bIsOptional); AndroidThunkJava_GetMetaDataString = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetMetaDataString", "(Ljava/lang/String;)Ljava/lang/String;", bIsOptional); AndroidThunkJava_SetSustainedPerformanceMode = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetSustainedPerformanceMode", "(Z)V", bIsOptional); AndroidThunkJava_ShowHiddenAlertDialog = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ShowHiddenAlertDialog", "()V", bIsOptional); AndroidThunkJava_LocalNotificationScheduleAtTime = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LocalNotificationScheduleAtTime", "(Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)I", bIsOptional); AndroidThunkJava_LocalNotificationClearAll = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LocalNotificationClearAll", "()V", bIsOptional); AndroidThunkJava_LocalNotificationExists = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LocalNotificationExists", "(I)Z", bIsOptional); AndroidThunkJava_LocalNotificationGetLaunchNotification = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LocalNotificationGetLaunchNotification", "()Lcom/epicgames/unreal/GameActivity$LaunchNotification;", bIsOptional); AndroidThunkJava_LocalNotificationDestroyIfExists = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LocalNotificationDestroyIfExists", "(I)Z", bIsOptional); AndroidThunkJava_GetNetworkConnectionType = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetNetworkConnectionType", "()I", bIsOptional); AndroidThunkJava_AddNetworkListener = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_AddNetworkListener", "()V", bIsOptional); AndroidThunkJava_RemoveNetworkListener = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_AddNetworkListener", "()V", bIsOptional); AndroidThunkJava_GetAndroidId = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetAndroidId", "()Ljava/lang/String;", bIsOptional); AndroidThunkJava_ShareURL = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ShareURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V", bIsOptional); AndroidThunkJava_IsPackageInstalled = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsPackageInstalled", "(Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_LaunchPackage = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_LaunchPackage", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_SendBroadcast = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SendBroadcast", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Z", bIsOptional); AndroidThunkJava_HasIntentExtrasKey = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_HasIntentExtrasKey", "(Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_GetIntentExtrasBoolean = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetIntentExtrasBoolean", "(Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_GetIntentExtrasInt = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetIntentExtrasInt", "(Ljava/lang/String;)I", bIsOptional); AndroidThunkJava_GetIntentExtrasString = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetIntentExtrasString", "(Ljava/lang/String;)Ljava/lang/String;", bIsOptional); AndroidThunkJava_GetIntentDataAsString = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetIntentDataAsString", "()Ljava/lang/String;", bIsOptional); AndroidThunkJava_PushSensorEvents = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_PushSensorEvents", "()V", bIsOptional); AndroidThunkJava_SetOrientation = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetOrientation", "(I)V", bIsOptional); AndroidThunkJava_HasSharedPreference = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_HasSharedPreference", "(Ljava/lang/String;Ljava/lang/String;)Z", bIsOptional); AndroidThunkJava_SetSharedPreferenceBoolean = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetSharedPreferenceBoolean", "(Ljava/lang/String;Ljava/lang/String;Z)V", bIsOptional); AndroidThunkJava_GetSharedPreferenceBoolean = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetSharedPreferenceBoolean", "(Ljava/lang/String;Ljava/lang/String;Z)Z", bIsOptional); AndroidThunkJava_SetSharedPreferenceInt = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetSharedPreferenceInt", "(Ljava/lang/String;Ljava/lang/String;I)V", bIsOptional); AndroidThunkJava_GetSharedPreferenceInt = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetSharedPreferenceInt", "(Ljava/lang/String;Ljava/lang/String;I)I", bIsOptional); AndroidThunkJava_SetSharedPreferenceString = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetSharedPreferenceString", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_GetSharedPreferenceString = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetSharedPreferenceString", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", bIsOptional); AndroidThunkJava_DeleteSharedPreference = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_DeleteSharedPreference", "(Ljava/lang/String;Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_DeleteSharedPreferenceGroup = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_DeleteSharedPreferenceGroup", "(Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_GetCacheDir = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetCacheDir", "()Ljava/lang/String;", bIsOptional); // Screen capture/recording permission AndroidThunkJava_IsScreenCaptureDisabled = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsScreenCaptureDisabled", "()Z", bIsOptional); AndroidThunkJava_DisableScreenCapture = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_DisableScreenCapture", "(Z)V", bIsOptional); // Native view click filtering AndroidThunkJava_HasExtraIgnoreView = FindMethod(Env, GameActivityClassID, "HasExtraIgnoreView", "()Z", bIsOptional);; // this is optional - only inserted if Oculus Mobile plugin enabled AndroidThunkJava_IsOculusMobileApplication = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsOculusMobileApplication", "()Z", true); // this is optional - only inserted if GCM plugin enabled AndroidThunkJava_RegisterForRemoteNotifications = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_RegisterForRemoteNotifications", "()V", true); AndroidThunkJava_UnregisterForRemoteNotifications = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_UnregisterForRemoteNotifications", "()V", true); AndroidThunkJava_IsAllowedRemoteNotifications = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_IsAllowedRemoteNotifications", "()Z", true); // get field IDs for InputDeviceInfo class members InputDeviceInfoClass = FindClassGlobalRef(Env, "com/epicgames/unreal/GameActivity$InputDeviceInfo", bIsOptional); InputDeviceInfo_VendorId = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "vendorId", "I", bIsOptional); InputDeviceInfo_ProductId = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "productId", "I", bIsOptional); InputDeviceInfo_ControllerId = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "controllerId", "I", bIsOptional); InputDeviceInfo_Name = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "name", "Ljava/lang/String;", bIsOptional); InputDeviceInfo_Descriptor = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "descriptor", "Ljava/lang/String;", bIsOptional); InputDeviceInfo_FeedbackMotorCount = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "feedbackMotorCount", "I", bIsOptional); InputDeviceInfo_IsExternal = FJavaWrapper::FindField(Env, InputDeviceInfoClass, "isExternal", "Z", bIsOptional); /** GooglePlay services */ FindGooglePlayMethods(Env); /** GooglePlay billing services */ FindGooglePlayBillingMethods(Env); // get field IDs for LaunchNotificationClass class members LaunchNotificationClass = FindClassGlobalRef(Env, "com/epicgames/unreal/GameActivity$LaunchNotification", bIsOptional); LaunchNotificationUsed = FJavaWrapper::FindField(Env, LaunchNotificationClass, "used", "Z", bIsOptional); LaunchNotificationEvent = FJavaWrapper::FindField(Env, LaunchNotificationClass, "event", "Ljava/lang/String;", bIsOptional); LaunchNotificationFireDate = FJavaWrapper::FindField(Env, LaunchNotificationClass, "fireDate", "I", bIsOptional); ThreadClass = FindClassGlobalRef(Env, "java/lang/Thread", bIsOptional); CurrentThreadMethod = FindStaticMethod(Env, ThreadClass, "currentThread", "()Ljava/lang/Thread;", bIsOptional); SetNameMethod = FindMethod(Env, ThreadClass, "setName", "(Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_RestartApplication = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_RestartApplication", "(Ljava/lang/String;)V", bIsOptional); // display refresh rates AndroidThunkJava_GetNativeDisplayRefreshRate = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetNativeDisplayRefreshRate", "()I", bIsOptional); AndroidThunkJava_SetNativeDisplayRefreshRate = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetNativeDisplayRefreshRate", "(I)Z", bIsOptional); AndroidThunkJava_GetSupportedNativeDisplayRefreshRates = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_GetSupportedNativeDisplayRefreshRates", "()[I", bIsOptional); // motion controls AndroidThunkJava_EnableMotion = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_EnableMotion", "(Z)V", bIsOptional); // the rest are optional bIsOptional = true; // SurfaceView functionality for view scaling on some devices AndroidThunkJava_UseSurfaceViewWorkaround = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_UseSurfaceViewWorkaround", "()V", bIsOptional); AndroidThunkJava_SetDesiredViewSize = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_SetDesiredViewSize", "(II)V", bIsOptional); AndroidThunkJava_VirtualInputIgnoreClick = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_VirtualInputIgnoreClick", "(II)Z", bIsOptional); // Multicast lock handling AndroidThunkJava_AcquireWifiManagerMulticastLock = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_AcquireWifiManagerMulticastLock", "()Z", bIsOptional); AndroidThunkJava_ReleaseWifiManagerMulticastLock = FindMethod(Env, GameActivityClassID, "AndroidThunkJava_ReleaseWifiManagerMulticastLock", "()V", bIsOptional); SetupEmbeddedCommunication(Env); } void FJavaWrapper::FindGooglePlayMethods(JNIEnv* Env) { bool bIsOptional = true; // @todo split GooglePlay // GoogleServicesClassID = FindClass(Env, "com/epicgames/unreal/GoogleServices", bIsOptional); GoogleServicesClassID = GameActivityClassID; AndroidThunkJava_ShowAdBanner = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_ShowAdBanner", "(Ljava/lang/String;Z)V", bIsOptional); AndroidThunkJava_HideAdBanner = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_HideAdBanner", "()V", bIsOptional); AndroidThunkJava_CloseAdBanner = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_CloseAdBanner", "()V", bIsOptional); AndroidThunkJava_LoadInterstitialAd = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_LoadInterstitialAd", "(Ljava/lang/String;)V", bIsOptional); AndroidThunkJava_IsInterstitialAdAvailable = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IsInterstitialAdAvailable", "()Z", bIsOptional); AndroidThunkJava_IsInterstitialAdRequested = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IsInterstitialAdRequested", "()Z", bIsOptional); AndroidThunkJava_ShowInterstitialAd = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_ShowInterstitialAd", "()V", bIsOptional); AndroidThunkJava_GetAdvertisingId = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_GetAdvertisingId", "()Ljava/lang/String;", bIsOptional); } void FJavaWrapper::FindGooglePlayBillingMethods(JNIEnv* Env) { // In app purchase functionality bool bSupportsInAppPurchasing = false; if (!GConfig->GetBool(TEXT("OnlineSubsystemGooglePlay.Store"), TEXT("bSupportsInAppPurchasing"), bSupportsInAppPurchasing, GEngineIni)) { FPlatformMisc::LowLevelOutputDebugString(TEXT("[JNI] - Failed to determine if app purchasing is enabled!")); } bool bIsStoreOptional = !bSupportsInAppPurchasing; JavaStringClass = FindClassGlobalRef(Env, "java/lang/String", false); AndroidThunkJava_IapSetupService = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapSetupService", "(Ljava/lang/String;)V", bIsStoreOptional); AndroidThunkJava_IapQueryInAppPurchases = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapQueryInAppPurchases", "([Ljava/lang/String;)Z", bIsStoreOptional); AndroidThunkJava_IapBeginPurchase = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapBeginPurchase", "([Ljava/lang/String;Ljava/lang/String;)Z", bIsStoreOptional); AndroidThunkJava_IapIsAllowedToMakePurchases = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapIsAllowedToMakePurchases", "()Z", bIsStoreOptional); AndroidThunkJava_IapAcknowledgePurchase = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapAcknowledgePurchase", "(Ljava/lang/String;)Z", bIsStoreOptional); AndroidThunkJava_IapConsumePurchase = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapConsumePurchase", "(Ljava/lang/String;)Z", bIsStoreOptional); AndroidThunkJava_IapQueryExistingPurchases = FindMethod(Env, GoogleServicesClassID, "AndroidThunkJava_IapQueryExistingPurchases", "()Z", bIsStoreOptional); } jclass FJavaWrapper::FindClass(JNIEnv* Env, const ANSICHAR* ClassName, bool bIsOptional) { jclass Class = Env->FindClass(ClassName); CHECK_JNI_RESULT(Class); return Class; } jclass FJavaWrapper::FindClassGlobalRef(JNIEnv* Env, const ANSICHAR* ClassName, bool bIsOptional) { auto LocalClass = NewScopedJavaObject(Env, Env->FindClass(ClassName)); CHECK_JNI_RESULT(*LocalClass); if (LocalClass) { return (jclass)Env->NewGlobalRef(*LocalClass); } return nullptr; } jmethodID FJavaWrapper::FindMethod(JNIEnv* Env, jclass Class, const ANSICHAR* MethodName, const ANSICHAR* MethodSignature, bool bIsOptional) { jmethodID Method = Class == NULL ? NULL : Env->GetMethodID(Class, MethodName, MethodSignature); CHECK_JNI_RESULT(Method); return Method; } jmethodID FJavaWrapper::FindStaticMethod(JNIEnv* Env, jclass Class, const ANSICHAR* MethodName, const ANSICHAR* MethodSignature, bool bIsOptional) { jmethodID Method = Class == NULL ? NULL : Env->GetStaticMethodID(Class, MethodName, MethodSignature); CHECK_JNI_RESULT(Method); return Method; } jfieldID FJavaWrapper::FindField(JNIEnv* Env, jclass Class, const ANSICHAR* FieldName, const ANSICHAR* FieldType, bool bIsOptional) { jfieldID Field = Class == NULL ? NULL : Env->GetFieldID(Class, FieldName, FieldType); CHECK_JNI_RESULT(Field); return Field; } void FJavaWrapper::CallVoidMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { // make sure the function exists if (Method == NULL || Object == NULL) { return; } va_list Args; va_start(Args, Method); Env->CallVoidMethodV(Object, Method, Args); va_end(Args); } jobject FJavaWrapper::CallObjectMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { if (Method == NULL || Object == NULL) { return NULL; } va_list Args; va_start(Args, Method); jobject Return = Env->CallObjectMethodV(Object, Method, Args); va_end(Args); return Return; } int32 FJavaWrapper::CallIntMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { if (Method == NULL || Object == NULL) { return 0; } va_list Args; va_start(Args, Method); jint Return = Env->CallIntMethodV(Object, Method, Args); va_end(Args); return (int32)Return; } int64 FJavaWrapper::CallLongMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { if (Method == NULL || Object == NULL) { return 0; } va_list Args; va_start(Args, Method); jlong Return = Env->CallLongMethodV(Object, Method, Args); va_end(Args); return (int64)Return; } float FJavaWrapper::CallFloatMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { if (Method == NULL || Object == NULL) { return 0.0f; } va_list Args; va_start(Args, Method); jfloat Return = Env->CallFloatMethodV(Object, Method, Args); va_end(Args); return (float)Return; } double FJavaWrapper::CallDoubleMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { if (Method == NULL || Object == NULL) { return 0.0; } va_list Args; va_start(Args, Method); jdouble Return = Env->CallDoubleMethodV(Object, Method, Args); va_end(Args); return (double)Return; } bool FJavaWrapper::CallBooleanMethod(JNIEnv* Env, jobject Object, jmethodID Method, ...) { if (Method == NULL || Object == NULL) { return false; } va_list Args; va_start(Args, Method); jboolean Return = Env->CallBooleanMethodV(Object, Method, Args); va_end(Args); return (bool)Return; } void FJavaWrapper::CallStaticVoidMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return; } va_list Args; va_start(Args, Method); Env->CallStaticVoidMethodV(Clazz, Method, Args); va_end(Args); } jobject FJavaWrapper::CallStaticObjectMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return nullptr; } va_list Args; va_start(Args, Method); jobject Return = Env->CallStaticObjectMethodV(Clazz, Method, Args); va_end(Args); return Return; } int32 FJavaWrapper::CallStaticIntMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return 0; } va_list Args; va_start(Args, Method); jint Return = Env->CallStaticIntMethod(Clazz, Method, Args); va_end(Args); return (int32)Return; } int64 FJavaWrapper::CallStaticLongMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return 0; } va_list Args; va_start(Args, Method); jlong Return = Env->CallStaticLongMethod(Clazz, Method, Args); va_end(Args); return (int64)Return; } float FJavaWrapper::CallStaticFloatMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return 0.f; } va_list Args; va_start(Args, Method); jfloat Return = Env->CallStaticFloatMethod(Clazz, Method, Args); va_end(Args); return (float)Return; } double FJavaWrapper::CallStaticDoubleMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return 0.; } va_list Args; va_start(Args, Method); jdouble Return = Env->CallStaticDoubleMethod(Clazz, Method, Args); va_end(Args); return (double)Return; } bool FJavaWrapper::CallStaticBooleanMethod(JNIEnv* Env, jclass Clazz, jmethodID Method, ...) { if (Method == NULL || Clazz == NULL) { return false; } va_list Args; va_start(Args, Method); jboolean Return = Env->CallStaticBooleanMethod(Clazz, Method, Args); va_end(Args); return (bool)Return; } //Declare all the static members of the class defs jclass FJavaWrapper::GameActivityClassID; jobject FJavaWrapper::GameActivityThis; jmethodID FJavaWrapper::AndroidThunkJava_ShowConsoleWindow; jmethodID FJavaWrapper::AndroidThunkJava_ShowVirtualKeyboardInputDialog; jmethodID FJavaWrapper::AndroidThunkJava_HideVirtualKeyboardInputDialog; jmethodID FJavaWrapper::AndroidThunkJava_ShowVirtualKeyboardInput; jmethodID FJavaWrapper::AndroidThunkJava_HideVirtualKeyboardInput; jmethodID FJavaWrapper::AndroidThunkJava_LaunchURL; jmethodID FJavaWrapper::AndroidThunkJava_GetAssetManager; jmethodID FJavaWrapper::AndroidThunkJava_Minimize; jmethodID FJavaWrapper::AndroidThunkJava_ClipboardCopy; jmethodID FJavaWrapper::AndroidThunkJava_ClipboardPaste; jmethodID FJavaWrapper::AndroidThunkJava_ForceQuit; jmethodID FJavaWrapper::AndroidThunkJava_GetFontDirectory; jmethodID FJavaWrapper::AndroidThunkJava_Vibrate; jmethodID FJavaWrapper::AndroidThunkJava_IsMusicActive; jmethodID FJavaWrapper::AndroidThunkJava_IsScreensaverEnabled; jmethodID FJavaWrapper::AndroidThunkJava_KeepScreenOn; jmethodID FJavaWrapper::AndroidThunkJava_InitHMDs; jmethodID FJavaWrapper::AndroidThunkJava_DismissSplashScreen; jmethodID FJavaWrapper::AndroidThunkJava_ShowProgressDialog; jmethodID FJavaWrapper::AndroidThunkJava_UpdateProgressDialog; jmethodID FJavaWrapper::AndroidThunkJava_GetInputDeviceInfo; jmethodID FJavaWrapper::AndroidThunkJava_SetInputDeviceVibrators; jmethodID FJavaWrapper::AndroidThunkJava_IsGamepadAttached; jmethodID FJavaWrapper::AndroidThunkJava_HasMetaDataKey; jmethodID FJavaWrapper::AndroidThunkJava_GetMetaDataBoolean; jmethodID FJavaWrapper::AndroidThunkJava_GetMetaDataInt; jmethodID FJavaWrapper::AndroidThunkJava_GetMetaDataLong; jmethodID FJavaWrapper::AndroidThunkJava_GetMetaDataFloat; jmethodID FJavaWrapper::AndroidThunkJava_GetMetaDataString; jmethodID FJavaWrapper::AndroidThunkJava_IsOculusMobileApplication; jmethodID FJavaWrapper::AndroidThunkJava_RegisterForRemoteNotifications; jmethodID FJavaWrapper::AndroidThunkJava_UnregisterForRemoteNotifications; jmethodID FJavaWrapper::AndroidThunkJava_IsAllowedRemoteNotifications; jmethodID FJavaWrapper::AndroidThunkJava_ShowHiddenAlertDialog; jmethodID FJavaWrapper::AndroidThunkJava_LocalNotificationScheduleAtTime; jmethodID FJavaWrapper::AndroidThunkJava_LocalNotificationClearAll; jmethodID FJavaWrapper::AndroidThunkJava_LocalNotificationExists; jmethodID FJavaWrapper::AndroidThunkJava_LocalNotificationGetLaunchNotification; jmethodID FJavaWrapper::AndroidThunkJava_LocalNotificationDestroyIfExists; jmethodID FJavaWrapper::AndroidThunkJava_GetNetworkConnectionType; jmethodID FJavaWrapper::AndroidThunkJava_GetAndroidId; jmethodID FJavaWrapper::AndroidThunkJava_ShareURL; jmethodID FJavaWrapper::AndroidThunkJava_IsPackageInstalled; jmethodID FJavaWrapper::AndroidThunkJava_LaunchPackage; jmethodID FJavaWrapper::AndroidThunkJava_SendBroadcast; jmethodID FJavaWrapper::AndroidThunkJava_HasIntentExtrasKey; jmethodID FJavaWrapper::AndroidThunkJava_GetIntentExtrasBoolean; jmethodID FJavaWrapper::AndroidThunkJava_GetIntentExtrasInt; jmethodID FJavaWrapper::AndroidThunkJava_GetIntentExtrasString; jmethodID FJavaWrapper::AndroidThunkJava_GetIntentDataAsString; jmethodID FJavaWrapper::AndroidThunkJava_SetSustainedPerformanceMode; jmethodID FJavaWrapper::AndroidThunkJava_PushSensorEvents; jmethodID FJavaWrapper::AndroidThunkJava_IsScreenCaptureDisabled; jmethodID FJavaWrapper::AndroidThunkJava_DisableScreenCapture; jmethodID FJavaWrapper::AndroidThunkJava_HasExtraIgnoreView; jmethodID FJavaWrapper::AndroidThunkJava_SetOrientation; jmethodID FJavaWrapper::AndroidThunkJava_HasSharedPreference; jmethodID FJavaWrapper::AndroidThunkJava_SetSharedPreferenceBoolean; jmethodID FJavaWrapper::AndroidThunkJava_GetSharedPreferenceBoolean; jmethodID FJavaWrapper::AndroidThunkJava_SetSharedPreferenceInt; jmethodID FJavaWrapper::AndroidThunkJava_GetSharedPreferenceInt; jmethodID FJavaWrapper::AndroidThunkJava_SetSharedPreferenceString; jmethodID FJavaWrapper::AndroidThunkJava_GetSharedPreferenceString; jmethodID FJavaWrapper::AndroidThunkJava_DeleteSharedPreference; jmethodID FJavaWrapper::AndroidThunkJava_DeleteSharedPreferenceGroup; jmethodID FJavaWrapper::AndroidThunkJava_GetCacheDir; jclass FJavaWrapper::InputDeviceInfoClass; jfieldID FJavaWrapper::InputDeviceInfo_VendorId; jfieldID FJavaWrapper::InputDeviceInfo_ProductId; jfieldID FJavaWrapper::InputDeviceInfo_ControllerId; jfieldID FJavaWrapper::InputDeviceInfo_Name; jfieldID FJavaWrapper::InputDeviceInfo_Descriptor; jfieldID FJavaWrapper::InputDeviceInfo_FeedbackMotorCount; jfieldID FJavaWrapper::InputDeviceInfo_IsExternal; jclass FJavaWrapper::GoogleServicesClassID; jobject FJavaWrapper::GoogleServicesThis; jmethodID FJavaWrapper::AndroidThunkJava_ShowAdBanner; jmethodID FJavaWrapper::AndroidThunkJava_HideAdBanner; jmethodID FJavaWrapper::AndroidThunkJava_CloseAdBanner; jmethodID FJavaWrapper::AndroidThunkJava_LoadInterstitialAd; jmethodID FJavaWrapper::AndroidThunkJava_IsInterstitialAdAvailable; jmethodID FJavaWrapper::AndroidThunkJava_IsInterstitialAdRequested; jmethodID FJavaWrapper::AndroidThunkJava_ShowInterstitialAd; jmethodID FJavaWrapper::AndroidThunkJava_GetAdvertisingId; jclass FJavaWrapper::JavaStringClass; jmethodID FJavaWrapper::AndroidThunkJava_IapSetupService; jmethodID FJavaWrapper::AndroidThunkJava_IapQueryInAppPurchases; jmethodID FJavaWrapper::AndroidThunkJava_IapBeginPurchase; jmethodID FJavaWrapper::AndroidThunkJava_IapIsAllowedToMakePurchases; jmethodID FJavaWrapper::AndroidThunkJava_IapQueryExistingPurchases; jmethodID FJavaWrapper::AndroidThunkJava_IapAcknowledgePurchase; jmethodID FJavaWrapper::AndroidThunkJava_IapConsumePurchase; jmethodID FJavaWrapper::AndroidThunkJava_UseSurfaceViewWorkaround; jmethodID FJavaWrapper::AndroidThunkJava_SetDesiredViewSize; jmethodID FJavaWrapper::AndroidThunkJava_VirtualInputIgnoreClick; jmethodID FJavaWrapper::AndroidThunkJava_RestartApplication; jmethodID FJavaWrapper::AndroidThunkJava_GetSupportedNativeDisplayRefreshRates; jmethodID FJavaWrapper::AndroidThunkJava_GetNativeDisplayRefreshRate; jmethodID FJavaWrapper::AndroidThunkJava_SetNativeDisplayRefreshRate; jmethodID FJavaWrapper::AndroidThunkJava_AddNetworkListener; jmethodID FJavaWrapper::AndroidThunkJava_RemoveNetworkListener; jmethodID FJavaWrapper::AndroidThunkJava_EnableMotion; jclass FJavaWrapper::LaunchNotificationClass; jfieldID FJavaWrapper::LaunchNotificationUsed; jfieldID FJavaWrapper::LaunchNotificationEvent; jfieldID FJavaWrapper::LaunchNotificationFireDate; jclass FJavaWrapper::ThreadClass; jmethodID FJavaWrapper::CurrentThreadMethod; jmethodID FJavaWrapper::SetNameMethod; jmethodID FJavaWrapper::AndroidThunkJava_AcquireWifiManagerMulticastLock; jmethodID FJavaWrapper::AndroidThunkJava_ReleaseWifiManagerMulticastLock; //Game-specific crash reporter void EngineCrashHandler(const FGenericCrashContext& GenericContext) { const FAndroidCrashContext& Context = static_cast(GenericContext); static int32 bHasEntered = 0; if (FPlatformAtomics::InterlockedCompareExchange(&bHasEntered, 1, 0) == 0) { const SIZE_T StackTraceSize = 65535; ANSICHAR StackTrace[StackTraceSize]; StackTrace[0] = 0; // Walk the stack and dump it to the allocated memory. FPlatformStackWalk::StackWalkAndDump(StackTrace, StackTraceSize, 0, Context.Context); UE_LOG(LogEngine, Error, TEXT("\n%s\n"), ANSI_TO_TCHAR(StackTrace)); if (GLog) { GLog->Panic(); } if (GWarn) { GWarn->Flush(); } } } bool AndroidThunkCpp_IsScreensaverEnabled() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // call the java side return FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsScreensaverEnabled); } return true; } void AndroidThunkCpp_KeepScreenOn(bool Enable) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // call the java side FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_KeepScreenOn, Enable); } } void AndroidThunkCpp_Vibrate(int32 Intensity, int32 Duration) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // call the java side FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_Vibrate, Intensity, Duration); } } // Call the Java side code for initializing VR HMD modules void AndroidThunkCpp_InitHMDs() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_InitHMDs); } } void AndroidThunkCpp_DismissSplashScreen() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_DismissSplashScreen); } } void AndroidThunkCpp_ShowProgressDialog(bool bShow, const FString& Message, bool bHorizontal, int32 MaxValue) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto JavaMessage = FJavaHelper::ToJavaString(Env, Message); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ShowProgressDialog, bShow, *JavaMessage, bHorizontal, MaxValue); } } void AndroidThunkCpp_UpdateProgressDialog(int32 Value) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_UpdateProgressDialog, Value); } } bool AndroidThunkCpp_GetInputDeviceInfo(int32 deviceId, FAndroidInputDeviceInfo &results) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto deviceInfo = NewScopedJavaObject(Env, (jobject)Env->CallObjectMethod(FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetInputDeviceInfo, deviceId)); if (Env->ExceptionCheck()) { Env->ExceptionDescribe(); Env->ExceptionClear(); } else if (deviceInfo) { bool bIsOptional = false; results.DeviceId = deviceId; results.VendorId = (int32)Env->GetIntField(*deviceInfo, FJavaWrapper::InputDeviceInfo_VendorId); results.ProductId = (int32)Env->GetIntField(*deviceInfo, FJavaWrapper::InputDeviceInfo_ProductId); results.ControllerId = (int32)Env->GetIntField(*deviceInfo, FJavaWrapper::InputDeviceInfo_ControllerId); FScopedJavaObject JavaInputDeviceName = NewScopedJavaObject(Env, (jstring)Env->GetObjectField(*deviceInfo, FJavaWrapper::InputDeviceInfo_Name)); const auto chars = Env->GetStringUTFChars(*JavaInputDeviceName, 0); results.Name = chars; // UTF8_TO_TCHAR(chars) Env->ReleaseStringUTFChars(*JavaInputDeviceName, chars); results.Descriptor = FJavaHelper::FStringFromLocalRef(Env, (jstring)Env->GetObjectField(*deviceInfo, FJavaWrapper::InputDeviceInfo_Descriptor)); results.FeedbackMotorCount = (int32)Env->GetIntField(*deviceInfo, FJavaWrapper::InputDeviceInfo_FeedbackMotorCount); results.IsExternal = (bool)Env->GetBooleanField(*deviceInfo, FJavaWrapper::InputDeviceInfo_IsExternal); return true; } } // failed results.DeviceId = deviceId; results.VendorId = 0; results.ProductId = 0; results.ControllerId = -1; results.Name = TEXT("Unknown"); results.Descriptor = FString("Unknown"); results.FeedbackMotorCount = 0; return false; } bool AndroidThunkCpp_SetInputDeviceVibrators(int32 deviceId, int32 leftIntensity, int32 leftDuration, int32 rightIntensity, int32 rightDuration) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { return FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetInputDeviceVibrators, deviceId, leftIntensity, leftDuration, rightIntensity, rightDuration); } return false; } bool AndroidThunkCpp_VirtualInputIgnoreClick(int32 x, int32 y) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_VirtualInputIgnoreClick, x, y); } return Result; } bool AndroidThunkCpp_HasExtraIgnoreView() { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_HasExtraIgnoreView); } return Result; } void AndroidThunkCpp_RestartApplication(const FString& IntentString) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, IntentString); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_RestartApplication, *Argument); } } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeHandleNewIntentData(JNIEnv* jenv, jobject thiz, jstring JavaIntent) { FAndroidMisc::HandleNewIntentUri(FJavaHelper::FStringFromParam(jenv, JavaIntent)); } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_WebViewVisible(JNIEnv* jenv, jobject thiz, jboolean bShown) { GWebViewShown = bShown; } bool AndroidThunkCpp_IsWebViewShown() { return GWebViewShown; } //Set GVirtualKeyboardShown.This function is declared in the Java-defined class, GameActivity.java: "public native void nativeVirtualKeyboardVisible(boolean bShown)" JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardVisible(JNIEnv* jenv, jobject thiz, jboolean bShown) { GVirtualKeyboardShown = bShown; //remove reference so the object can be clicked again to show the virtual keyboard if (!bShown) { VirtualKeyboardWidget.Reset(); if (FTaskGraphInterface::IsRunning()) { FGraphEventRef VirtualKeyboardShown = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() { FAndroidApplication::Get()->OnVirtualKeyboardHidden().Broadcast(); }, TStatId(), NULL, ENamedThreads::GameThread); } } } bool AndroidThunkCpp_IsVirtualKeyboardShown() { return GVirtualKeyboardShown; } bool AndroidThunkCpp_IsGamepadAttached() { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsGamepadAttached); } return Result; } bool AndroidThunkCpp_HasMetaDataKey(const FString& Key) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_HasMetaDataKey, *Argument); } return Result; } bool AndroidThunkCpp_GetMetaDataBoolean(const FString& Key) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetMetaDataBoolean, *Argument); } return Result; } int32 AndroidThunkCpp_GetMetaDataInt(const FString& Key) { int32 Result = 0; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetMetaDataInt, *Argument); } return Result; } int64 AndroidThunkCpp_GetMetaDataLong(const FString& Key) { int64 Result = 0; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = (int64)FJavaWrapper::CallLongMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetMetaDataLong, *Argument); } return Result; } float AndroidThunkCpp_GetMetaDataFloat(const FString& Key) { float Result = 0.0f; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = (float)FJavaWrapper::CallFloatMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetMetaDataFloat, *Argument); } return Result; } FString AndroidThunkCpp_GetMetaDataString(const FString& Key) { FString Result = FString(""); if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetMetaDataString, *Argument)); } return Result; } bool AndroidThunkCpp_HasIntentExtrasKey(const FString& Key) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_HasIntentExtrasKey, *Argument); } return Result; } bool AndroidThunkCpp_GetIntentExtrasBoolean(const FString& Key) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetIntentExtrasBoolean, *Argument); } return Result; } int32 AndroidThunkCpp_GetIntentExtrasInt(const FString& Key) { int32 Result = 0; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetIntentExtrasInt, *Argument); } return Result; } FString AndroidThunkCpp_GetIntentExtrasString(const FString& Key) { FString Result = FString(""); if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, Key); Result = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetIntentExtrasString, *Argument)); } return Result; } FString AndroidThunkCpp_GetIntentDataAsString() { FString Result = FString(""); if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetIntentDataAsString)); } return Result; } void AndroidThunkCpp_SetSustainedPerformanceMode(bool bEnable) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetSustainedPerformanceMode, bEnable); } } void AndroidThunkCpp_PushSensorEvents() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_PushSensorEvents); } } bool AndroidThunkCpp_IsScreenCaptureDisabled() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // call the java side return FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsScreenCaptureDisabled); } return true; } void AndroidThunkCpp_DisableScreenCapture(bool bDisable) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // call the java side FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_DisableScreenCapture, bDisable); } } void AndroidThunkCpp_ShowHiddenAlertDialog() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ShowHiddenAlertDialog); } } // call out to JNI to see if the application was packaged for Oculus Mobile bool AndroidThunkCpp_IsOculusMobileApplication() { static int32 IsOculusMobileApplication = -1; if (IsOculusMobileApplication == -1) { IsOculusMobileApplication = 0; if (FJavaWrapper::AndroidThunkJava_IsOculusMobileApplication) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { IsOculusMobileApplication = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsOculusMobileApplication) ? 1 : 0; } } } return IsOculusMobileApplication == 1; } // call optional remote notification registration void AndroidThunkCpp_RegisterForRemoteNotifications() { if (FJavaWrapper::AndroidThunkJava_RegisterForRemoteNotifications) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_RegisterForRemoteNotifications); } } } // call optional remote notification unregistration void AndroidThunkCpp_UnregisterForRemoteNotifications() { if (FJavaWrapper::AndroidThunkJava_UnregisterForRemoteNotifications) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_UnregisterForRemoteNotifications); } } } bool AndroidThunkCpp_IsAllowedRemoteNotifications() { bool Result = false; if (FJavaWrapper::AndroidThunkJava_UnregisterForRemoteNotifications) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsAllowedRemoteNotifications); } } return Result; } void AndroidThunkCpp_ShowConsoleWindow() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // figure out all the possible texture formats that are allowed TArray PossibleTargetPlatforms; FPlatformMisc::GetValidTargetPlatforms(PossibleTargetPlatforms); // separate the format suffixes with commas FString ConsoleText; for (int32 FormatIndex = 0; FormatIndex < PossibleTargetPlatforms.Num(); FormatIndex++) { const FString& Format = PossibleTargetPlatforms[FormatIndex]; int32 UnderscoreIndex; if (Format.FindLastChar('_', UnderscoreIndex)) { if (ConsoleText != TEXT("")) { ConsoleText += ", "; } ConsoleText += Format.Mid(UnderscoreIndex + 1); } } // call the java side auto ConsoleTextJava = FJavaHelper::ToJavaString(Env, ConsoleText); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ShowConsoleWindow, *ConsoleTextJava); } } void AndroidThunkCpp_ShowVirtualKeyboardInputDialog(TSharedPtr TextWidget, int32 InputType, const FString& Label, const FString& Contents) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // remember target widget for contents VirtualKeyboardWidget = TextWidget; // call the java side auto LabelJava = FJavaHelper::ToJavaString(Env, Label); auto ContentsJava = FJavaHelper::ToJavaString(Env, Contents); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ShowVirtualKeyboardInputDialog, InputType, *LabelJava, *ContentsJava); } } void AndroidThunkCpp_HideVirtualKeyboardInputDialog() { // Make sure virtual keyboard currently open if (!VirtualKeyboardWidget.IsValid()) { return; } if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // ignore anything it might return VirtualKeyboardWidget.Reset(); // call the java side FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_HideVirtualKeyboardInputDialog); } } // This is called from the ViewTreeObserver.OnGlobalLayoutListener in GameActivity JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardShown(JNIEnv* jenv, jobject thiz, jint left, jint top, jint right, jint bottom) { FPlatformRect ScreenRect(left, top, right, bottom); if (FTaskGraphInterface::IsRunning()) { FGraphEventRef VirtualKeyboardShown = FFunctionGraphTask::CreateAndDispatchWhenReady([ScreenRect]() { FAndroidApplication::Get()->OnVirtualKeyboardShown().Broadcast(ScreenRect); }, TStatId(), NULL, ENamedThreads::GameThread); } } void AndroidThunkCpp_HideVirtualKeyboardInput() { // Make sure virtual keyboard currently open if (!VirtualKeyboardWidget.IsValid()) { return; } if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // ignore anything it might return VirtualKeyboardWidget.Reset(); // call the java side FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_HideVirtualKeyboardInput); } } void AndroidThunkCpp_ShowVirtualKeyboardInput(TSharedPtr TextWidget, int32 InputType, const FString& Label, const FString& Contents) { //#jira UE-49139 Tapping in the same text box doesn't make the virtual keyboard disappear if (VirtualKeyboardWidget.HasSameObject(TextWidget.Get())) { FPlatformMisc::LowLevelOutputDebugString(TEXT("[JNI] - AndroidThunkCpp_ShowVirtualKeyboardInput same control")); AndroidThunkCpp_HideVirtualKeyboardInput(); } else if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // remember target widget for contents VirtualKeyboardWidget = TextWidget; // call the java side auto LabelJava = FJavaHelper::ToJavaString(Env, Label); auto ContentsJava = FJavaHelper::ToJavaString(Env, Contents); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ShowVirtualKeyboardInput, InputType, *LabelJava, *ContentsJava); } } //This function is declared in the Java-defined class, GameActivity.java: "public native void nativeVirtualKeyboardResult(bool update, String contents);" JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardResult(JNIEnv* jenv, jobject thiz, jboolean update, jstring contents) { // update text widget with new contents if OK pressed if (update == JNI_TRUE) { if (VirtualKeyboardWidget.IsValid()) { FString Contents = FJavaHelper::FStringFromParam(jenv, contents); // call to set the widget text on game thread if (FTaskGraphInterface::IsRunning()) { FFunctionGraphTask::CreateAndDispatchWhenReady([Contents=Contents]() { TSharedPtr LockedKeyboardWidget(VirtualKeyboardWidget.Pin()); if (LockedKeyboardWidget.IsValid()) { LockedKeyboardWidget->SetTextFromVirtualKeyboard(FText::FromString(Contents), ETextEntryType::TextEntryAccepted); } // release reference VirtualKeyboardWidget.Reset(); }, TStatId(), NULL, ENamedThreads::GameThread); } else { // release reference VirtualKeyboardWidget.Reset(); } } } else { // release reference VirtualKeyboardWidget.Reset(); } } //This function is declared in the Java-defined class, GameActivity.java: "public native void nativeVirtualKeyboardChanged(String contents);" JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardChanged(JNIEnv* jenv, jobject thiz, jstring contents) { if (VirtualKeyboardWidget.IsValid()) { FString Contents = FJavaHelper::FStringFromParam(jenv, contents); // call to set the widget text on game thread if (FTaskGraphInterface::IsRunning()) { FFunctionGraphTask::CreateAndDispatchWhenReady([Contents=Contents]() { TSharedPtr LockedKeyboardWidget(VirtualKeyboardWidget.Pin()); if (LockedKeyboardWidget.IsValid()) { LockedKeyboardWidget->SetTextFromVirtualKeyboard(FText::FromString(Contents), ETextEntryType::TextEntryUpdated); } }, TStatId(), NULL, ENamedThreads::GameThread); } } } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardSendKey(JNIEnv* jenv, jobject thiz, jint keyCode) { if (FTaskGraphInterface::IsRunning()) { // Dispatch when ready to delay the submit when the text is being changed by another task (e.g Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardChanged) FFunctionGraphTask::CreateAndDispatchWhenReady([keyCode = keyCode, CurrentVirtualKeyboardWidget = VirtualKeyboardWidget]() { if (CurrentVirtualKeyboardWidget.IsValid() && CurrentVirtualKeyboardWidget == VirtualKeyboardWidget) { FDeferredAndroidMessage Message; Message.messageType = MessageType_KeyDown; Message.KeyEventData.keyId = keyCode; FAndroidInputInterface::DeferMessage(Message); } }, TStatId(), NULL, ENamedThreads::GameThread); } } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeVirtualKeyboardSendSelection(JNIEnv* jenv, jobject thiz, jint selStart, jint selEnd) { // call to set the widget selection on game thread if (VirtualKeyboardWidget.IsValid()) { if (FTaskGraphInterface::IsRunning()) { FGraphEventRef SetWidgetSelection = FFunctionGraphTask::CreateAndDispatchWhenReady([&, selStart, selEnd]() { TSharedPtr LockedKeyboardWidget(VirtualKeyboardWidget.Pin()); if (LockedKeyboardWidget.IsValid()) { LockedKeyboardWidget->SetSelectionFromVirtualKeyboard(selStart, selEnd); } }, TStatId(), NULL, ENamedThreads::GameThread); } } } void AndroidThunkCpp_LaunchURL(const FString& URL) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Argument = FJavaHelper::ToJavaString(Env, URL); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LaunchURL, *Argument); } } void AndroidThunkCpp_ShowAdBanner(const FString& AdUnitID, bool bShowOnBottomOfScreen) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto AdUnitIDArg = FJavaHelper::ToJavaString(Env, AdUnitID); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_ShowAdBanner, *AdUnitIDArg, bShowOnBottomOfScreen); } } void AndroidThunkCpp_HideAdBanner() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_HideAdBanner); } } void AndroidThunkCpp_CloseAdBanner() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_CloseAdBanner); } } void AndroidThunkCpp_LoadInterstitialAd(const FString& AdUnitID) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto AdUnitIDArg = FJavaHelper::ToJavaString(Env, AdUnitID); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_LoadInterstitialAd, *AdUnitIDArg); } } bool AndroidThunkCpp_IsInterstitialAdAvailable() { bool bIsAdAvailable = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { bIsAdAvailable = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IsInterstitialAdAvailable); } return bIsAdAvailable; } bool AndroidThunkCpp_IsInterstitialAdRequested() { bool bIsAdRequested = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { bIsAdRequested = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IsInterstitialAdRequested); } return bIsAdRequested; } void AndroidThunkCpp_ShowInterstitialAd() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_ShowInterstitialAd); } } FString AndroidThunkCpp_GetAdvertisingId() { FString adIdResult = FString(""); if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { adIdResult = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_GetAdvertisingId)); } return adIdResult; } FString AndroidThunkCpp_GetAndroidId() { FString androidIdResult = FString(""); if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { androidIdResult = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetAndroidId)); } return androidIdResult; } void AndroidThunkCpp_ShareURL(const FString& URL, const FText& Description, const FText& SharePrompt, int32 LocationHintX, int32 LocationHintY) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto URLArg = FJavaHelper::ToJavaString(Env, URL); auto DescArg = FJavaHelper::ToJavaString(Env, Description.ToString()); auto PromptArg = FJavaHelper::ToJavaString(Env, SharePrompt.ToString()); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ShareURL, *URLArg, *DescArg, *PromptArg, LocationHintX, LocationHintY); } } bool AndroidThunkCpp_IsPackageInstalled(const FString& PackageName) { bool result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto PackageNameArg = FJavaHelper::ToJavaString(Env, PackageName); result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsPackageInstalled, *PackageNameArg); } return result; } bool AndroidThunkCpp_LaunchPackage(const FString& PackageName, const FString& ExtraKey, const FString& ExtraValue) { bool result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto PackageNameArg = FJavaHelper::ToJavaString(Env, PackageName); auto ExtraKeyArg = FJavaHelper::ToJavaString(Env, ExtraKey); auto ExtraValueArg = FJavaHelper::ToJavaString(Env, ExtraValue); result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LaunchPackage, *PackageNameArg, *ExtraKeyArg, *ExtraValueArg); } return result; } bool AndroidThunkCpp_SendBroadcast(const FString& PackageName, const FString& ExtraKey, const FString& ExtraValue, bool bExit) { bool result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto PackageNameArg = FJavaHelper::ToJavaString(Env, PackageName); auto ExtraKeyArg = FJavaHelper::ToJavaString(Env, ExtraKey); auto ExtraValueArg = FJavaHelper::ToJavaString(Env, ExtraValue); result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SendBroadcast, *PackageNameArg, *ExtraKeyArg, *ExtraValueArg, bExit); } return result; } namespace { jobject GJavaAssetManager = NULL; AAssetManager* GAssetManagerRef = NULL; } jobject AndroidJNI_GetJavaAssetManager() { if (!GJavaAssetManager) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto local = NewScopedJavaObject(Env, FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetAssetManager)); GJavaAssetManager = (jobject)Env->NewGlobalRef(*local); checkf(GJavaAssetManager != nullptr, TEXT("Failed get GJavaAssetManager!")); } else { checkf(FAndroidApplication::GetJavaEnv() != nullptr, TEXT("Failed get FAndroidApplication::GetJavaEnv() ")); } } return GJavaAssetManager; } AAssetManager * AndroidThunkCpp_GetAssetManager() { if (!GAssetManagerRef) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { jobject JavaAssetMgr = AndroidJNI_GetJavaAssetManager(); GAssetManagerRef = AAssetManager_fromJava(Env, JavaAssetMgr); } } return GAssetManagerRef; } void AndroidThunkCpp_Minimize() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_Minimize); } } void AndroidThunkCpp_ForceQuit() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ForceQuit); } } void AndroidThunkCpp_SetOrientation(int32 Value) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetOrientation, Value); } } bool AndroidThunkCpp_HasSharedPreference(const FString& Group, const FString& Key) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_HasSharedPreference, *GroupArg, *KeyArg); } return Result; } void AndroidThunkCpp_SetSharedPreferenceBool(const FString& Group, const FString& Key, bool Value) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetSharedPreferenceBoolean, *GroupArg, *KeyArg, Value); } } bool AndroidThunkCpp_GetSharedPreferenceBool(const FString& Group, const FString& Key, bool DefaultValue) { bool Result = DefaultValue; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSharedPreferenceBoolean, *GroupArg, *KeyArg, DefaultValue); } return Result; } bool AndroidThunkCpp_GetSharedPreferenceBoolTypeSafe(const FString& Group, const FString& Key, bool& OutValue) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); bool bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSharedPreferenceBoolean, *GroupArg, *KeyArg, false); // SharedPreferences.getBoolean will throw an exception if there is a type mismatch if (!Env->ExceptionCheck()) { OutValue = bResult; return true; } else { Env->ExceptionClear(); return false; } } return false; } void AndroidThunkCpp_SetSharedPreferenceInt(const FString& Group, const FString& Key, int32 Value) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetSharedPreferenceInt, *GroupArg, *KeyArg, Value); } } int32 AndroidThunkCpp_GetSharedPreferenceInt(const FString& Group, const FString& Key, int32 DefaultValue) { int32 Result = DefaultValue; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); Result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSharedPreferenceInt, *GroupArg, *KeyArg, DefaultValue); } return Result; } bool AndroidThunkCpp_GetSharedPreferenceIntTypeSafe(const FString& Group, const FString& Key, int32& OutValue) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); int32 Result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSharedPreferenceInt, *GroupArg, *KeyArg, 0); // SharedPreferences.getInt will throw an exception if there is a type mismatch if (!Env->ExceptionCheck()) { OutValue = Result; return true; } else { Env->ExceptionClear(); return false; } } return false; } void AndroidThunkCpp_SetSharedPreferenceString(const FString& Group, const FString& Key, const FString& Value) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); auto ValueArg = FJavaHelper::ToJavaString(Env, Value); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetSharedPreferenceString, *GroupArg, *KeyArg, *ValueArg); } } FString AndroidThunkCpp_GetSharedPreferenceString(const FString& Group, const FString& Key, const FString& DefaultValue) { FString Result = DefaultValue; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); auto DefaultValueArg = FJavaHelper::ToJavaString(Env, DefaultValue); Result = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSharedPreferenceString, *GroupArg, *KeyArg, *DefaultValueArg)); } return Result; } FString AndroidThunkCpp_GetCacheDir() { FString Result; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetCacheDir)); } return Result; } bool AndroidThunkCpp_GetSharedPreferenceStringTypeSafe(const FString& Group, const FString& Key, FString& OutValue) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); auto ResultObject = FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSharedPreferenceString, *GroupArg, *KeyArg, NULL); // SharedPreferences.getString will throw an exception if there is a type mismatch if (Env->ExceptionCheck()) { Env->ExceptionClear(); return false; } else if (ResultObject && !Env->IsSameObject(ResultObject, NULL)) { OutValue = FJavaHelper::FStringFromLocalRef(Env, (jstring)ResultObject); return true; } } return false; } void AndroidThunkCpp_DeleteSharedPreference(const FString& Group, const FString& Key) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); auto KeyArg = FJavaHelper::ToJavaString(Env, Key); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_DeleteSharedPreference, *GroupArg, *KeyArg); } } void AndroidThunkCpp_DeleteSharedPreferenceGroup(const FString& Group) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto GroupArg = FJavaHelper::ToJavaString(Env, Group); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_DeleteSharedPreferenceGroup, *GroupArg); } } bool AndroidThunkCpp_IsMusicActive() { bool bIsActive = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { bIsActive = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_IsMusicActive); } return bIsActive; } void AndroidThunkCpp_Iap_SetupIapService(const FString& InProductKey) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto ProductKey = FJavaHelper::ToJavaString(Env, InProductKey); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapSetupService, *ProductKey); } } bool AndroidThunkCpp_Iap_QueryInAppPurchases(const TArray& ProductIDs) { FPlatformMisc::LowLevelOutputDebugString(TEXT("[JNI] - AndroidThunkCpp_Iap_QueryInAppPurchases")); bool bResult = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { CHECK_JNI_METHOD(FJavaWrapper::AndroidThunkJava_IapQueryInAppPurchases); // Populate some java types with the provided product information auto ProductIDArray = NewScopedJavaObject(Env, (jobjectArray)Env->NewObjectArray(ProductIDs.Num(), FJavaWrapper::JavaStringClass, NULL)); if (ProductIDArray) { for (uint32 Param = 0; Param < ProductIDs.Num(); Param++) { auto StringValue = FJavaHelper::ToJavaString(Env, ProductIDs[Param]); Env->SetObjectArrayElement(*ProductIDArray, Param, *StringValue); } // Execute the java code for this operation bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapQueryInAppPurchases, *ProductIDArray); } } return bResult; } bool AndroidThunkCpp_Iap_QueryInAppPurchases(const TArray& ProductIDs, const TArray& bConsumable) { FPlatformMisc::LowLevelOutputDebugString(TEXT("AndroidThunkCpp_Iap_QueryInAppPurchases DEPRECATED, won't use consumables array")); return AndroidThunkCpp_Iap_QueryInAppPurchases(ProductIDs); } bool AndroidThunkCpp_Iap_BeginPurchase(const TArray& ProductIds, const FString& AccountId) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_BeginPurchase")); bool bResult = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { CHECK_JNI_METHOD(FJavaWrapper::AndroidThunkJava_IapBeginPurchase); auto ProductIdsJava = FJavaHelper::ToJavaStringArray(Env, ProductIds); if (AccountId.IsEmpty()) { bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapBeginPurchase, *ProductIdsJava, nullptr); } else { auto ObfuscatedAccountIdJava = FJavaHelper::ToJavaString(Env, AccountId); bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapBeginPurchase, *ProductIdsJava, *ObfuscatedAccountIdJava); } } return bResult; } bool AndroidThunkCpp_Iap_ConsumePurchase(const FString& ProductToken) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_ConsumePurchase %s"), *ProductToken); bool bResult = false; if (!ProductToken.IsEmpty()) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { CHECK_JNI_METHOD(FJavaWrapper::AndroidThunkJava_IapConsumePurchase); auto ProductTokenJava = FJavaHelper::ToJavaString(Env, ProductToken); //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_ConsumePurchase BEGIN")); bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapConsumePurchase, *ProductTokenJava); //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_ConsumePurchase END")); } } return bResult; } bool AndroidThunkCpp_Iap_AcknowledgePurchase(const FString& ProductToken) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_AcknowledgePurchase %s"), *ProductToken); bool bResult = false; if (!ProductToken.IsEmpty()) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { CHECK_JNI_METHOD(FJavaWrapper::AndroidThunkJava_IapAcknowledgePurchase); auto ProductTokenJava = FJavaHelper::ToJavaString(Env, ProductToken); //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_AcknowledgePurchase BEGIN")); bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapAcknowledgePurchase, *ProductTokenJava); //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_AcknowledgePurchase END")); } } return bResult; } bool AndroidThunkCpp_Iap_QueryExistingPurchases() { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_QueryExistingPurchases")); bool bResult = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { CHECK_JNI_METHOD(FJavaWrapper::AndroidThunkJava_IapQueryExistingPurchases); //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_QueryExistingPurchases BEGIN")); bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapQueryExistingPurchases); //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("[JNI] - AndroidThunkCpp_Iap_QueryExistingPurchases END")); } return bResult; } bool AndroidThunkCpp_Iap_IsAllowedToMakePurchases() { FPlatformMisc::LowLevelOutputDebugString(TEXT("[JNI] - AndroidThunkCpp_Iap_IsAllowedToMakePurchases")); bool bResult = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { CHECK_JNI_METHOD(FJavaWrapper::AndroidThunkJava_IapIsAllowedToMakePurchases); bResult = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GoogleServicesThis, FJavaWrapper::AndroidThunkJava_IapIsAllowedToMakePurchases); } return bResult; } void AndroidThunkCpp_UseSurfaceViewWorkaround() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_UseSurfaceViewWorkaround); } } void AndroidThunkCpp_SetDesiredViewSize(int32 Width, int32 Height) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetDesiredViewSize, Width, Height); } // #if BUILD_EMBEDDED_APP // also send info to native wrapper around embedded FEmbeddedCallParamsHelper Helper; Helper.Command = TEXT("setueresolution"); Helper.Parameters = { {TEXT("width"), LexToString(Width)}, {TEXT("height"), LexToString(Height)} }; FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("native")).Broadcast(Helper); // #endif } int32 AndroidThunkCpp_ScheduleLocalNotificationAtTime(const FDateTime& FireDateTime, bool LocalTime, const FText& Title, const FText& Body, const FText& Action, const FString& ActivationEvent, int32 IdOverride) { //Convert FireDateTime to yyyy-MM-dd HH:mm:ss in order to pass to java FString FireDateTimeFormatted = FString::FromInt(FireDateTime.GetYear()) + "-" + FString::FromInt(FireDateTime.GetMonth()) + "-" + FString::FromInt(FireDateTime.GetDay()) + " " + FString::FromInt(FireDateTime.GetHour()) + ":" + FString::FromInt(FireDateTime.GetMinute()) + ":" + FString::FromInt(FireDateTime.GetSecond()); JNIEnv* Env = FAndroidApplication::GetJavaEnv(); if (Env != NULL) { auto jFireDateTime = FJavaHelper::ToJavaString(Env, FireDateTimeFormatted); auto jTitle = FJavaHelper::ToJavaString(Env, Title.ToString()); auto jBody = FJavaHelper::ToJavaString(Env, Body.ToString()); auto jAction = FJavaHelper::ToJavaString(Env, Action.ToString()); auto jActivationEvent = FJavaHelper::ToJavaString(Env, ActivationEvent); return FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LocalNotificationScheduleAtTime, *jFireDateTime, LocalTime, *jTitle, *jBody, *jAction, *jActivationEvent, IdOverride); } return -1; } void AndroidThunkCpp_GetLaunchNotification(bool& NotificationLaunchedApp, FString& ActivationEvent, int32& FireDate) { bool bIsOptional = false; NotificationLaunchedApp = false; ActivationEvent = ""; FireDate = 0; JNIEnv* Env = FAndroidApplication::GetJavaEnv(); if (Env != NULL) { auto launchInfo = NewScopedJavaObject(Env, (jobject)Env->CallObjectMethod(FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LocalNotificationGetLaunchNotification)); if (launchInfo && FJavaWrapper::LaunchNotificationUsed != 0) { NotificationLaunchedApp = (bool)Env->GetBooleanField(*launchInfo, FJavaWrapper::LaunchNotificationUsed); ActivationEvent = FJavaHelper::FStringFromLocalRef(Env, (jstring)Env->GetObjectField(*launchInfo, FJavaWrapper::LaunchNotificationEvent)); FireDate = (int32)Env->GetIntField(*launchInfo, FJavaWrapper::LaunchNotificationFireDate); } } } void AndroidThunkCpp_ClearAllLocalNotifications() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LocalNotificationClearAll); } } bool AndroidThunkCpp_LocalNotificationExists(int32 NotificationId) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LocalNotificationExists, NotificationId); } return Result; } bool AndroidThunkCpp_DestroyScheduledNotificationIfExists(int32 NotificationId) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_LocalNotificationDestroyIfExists, NotificationId); } return Result; } int32 AndroidThunkCpp_GetNetworkConnectionType() { int32 result = -1; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetNetworkConnectionType); } return result; } bool AndroidThunkCpp_AcquireWifiManagerMulticastLock() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { return FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_AcquireWifiManagerMulticastLock); } return false; } void AndroidThunkCpp_ReleaseWifiManagerMulticastLock() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ReleaseWifiManagerMulticastLock); } } //The JNI_OnLoad function is triggered by loading the game library from //the Java source file. // static // { // System.loadLibrary("MyGame"); // } // // Use the JNI_OnLoad function to map all the class IDs and method IDs to their respective // variables. That way, later when the Java functions need to be called, the IDs will be ready. // It is much slower to keep looking up the class and method IDs. JNIEXPORT jint JNI_OnLoad(JavaVM* InJavaVM, void* InReserved) { FTaskTagScope::SetTagNone(); FPlatformMisc::LowLevelOutputDebugString(TEXT("In the JNI_OnLoad function")); JNIEnv* Env = NULL; InJavaVM->GetEnv((void **)&Env, JNI_CURRENT_VERSION); // if you have problems with stuff being missing especially in distribution builds then it could be because proguard is stripping things from java // check proguard-project.txt and see if your stuff is included in the exceptions GJavaVM = InJavaVM; FAndroidApplication::InitializeJavaEnv(GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis); FJavaWrapper::FindClassesAndMethods(Env); // hook signals if (!FPlatformMisc::IsDebuggerPresent() || GAlwaysReportCrash) { // disable crash handler.. getting better stack traces from system for now //FPlatformMisc::SetCrashHandler(EngineCrashHandler); } // Cache path to external storage auto EnvClass = NewScopedJavaObject(Env, Env->FindClass("android/os/Environment")); jmethodID getExternalStorageDir = Env->GetStaticMethodID(*EnvClass, "getExternalStorageDirectory", "()Ljava/io/File;"); auto externalStoragePath = NewScopedJavaObject(Env, Env->CallStaticObjectMethod(*EnvClass, getExternalStorageDir, nullptr)); jmethodID getFilePath = Env->GetMethodID(Env->FindClass("java/io/File"), "getPath", "()Ljava/lang/String;"); // Copy that somewhere safe GFilePathBase = FJavaHelper::FStringFromLocalRef(Env, (jstring)Env->CallObjectMethod(*externalStoragePath, getFilePath, nullptr)); GOBBFilePathBase = GFilePathBase; GOBBMainFilePath = TEXT(""); GOBBPatchFilePath = TEXT(""); GOBBOverflow1FilePath = TEXT(""); GOBBOverflow2FilePath = TEXT(""); // then release... FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Path found as '%s'\n"), *GFilePathBase); // Get the system font directory GFontPathBase = FJavaHelper::FStringFromLocalRef(Env, (jstring)Env->CallStaticObjectMethod(FJavaWrapper::GameActivityClassID, FJavaWrapper::AndroidThunkJava_GetFontDirectory)); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Font Path found as '%s'\n"), *GFontPathBase); // Wire up to core delegates, so core code can call out to Java DECLARE_DELEGATE_OneParam(FAndroidLaunchURLDelegate, const FString&); extern CORE_API FAndroidLaunchURLDelegate OnAndroidLaunchURL; OnAndroidLaunchURL = FAndroidLaunchURLDelegate::CreateStatic(&AndroidThunkCpp_LaunchURL); FPlatformMisc::LowLevelOutputDebugString(TEXT("In the JNI_OnLoad function 5")); return JNI_CURRENT_VERSION; } //Native-defined functions //This function is declared in the Java-defined class, GameActivity.java: "public native void naativeSetObbFilePaths();" JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeSetObbFilePaths(JNIEnv* jenv, jobject thiz, jstring OBBMainFilePath, jstring OBBPatchFilePath, jstring OBBOverflow1FilePath, jstring OBBOverflow2FilePath) { GOBBMainFilePath = FJavaHelper::FStringFromParam(jenv, OBBMainFilePath); GOBBPatchFilePath = FJavaHelper::FStringFromParam(jenv, OBBPatchFilePath); GOBBOverflow1FilePath = FJavaHelper::FStringFromParam(jenv, OBBOverflow1FilePath); GOBBOverflow2FilePath = FJavaHelper::FStringFromParam(jenv, OBBOverflow2FilePath); } //This function is declared in the Java-defined class, GameActivity.java: "public native void nativeSetGlobalActivity();" JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeSetGlobalActivity(JNIEnv* jenv, jobject thiz, jboolean bUseExternalFilesDir, jboolean bPublicLogFiles, jstring internalFilePath, jstring externalFilePath, jboolean bOBBinAPK, jstring APKFilename /*, jobject googleServices*/) { STANDALONE_DEBUG_LOG(TEXT("nativeSetGlobalActivity(unreal): Entering unreal nativeSetGlobalActivity, GameActivityThis=%p\n"), FJavaWrapper::GameActivityThis); if (FJavaWrapper::GameActivityThis != nullptr) { STANDALONE_DEBUG_LOG(TEXT("nativeSetGlobalActivity(unreal): Error GameActivityThis is already set GameActivityThis=%p\n"), FJavaWrapper::GameActivityThis); //jenv->DeleteGlobalRef(FJavaWrapper::GameActivityThis); GGameActivityThis = FJavaWrapper::GameActivityThis = nullptr; } if (!FJavaWrapper::GameActivityThis) { GGameActivityThis = FJavaWrapper::GameActivityThis = jenv->NewGlobalRef(thiz); if (!FJavaWrapper::GameActivityThis) { FPlatformMisc::LowLevelOutputDebugString(TEXT("Error setting the global GameActivity activity")); check(false); } STANDALONE_DEBUG_LOG(TEXT("nativeSetGlobalActivity(unreal): jenv=%p, set GameActivityThis=%p, bUseExternalFilesDir=%d, bOBBinAPK=%d\n"), jenv, FJavaWrapper::GameActivityThis, bUseExternalFilesDir, bOBBinAPK); // This call is only to set the correct GameActivityThis FAndroidApplication::InitializeJavaEnv(GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis); // @todo split GooglePlay, this needs to be passed in to this function FJavaWrapper::GoogleServicesThis = FJavaWrapper::GameActivityThis; // FJavaWrapper::GoogleServicesThis = jenv->NewGlobalRef(googleServices); // Next we check to see if the OBB file is in the APK //jmethodID isOBBInAPKMethod = jenv->GetStaticMethodID(FJavaWrapper::GameActivityClassID, "isOBBInAPK", "()Z"); //GOBBinAPK = (bool)jenv->CallStaticBooleanMethod(FJavaWrapper::GameActivityClassID, isOBBInAPKMethod, nullptr); GOBBinAPK = bOBBinAPK; GAPKFilename = FJavaHelper::FStringFromParam(jenv, APKFilename); GInternalFilePath = FJavaHelper::FStringFromParam(jenv, internalFilePath); GExternalFilePath = FJavaHelper::FStringFromParam(jenv, externalFilePath); if (bUseExternalFilesDir) { #if UE_BUILD_SHIPPING GFilePathBase = GInternalFilePath; GOverrideAndroidLogDir = bPublicLogFiles; #else GFilePathBase = GExternalFilePath; #endif FPlatformMisc::LowLevelOutputDebugStringf(TEXT("nativeSetGlobalActivity(unreal): GFilePathBase Path override to'%s'\n"), *GFilePathBase); } FPlatformMisc::LowLevelOutputDebugStringf(TEXT("nativeSetGlobalActivity(unreal): InternalFilePath found as '%s'\n"), *GInternalFilePath); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("nativeSetGlobalActivity(unreal): ExternalFilePath found as '%s'\n"), *GExternalFilePath); } } JNI_METHOD bool Java_com_epicgames_unreal_GameActivity_nativeIsShippingBuild(JNIEnv* LocalJNIEnv, jobject LocalThiz) { #if UE_BUILD_SHIPPING return JNI_TRUE; #else return JNI_FALSE; #endif } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeOnActivityResult(JNIEnv* jenv, jobject thiz, jobject activity, jint requestCode, jint resultCode, jobject data) { FJavaWrapper::OnActivityResultDelegate.Broadcast(jenv, thiz, activity, requestCode, resultCode, data); } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeHandleSensorEvents(JNIEnv* jenv, jobject thiz, jfloatArray tilt, jfloatArray rotation_rate, jfloatArray gravity, jfloatArray acceleration) { jfloat* tiltFloatValues = jenv->GetFloatArrayElements(tilt, 0); FVector current_tilt(tiltFloatValues[0], tiltFloatValues[1], tiltFloatValues[2]); jenv->ReleaseFloatArrayElements(tilt, tiltFloatValues, 0); jfloat* rotation_rate_FloatValues = jenv->GetFloatArrayElements(rotation_rate, 0); FVector current_rotation_rate(rotation_rate_FloatValues[0], rotation_rate_FloatValues[1], rotation_rate_FloatValues[2]); jenv->ReleaseFloatArrayElements(rotation_rate, rotation_rate_FloatValues, 0); jfloat* gravity_FloatValues = jenv->GetFloatArrayElements(gravity, 0); FVector current_gravity(gravity_FloatValues[0], gravity_FloatValues[1], gravity_FloatValues[2]); jenv->ReleaseFloatArrayElements(gravity, gravity_FloatValues, 0); jfloat* acceleration_FloatValues = jenv->GetFloatArrayElements(acceleration, 0); FVector current_acceleration(acceleration_FloatValues[0], acceleration_FloatValues[1], acceleration_FloatValues[2]); jenv->ReleaseFloatArrayElements(acceleration, acceleration_FloatValues, 0); FAndroidInputInterface::QueueMotionData(current_tilt, current_rotation_rate, current_gravity, current_acceleration); } void AndroidThunkCpp_ClipboardCopy(const FString& Str) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto JStr = FJavaHelper::ToJavaString(Env, Str); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ClipboardCopy, *JStr); } } FString AndroidThunkCpp_ClipboardPaste() { FString PasteStringResult = FString(""); if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { PasteStringResult = FJavaHelper::FStringFromLocalRef(Env, (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_ClipboardPaste)); } return PasteStringResult; } TArray AndroidThunkCpp_GetSupportedNativeDisplayRefreshRates() { TArray Result; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { jintArray JavaIntArray = (jintArray)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetSupportedNativeDisplayRefreshRates); if (JavaIntArray != nullptr) { jint* JavaInts = Env->GetIntArrayElements(JavaIntArray, 0); if (JavaInts) { jsize IntsLen = Env->GetArrayLength(JavaIntArray); Result.Empty(IntsLen); for (int32 Idx = 0; Idx < IntsLen; Idx++) { Result.Add(JavaInts[Idx]); } Env->ReleaseIntArrayElements(JavaIntArray, JavaInts, 0); } Env->DeleteLocalRef(JavaIntArray); } } return Result; } void AndroidThunkJava_AddNetworkListener() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_AddNetworkListener); } } void AndroidThunkJava_RemoveNetworkListener() { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_RemoveNetworkListener); } } bool AndroidThunkCpp_SetNativeDisplayRefreshRate(int32 RefreshRate) { bool Result = false; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallBooleanMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_SetNativeDisplayRefreshRate, RefreshRate); } return Result; } int32 AndroidThunkCpp_GetNativeDisplayRefreshRate() { int32 Result = 60; if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { Result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_GetNativeDisplayRefreshRate); } return Result; } void AndroidThunkCpp_EnableMotion(bool bEnable) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // call the java side FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, FJavaWrapper::AndroidThunkJava_EnableMotion, bEnable); } } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeOnSafetyNetAttestationSucceeded(JNIEnv* jenv, jobject thiz, jstring jwsData) { FString JwsString = FJavaHelper::FStringFromParam(jenv, jwsData); // call to OnSafetyNetAttestationResultDelegate on game thread if (FTaskGraphInterface::IsRunning()) { FGraphEventRef SafetyNetAttestationSucceeded = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() { FJavaWrapper::OnSafetyNetAttestationResultDelegate.Broadcast(true, JwsString, 0); }, TStatId(), NULL, ENamedThreads::GameThread); FTaskGraphInterface::Get().WaitUntilTaskCompletes(SafetyNetAttestationSucceeded); } } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeOnSafetyNetAttestationFailed(JNIEnv* jenv, jobject thiz, jint jwsValue) { // call to OnSafetyNetAttestationResultDelegate on game thread if (FTaskGraphInterface::IsRunning()) { FGraphEventRef SafetyNetAttestationFailed = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() { FJavaWrapper::OnSafetyNetAttestationResultDelegate.Broadcast(false, FString(TEXT("")), jwsValue); }, TStatId(), NULL, ENamedThreads::GameThread); FTaskGraphInterface::Get().WaitUntilTaskCompletes(SafetyNetAttestationFailed); } } #include "Async/TaskGraphInterfaces.h" static jmethodID ReplyMethod; void AndroidThunkCpp_OnNativeToEmbeddedReply(FString ID, const FEmbeddedCommunicationMap& InReturnValues, FString InError, FString RoutingFunction) { if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { // marshall some data #if !UE_BUILD_SHIPPING UE_LOG(LogInit, Display, TEXT("Java call Id: %s, Routing Function: %s, Error %s, Params:"), *ID, *RoutingFunction, *InError); #endif // create a string array for the pairs static auto StringClass = FJavaWrapper::FindClassGlobalRef(Env, "java/lang/String", false); auto ReturnValues = NewScopedJavaObject(Env, Env->NewObjectArray(InReturnValues.Num() * 2, StringClass, 0)); int32 Index = 0; for (auto It : InReturnValues) { auto KeyString = FJavaHelper::ToJavaString(Env, It.Key); auto ValueString = FJavaHelper::ToJavaString(Env, It.Value); Env->SetObjectArrayElement(*ReturnValues, Index++, *KeyString); Env->SetObjectArrayElement(*ReturnValues, Index++, *ValueString); #if !UE_BUILD_SHIPPING UE_LOG(LogInit, Display, TEXT(" %s : %s"), *It.Key, *It.Value); #endif } auto Error = FJavaHelper::ToJavaString(Env, InError); // call back into java auto IDReturn = FJavaHelper::ToJavaString(Env, ID); auto RoutingFunctionReturn = FJavaHelper::ToJavaString(Env, RoutingFunction); FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, ReplyMethod, *IDReturn, *ReturnValues, *Error, *RoutingFunctionReturn); } } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_CallNativeToEmbedded(JNIEnv* jenv, jobject thiz, jstring InID, jint Priority, jstring InSubsystem, jstring InCommand, jobjectArray InParams, jstring InRoutingFunction) { #if BUILD_EMBEDDED_APP auto Subsystem = FJavaHelper::FStringFromParam(jenv, InSubsystem); auto Command = FJavaHelper::FStringFromParam(jenv, InCommand); auto ID = FJavaHelper::FStringFromParam(jenv, InID); auto RoutingFunction = FJavaHelper::FStringFromParam(jenv, InRoutingFunction); FEmbeddedCallParamsHelper Helper; Helper.Command = Command; FName SubsystemName(*Subsystem); FName CommandName = *FString::Printf(TEXT("%s_%s"), *Subsystem, *Command); // wake up UE FEmbeddedCommunication::KeepAwake(CommandName, false); #if !UE_BUILD_SHIPPING FPlatformMisc::LowLevelOutputDebugStringf(TEXT("NativeToEmbeddedCall: Subsystem: %s, Command: %s, Params:"), *Subsystem, *Helper.Command); #endif if (InParams != nullptr) { int32 Count = jenv->GetArrayLength(InParams); int32 Index = 0; while (Index < Count) { auto javaKey = FJavaHelper::FStringFromLocalRef(jenv, (jstring)(jenv->GetObjectArrayElement(InParams, Index++))); auto javaValue = FJavaHelper::FStringFromLocalRef(jenv, (jstring)(jenv->GetObjectArrayElement(InParams, Index++))); Helper.Parameters.Add(javaKey, javaValue); #if !UE_BUILD_SHIPPING FPlatformMisc::LowLevelOutputDebugStringf(TEXT(" %s : %s"), *javaKey, *javaValue); #endif } } Helper.OnCompleteDelegate = [CommandName, ID, RoutingFunction](const FEmbeddedCommunicationMap& InReturnValues, FString InError) { // wake up UE FEmbeddedCommunication::AllowSleep(CommandName); AndroidThunkCpp_OnNativeToEmbeddedReply(ID, InReturnValues, InError, RoutingFunction); }; FEmbeddedCommunication::RunOnGameThread(Priority, [SubsystemName, Helper]() { if (FEmbeddedDelegates::GetNativeToEmbeddedParamsDelegateForSubsystem(SubsystemName).IsBound()) { FEmbeddedDelegates::GetNativeToEmbeddedParamsDelegateForSubsystem(SubsystemName).Broadcast(Helper); } else { Helper.OnCompleteDelegate({} , FString::Printf(TEXT("No one is listening to subsystem %s"), *SubsystemName.ToString())); } }); #endif // BUILD_EMBEDDED_APP } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_SetNamedObject(JNIEnv* jenv, jobject thiz, jstring InName, jobject InObj) { auto Name = FJavaHelper::FStringFromParam(jenv, InName); FEmbeddedDelegates::SetNamedObject(Name, (void*)InObj); } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_KeepAwake(JNIEnv* jenv, jobject thiz, jstring InRequester, jboolean bIsForRendering) { auto Requester = FJavaHelper::FStringFromParam(jenv, InRequester); FEmbeddedCommunication::KeepAwake(*Requester, bIsForRendering); } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_AllowSleep(JNIEnv* jenv, jobject thiz, jstring InRequester) { auto Requester = FJavaHelper::FStringFromParam(jenv, InRequester); FEmbeddedCommunication::AllowSleep(*Requester); } #if !BUILD_EMBEDDED_APP DEFINE_LOG_CATEGORY_STATIC(LogJava, Log, All); #endif JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_UELogError(JNIEnv* jenv, jobject thiz, jstring InString) { const auto chars = jenv->GetStringUTFChars(InString, 0); #if BUILD_EMBEDDED_APP FEmbeddedCommunication::UELogError(UTF8_TO_TCHAR(chars)); #else if (GLog && UE_LOG_ACTIVE(LogJava, Error)) { GLog->Log("LogJava", ELogVerbosity::Error, UTF8_TO_TCHAR(chars)); } #endif jenv->ReleaseStringUTFChars(InString, chars); } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_UELogWarning(JNIEnv* jenv, jobject thiz, jstring InString) { const auto chars = jenv->GetStringUTFChars(InString, 0); #if BUILD_EMBEDDED_APP FEmbeddedCommunication::UELogWarning(UTF8_TO_TCHAR(chars)); #else if (GLog && UE_LOG_ACTIVE(LogJava, Warning)) { GLog->Log("LogJava", ELogVerbosity::Warning, UTF8_TO_TCHAR(chars)); } #endif jenv->ReleaseStringUTFChars(InString, chars); } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_UELogLog(JNIEnv* jenv, jobject thiz, jstring InString) { const auto chars = jenv->GetStringUTFChars(InString, 0); #if BUILD_EMBEDDED_APP FEmbeddedCommunication::UELogLog(UTF8_TO_TCHAR(chars)); #else if (GLog && UE_LOG_ACTIVE(LogJava, Log)) { GLog->Log("LogJava", ELogVerbosity::Log, UTF8_TO_TCHAR(chars)); } #endif jenv->ReleaseStringUTFChars(InString, chars); } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_UELogVerbose(JNIEnv* jenv, jobject thiz, jstring InString) { const auto chars = jenv->GetStringUTFChars(InString, 0); #if BUILD_EMBEDDED_APP FEmbeddedCommunication::UELogVerbose(UTF8_TO_TCHAR(chars)); #else if (GLog && UE_LOG_ACTIVE(LogJava, Verbose)) { GLog->Log("LogJava", ELogVerbosity::Verbose, UTF8_TO_TCHAR(chars)); } #endif jenv->ReleaseStringUTFChars(InString, chars); } #if USE_ANDROID_STANDALONE JNI_METHOD void Java_com_epicgames_asis_AsisGameActivity_nativeSetGlobalActivity(JNIEnv* jenv, jobject thiz, jboolean bUseExternalFilesDir, jboolean bPublicLogFiles, jstring internalFilePath, jstring externalFilePath, jboolean bOBBinAPK, jstring APKFilename /*, jobject googleServices*/) { STANDALONE_DEBUG_LOG(TEXT("nativeSetGlobalActivity: Entering asis nativeSetGlobalActivity, GameActivityThis=%p\n"), FJavaWrapper::GameActivityThis); if (FJavaWrapper::GameActivityThis != nullptr) { STANDALONE_DEBUG_LOG(TEXT("nativeSetGlobalActivity(asis): Error GameActivityThis is already set GameActivityThis=%p\n"), FJavaWrapper::GameActivityThis); //jenv->DeleteGlobalRef(FJavaWrapper::GameActivityThis); GGameActivityThis = FJavaWrapper::GameActivityThis = nullptr; } if (!FJavaWrapper::GameActivityThis) { GGameActivityThis = FJavaWrapper::GameActivityThis = jenv->NewGlobalRef(thiz); if (!FJavaWrapper::GameActivityThis) { FPlatformMisc::LowLevelOutputDebugString(TEXT("Error setting the global GameActivity activity")); check(false); } STANDALONE_DEBUG_LOG(TEXT("nativeSetGlobalActivity(asis): jenv=%p, set GameActivityThis=%p, bUseExternalFilesDir=%d, bOBBinAPK=%d\n"), jenv, FJavaWrapper::GameActivityThis, bUseExternalFilesDir, bOBBinAPK); // This call is only to set the correct GameActivityThis FAndroidApplication::InitializeJavaEnv(GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis); // Rescan methods since we are switching to the asis version of GameActivity FJavaWrapper::FindClassesAndMethods(jenv); // @todo split GooglePlay, this needs to be passed in to this function FJavaWrapper::GoogleServicesThis = FJavaWrapper::GameActivityThis; // FJavaWrapper::GoogleServicesThis = jenv->NewGlobalRef(googleServices); // Next we check to see if the OBB file is in the APK //jmethodID isOBBInAPKMethod = jenv->GetStaticMethodID(FJavaWrapper::GameActivityClassID, "isOBBInAPK", "()Z"); //GOBBinAPK = (bool)jenv->CallStaticBooleanMethod(FJavaWrapper::GameActivityClassID, isOBBInAPKMethod, nullptr); GOBBinAPK = bOBBinAPK; GAPKFilename = FJavaHelper::FStringFromParam(jenv, APKFilename); GInternalFilePath = FJavaHelper::FStringFromParam(jenv, internalFilePath); GExternalFilePath = FJavaHelper::FStringFromParam(jenv, externalFilePath); if (bUseExternalFilesDir) { #if UE_BUILD_SHIPPING GFilePathBase = GInternalFilePath; GOverrideAndroidLogDir = bPublicLogFiles; #else GFilePathBase = GExternalFilePath; #endif FPlatformMisc::LowLevelOutputDebugStringf(TEXT("nativeSetGlobalActivity(asis): GFilePathBase Path override to'%s'\n"), *GFilePathBase); } FPlatformMisc::LowLevelOutputDebugStringf(TEXT("nativeSetGlobalActivity(asis): InternalFilePath found as '%s'\n"), *GInternalFilePath); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("nativeSetGlobalActivity(asis): ExternalFilePath found as '%s'\n"), *GExternalFilePath); } // DO NOT CALL, we have already handled logic above... // Java_com_epicgames_unreal_GameActivity_nativeSetGlobalActivity(jenv, thiz, bUseExternalFilesDir, bPublicLogFiles, internalFilePath, externalFilePath, bOBBinAPK, APKFilename); } #endif // USE_ANDROID_STANDALONE void FJavaWrapper::SetupEmbeddedCommunication(JNIEnv* Env) { #if BUILD_EMBEDDED_APP ReplyMethod = FJavaWrapper::FindMethod(Env, GGameActivityClassID, "AndroidThunkJava_OnNativeToEmbeddedReply", "(Ljava/lang/Object;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false); FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("webview")).AddLambda([](const FEmbeddedCallParamsHelper& InMessage) { UE_LOG(LogAndroid, Display, TEXT("Calling out from embedded to native for 'webview' subsystem:")); AndroidThunkCpp_OnNativeToEmbeddedReply(InMessage.Command, InMessage.Parameters, TEXT(""), TEXT("webviewnotification")); // @todo: Do we need to have results passed back? if so we will need a wrapper block that we pass in to all of the above handling things to call back // same for ios if (InMessage.OnCompleteDelegate != nullptr) { InMessage.OnCompleteDelegate(FEmbeddedCommunicationMap(), TEXT("")); } }); FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("native")).AddLambda([](const FEmbeddedCallParamsHelper& InMessage) { UE_LOG(LogAndroid, Display, TEXT("Calling out from embedded to native for 'native' subsystem:")); AndroidThunkCpp_OnNativeToEmbeddedReply(InMessage.Command, InMessage.Parameters, TEXT(""), TEXT("native")); // @todo: Do we need to have results passed back? if so we will need a wrapper block that we pass in to all of the above handling things to call back // same for ios if (InMessage.OnCompleteDelegate != nullptr) { InMessage.OnCompleteDelegate(FEmbeddedCommunicationMap(), TEXT("")); } }); #endif } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_ForwardNotification(JNIEnv* jenv, jobject thiz, jstring payload) { // } JNI_METHOD void Java_com_epicgames_unreal_NativeCalls_RouteServiceIntent(JNIEnv* jenv, jobject thiz, jstring InAction, jstring InPayload) { // call to OnSafetyNetAttestationResultDelegate on game thread if (FTaskGraphInterface::IsRunning()) { const char *nativeAction = jenv->GetStringUTFChars(InAction, 0); FString Action = FString(nativeAction); jenv->ReleaseStringUTFChars(InAction, nativeAction); const char *nativePayload = jenv->GetStringUTFChars(InPayload, 0); FString Payload = FString(nativePayload); jenv->ReleaseStringUTFChars(InPayload, nativePayload); FGraphEventRef RouteServiceIntentTask = FFunctionGraphTask::CreateAndDispatchWhenReady([Action, Payload]() { FJavaWrapper::OnRouteServiceIntentDelegate.Broadcast(Action, Payload); }, TStatId(), NULL, ENamedThreads::GameThread); } } JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeOnThermalStatusChangedListener(JNIEnv* jenv, jobject thiz, jint Status) { FAndroidStats::OnThermalStatusChanged(Status); FCoreDelegates::ETemperatureSeverity Severity; switch (Status) { case 0: case 1: Severity = FCoreDelegates::ETemperatureSeverity::Good; break; case 2: Severity = FCoreDelegates::ETemperatureSeverity::Bad; break; case 3: case 4: Severity = FCoreDelegates::ETemperatureSeverity::Serious; break; case 5: case 6: Severity = FCoreDelegates::ETemperatureSeverity::Critical; break; default: Severity = FCoreDelegates::ETemperatureSeverity::Unknown; break; } FCoreDelegates::OnTemperatureChange.Broadcast(Severity); } static void OnTrimMessage(int MemoryTrimValue) { FAndroidPlatformMemory::UpdateOSMemoryStatus(FAndroidPlatformMemory::EOSMemoryStatusCategory::OSTrim, MemoryTrimValue); FAndroidStats::OnTrimMemory(MemoryTrimValue); } #if !UE_BUILD_SHIPPING FAutoConsoleCommand TestTrimMessage( TEXT("android.TestTrimMessage"), TEXT("testing only, android.TestTrimMessage int \n") TEXT("int value must match expected values from android OS (see ComponentCallbacks2 api)\n") TEXT("eg. android.TestTrimMessage 15") , FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& Args) { if(Args.Num() > 0) { uint64 MemoryTrimValue = 0; LexFromString(MemoryTrimValue, *Args[0]); if(MemoryTrimValue) { OnTrimMessage(MemoryTrimValue); } } })); #endif JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeOnTrimMemory(JNIEnv* jenv, jobject thiz, jint MemoryTrimValue) { OnTrimMessage(MemoryTrimValue); } class FAndroidEmbeddedExec : public FSelfRegisteringExec { public: FAndroidEmbeddedExec() : FSelfRegisteringExec() { } virtual bool Exec(UWorld* Inworld, const TCHAR* Cmd, FOutputDevice& Ar) override { if (FParse::Command(&Cmd, TEXT("Android"))) { // commands to override and append commandline options for next boot (see FIOSCommandLineHelper) if (FParse::Command(&Cmd, TEXT("HideWeb"))) { AndroidThunkCpp_OnNativeToEmbeddedReply(TEXT("HideWeb"), {}, TEXT(""), TEXT("native")); } else if (FParse::Command(&Cmd, TEXT("ShowWeb"))) { AndroidThunkCpp_OnNativeToEmbeddedReply(TEXT("ShowWeb"), {}, TEXT(""), TEXT("native")); } } return false; } } GAndroidEmbeddedExec; #endif