diff --git a/Graphics/GraphicsEngine/interface/EngineFactory.h b/Graphics/GraphicsEngine/interface/EngineFactory.h index 79903ecf35..13df321c8c 100644 --- a/Graphics/GraphicsEngine/interface/EngineFactory.h +++ b/Graphics/GraphicsEngine/interface/EngineFactory.h @@ -136,15 +136,13 @@ DILIGENT_BEGIN_INTERFACE(IEngineFactory, IObject) /// On Android platform, it is necessary to initialize the file system before /// CreateDefaultShaderSourceStreamFactory() method can be called. - /// \param [in] NativeActivity - Pointer to the native activity object (ANativeActivity). - /// \param [in] NativeActivityClassName - Native activity class name. - /// \param [in] AssetManager - Pointer to the asset manager (AAssetManager). + /// \param [in] ExternalFilesDir - External files directory. + /// \param [in] AssetManager - A pointer to the asset manager (AAssetManager). /// /// \remarks See AndroidFileSystem::Init. VIRTUAL void METHOD(InitAndroidFileSystem)(THIS_ - struct ANativeActivity* NativeActivity, - const Char* NativeActivityClassName, - struct AAssetManager* AssetManager) CONST PURE; + const char* ExternalFilesDir, + struct AAssetManager* AssetManager) CONST PURE; #endif }; DILIGENT_END_INTERFACE diff --git a/Graphics/GraphicsEngineOpenGL/src/EngineFactoryOpenGL.cpp b/Graphics/GraphicsEngineOpenGL/src/EngineFactoryOpenGL.cpp index e03d9bbde5..aa4e7ea79a 100644 --- a/Graphics/GraphicsEngineOpenGL/src/EngineFactoryOpenGL.cpp +++ b/Graphics/GraphicsEngineOpenGL/src/EngineFactoryOpenGL.cpp @@ -112,9 +112,8 @@ class EngineFactoryOpenGLImpl : public EngineFactoryBase } #if PLATFORM_ANDROID - virtual void InitAndroidFileSystem(struct ANativeActivity* NativeActivity, - const char* NativeActivityClassName, - struct AAssetManager* AssetManager) const override final; + virtual void InitAndroidFileSystem(const char* ExternalFilesDir, + struct AAssetManager* AssetManager) const override final; #endif }; @@ -377,11 +376,10 @@ void EngineFactoryOpenGLImpl::CreateHLSL2GLSLConverter(IHLSL2GLSLConverter** ppC } #if PLATFORM_ANDROID -void EngineFactoryOpenGLImpl::InitAndroidFileSystem(struct ANativeActivity* NativeActivity, - const char* NativeActivityClassName, - struct AAssetManager* AssetManager) const +void EngineFactoryOpenGLImpl::InitAndroidFileSystem(const char* ExternalFilesDir, + struct AAssetManager* AssetManager) const { - AndroidFileSystem::Init(NativeActivity, NativeActivityClassName, AssetManager); + AndroidFileSystem::Init(ExternalFilesDir, AssetManager); } #endif diff --git a/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp b/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp index 4fbe803bdc..3859fe93b2 100644 --- a/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp @@ -118,9 +118,8 @@ class EngineFactoryVkImpl final : public EngineFactoryBase } #if PLATFORM_ANDROID - virtual void InitAndroidFileSystem(struct ANativeActivity* NativeActivity, - const char* NativeActivityClassName, - struct AAssetManager* AssetManager) const override final; + virtual void InitAndroidFileSystem(const char* ExternalFilesDir, + struct AAssetManager* AssetManager) const override final; #endif private: @@ -1384,11 +1383,10 @@ void EngineFactoryVkImpl::CreateSwapChainVk(IRenderDevice* pDevice, #if PLATFORM_ANDROID -void EngineFactoryVkImpl::InitAndroidFileSystem(struct ANativeActivity* NativeActivity, - const char* NativeActivityClassName, - struct AAssetManager* AssetManager) const +void EngineFactoryVkImpl::InitAndroidFileSystem(const char* ExternalFilesDir, + struct AAssetManager* AssetManager) const { - AndroidFileSystem::Init(NativeActivity, NativeActivityClassName, AssetManager); + AndroidFileSystem::Init(ExternalFilesDir, AssetManager); } #endif diff --git a/Platforms/Android/CMakeLists.txt b/Platforms/Android/CMakeLists.txt index 9cf22ffaeb..0fd029deca 100644 --- a/Platforms/Android/CMakeLists.txt +++ b/Platforms/Android/CMakeLists.txt @@ -8,6 +8,7 @@ set(INTERFACE interface/AndroidPlatformDefinitions.h interface/AndroidPlatformMisc.hpp interface/AndroidNativeWindow.h + Interface/JNIMiniHelper.hpp ../Linux/interface/LinuxPlatformMisc.hpp ) diff --git a/Platforms/Android/interface/AndroidFileSystem.hpp b/Platforms/Android/interface/AndroidFileSystem.hpp index 4b02a64f59..cb16362793 100644 --- a/Platforms/Android/interface/AndroidFileSystem.hpp +++ b/Platforms/Android/interface/AndroidFileSystem.hpp @@ -74,18 +74,15 @@ struct AndroidFileSystem : public BasicFileSystem public: /// Initializes the file system. - /// \param [in] NativeActivity - Pointer to the native activity object (ANativeActivity). - /// \param [in] NativeActivityClassName - Native activity class name. - /// \param [in] AssetManager - Pointer to the asset manager (AAssetManager). + /// \param [in] ExternalFilesDir - External files directory. + /// \param [in] AssetManager - A pointer to the asset manager (AAssetManager). /// - /// \remarks The file system can be initialized to use either native activity or asset manager, or both. - /// When NativeActivity is not null, the file system will try to use it first when opening files. - /// It will then resort to using the asset manager. When NativeActivity is not null, but AssetManager - /// parameter is null, the file system will use the asset manager from the activity. - /// If NativeActivity is null, the file system will only use the asset manager. - static void Init(struct ANativeActivity* NativeActivity, - const char* NativeActivityClassName, - struct AAssetManager* AssetManager); + /// \remarks The file system can be initialized to use either the external assets path or asset manager, or both. + /// When ExternalFilesDir is not null, the file system will try to use it first when opening files. + /// It will then resort to using the asset manager. + /// If ExternalFilesDir is null, the file system will only use the asset manager. + static void Init(const char* ExternalFilesDir, + struct AAssetManager* AssetManager); static AndroidFile* OpenFile(const FileOpenAttribs& OpenAttribs); diff --git a/Platforms/Android/interface/JNIMiniHelper.hpp b/Platforms/Android/interface/JNIMiniHelper.hpp new file mode 100644 index 0000000000..48c8cc1a48 --- /dev/null +++ b/Platforms/Android/interface/JNIMiniHelper.hpp @@ -0,0 +1,167 @@ +/* + * Copyright 2023 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#include +#include +#include + +namespace Diligent +{ + +class JNIMiniHelper +{ +public: + void Init(ANativeActivity* activity, std::string activity_class_name) + { + VERIFY(activity != nullptr && !activity_class_name.empty(), "Activity and class name can't be null"); + activity_ = activity; + activity_class_name_ = std::move(activity_class_name); + } + + static JNIMiniHelper& GetInstance() + { + static JNIMiniHelper helper; + return helper; + } + + static std::string GetExternalFilesDir(ANativeActivity* activity, std::string activity_class_name) + { + JNIMiniHelper Helper; + Helper.Init(activity, activity_class_name); + return Helper.GetExternalFilesDir(); + } + + std::string GetExternalFilesDir() + { + if (activity_ == nullptr) + { + LOG_ERROR_MESSAGE("JNIMiniHelper has not been initialized. Call init() to initialize the helper"); + return ""; + } + + std::string ExternalFilesPath; + { + std::lock_guard guard{mutex_}; + + JNIEnv* env = nullptr; + bool DetachThread = AttachCurrentThread(env); + if (jstring jstr_path = GetExternalFilesDirJString(env)) + { + const char* path = env->GetStringUTFChars(jstr_path, nullptr); + ExternalFilesPath = std::string(path); + env->ReleaseStringUTFChars(jstr_path, path); + env->DeleteLocalRef(jstr_path); + } + if (DetachThread) + DetachCurrentThread(); + } + + return ExternalFilesPath; + } + + /* + * Attach current thread + * In Android, the thread doesn't have to be 'Detach' current thread + * as application process is only killed and VM does not shut down + */ + bool AttachCurrentThread(JNIEnv*& env) + { + env = nullptr; + if (activity_->vm->GetEnv((void**)&env, JNI_VERSION_1_4) == JNI_OK) + return false; // Already attached + activity_->vm->AttachCurrentThread(&env, nullptr); + pthread_key_create((int32_t*)activity_, DetachCurrentThreadDtor); + return true; + } + + /* + * Unregister this thread from the VM + */ + static void DetachCurrentThreadDtor(void* p) + { + LOG_INFO_MESSAGE("detached current thread"); + auto* activity = reinterpret_cast(p); + activity->vm->DetachCurrentThread(); + } + +private: + JNIMiniHelper() + { + } + + ~JNIMiniHelper() + { + } + + // clang-format off + JNIMiniHelper (const JNIMiniHelper&) = delete; + JNIMiniHelper& operator=(const JNIMiniHelper&) = delete; + JNIMiniHelper (JNIMiniHelper&&) = delete; + JNIMiniHelper& operator=(JNIMiniHelper&&) = delete; + // clang-format on + + jstring GetExternalFilesDirJString(JNIEnv* env) + { + if (activity_ == nullptr) + { + LOG_ERROR_MESSAGE("JNIHelper has not been initialized. Call init() to initialize the helper"); + return NULL; + } + + jstring obj_Path = nullptr; + // Invoking getExternalFilesDir() java API + jclass cls_Env = env->FindClass(activity_class_name_.c_str()); + jmethodID mid = env->GetMethodID(cls_Env, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); + jobject obj_File = env->CallObjectMethod(activity_->clazz, mid, NULL); + if (obj_File) + { + jclass cls_File = env->FindClass("java/io/File"); + jmethodID mid_getPath = env->GetMethodID(cls_File, "getPath", "()Ljava/lang/String;"); + obj_Path = (jstring)env->CallObjectMethod(obj_File, mid_getPath); + env->DeleteLocalRef(cls_File); + env->DeleteLocalRef(obj_File); + } + env->DeleteLocalRef(cls_Env); + return obj_Path; + } + + void DetachCurrentThread() + { + activity_->vm->DetachCurrentThread(); + } + + ANativeActivity* activity_ = nullptr; + std::string activity_class_name_; + + // mutex for synchronization + // This class uses singleton pattern and can be invoked from multiple threads, + // each methods locks the mutex for a thread safety + mutable std::mutex mutex_; +}; + +} // namespace Diligent diff --git a/Platforms/Android/src/AndroidFileSystem.cpp b/Platforms/Android/src/AndroidFileSystem.cpp index eef15c704e..c94bc1bffc 100644 --- a/Platforms/Android/src/AndroidFileSystem.cpp +++ b/Platforms/Android/src/AndroidFileSystem.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2023 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,74 +25,55 @@ * of the possibility of such damages. */ -#include -#include - #include "AndroidFileSystem.hpp" #include "Errors.hpp" #include "DebugUtilities.hpp" +namespace Diligent +{ + namespace { -class JNIMiniHelper +struct AndroidFileSystemHelper { -public: - static void Init(ANativeActivity* activity, std::string activity_class_name, AAssetManager* asset_manager) + static AndroidFileSystemHelper& GetInstance() { - VERIFY(activity != nullptr || asset_manager != nullptr, "Activity and asset manager can't both be null"); - - auto& TheHelper = GetInstance(); - TheHelper.activity_ = activity; - TheHelper.activity_class_name_ = std::move(activity_class_name); - TheHelper.asset_manager_ = asset_manager; - if (TheHelper.asset_manager_ == nullptr && TheHelper.activity_ != nullptr) - { - TheHelper.asset_manager_ = TheHelper.activity_->assetManager; - } + static AndroidFileSystemHelper Instance; + return Instance; } - static JNIMiniHelper& GetInstance() + void Init(const char* ExternalFilesDir, AAssetManager* AssetManager) { - static JNIMiniHelper helper; - return helper; + m_ExternalFilesDir = ExternalFilesDir != nullptr ? ExternalFilesDir : ""; + m_AssetManager = AssetManager; } - bool OpenFile(const char* fileName, std::ifstream& IFS, AAsset*& AssetFile, size_t& FileSize) { - if (activity_ == nullptr && asset_manager_ == nullptr) + if (fileName == nullptr || fileName[0] == '\0') { - LOG_ERROR_MESSAGE("JNIMiniHelper has not been initialized. Call init() to initialize the helper"); return false; } - // Lock mutex - std::lock_guard lock(mutex_); - - if (activity_ != nullptr) + const auto IsAbsolutePath = AndroidFileSystem::IsPathAbsolute(fileName); + if (!IsAbsolutePath && m_ExternalFilesDir.empty() && m_AssetManager == nullptr) { - // First, try reading from externalFileDir; - std::string ExternalFilesPath; - { - JNIEnv* env = nullptr; - bool DetachThread = AttachCurrentThread(env); - if (jstring jstr_path = GetExternalFilesDirJString(env)) - { - const char* path = env->GetStringUTFChars(jstr_path, nullptr); - ExternalFilesPath = std::string(path); - if (fileName[0] != '/') - { - ExternalFilesPath.append("/"); - } - ExternalFilesPath.append(fileName); - env->ReleaseStringUTFChars(jstr_path, path); - env->DeleteLocalRef(jstr_path); - } - if (DetachThread) - DetachCurrentThread(); - } + LOG_ERROR_MESSAGE("File system has not been initialized. Call AndroidFileSystem::Init()."); + return false; + } + // First, try reading from the external directory + if (IsAbsolutePath) + { + IFS.open(fileName, std::ios::binary); + } + else if (!m_ExternalFilesDir.empty()) + { + auto ExternalFilesPath = m_ExternalFilesDir; + if (ExternalFilesPath.back() != '/') + ExternalFilesPath.append("/"); + ExternalFilesPath.append(fileName); IFS.open(ExternalFilesPath.c_str(), std::ios::binary); } @@ -103,10 +84,10 @@ class JNIMiniHelper IFS.seekg(0, std::ifstream::beg); return true; } - else if (asset_manager_ != nullptr) + else if (!IsAbsolutePath && m_AssetManager != nullptr) { // Fallback to assetManager - AssetFile = AAssetManager_open(asset_manager_, fileName, AASSET_MODE_BUFFER); + AssetFile = AAssetManager_open(m_AssetManager, fileName, AASSET_MODE_BUFFER); if (!AssetFile) { return false; @@ -128,96 +109,19 @@ class JNIMiniHelper } } - /* - * Attach current thread - * In Android, the thread doesn't have to be 'Detach' current thread - * as application process is only killed and VM does not shut down - */ - bool AttachCurrentThread(JNIEnv*& env) - { - env = nullptr; - if (activity_->vm->GetEnv((void**)&env, JNI_VERSION_1_4) == JNI_OK) - return false; // Already attached - activity_->vm->AttachCurrentThread(&env, nullptr); - pthread_key_create((int32_t*)activity_, DetachCurrentThreadDtor); - return true; - } - - /* - * Unregister this thread from the VM - */ - static void DetachCurrentThreadDtor(void* p) - { - LOG_INFO_MESSAGE("detached current thread"); - auto* activity = reinterpret_cast(p); - activity->vm->DetachCurrentThread(); - } - private: - JNIMiniHelper() - { - } - - ~JNIMiniHelper() - { - } - - // clang-format off - JNIMiniHelper (const JNIMiniHelper&) = delete; - JNIMiniHelper& operator=(const JNIMiniHelper&) = delete; - JNIMiniHelper (JNIMiniHelper&&) = delete; - JNIMiniHelper& operator=(JNIMiniHelper&&) = delete; - // clang-format on + AndroidFileSystemHelper() {} - jstring GetExternalFilesDirJString(JNIEnv* env) - { - if (activity_ == nullptr) - { - LOG_ERROR_MESSAGE("JNIHelper has not been initialized. Call init() to initialize the helper"); - return NULL; - } - - jstring obj_Path = nullptr; - // Invoking getExternalFilesDir() java API - jclass cls_Env = env->FindClass(activity_class_name_.c_str()); - jmethodID mid = env->GetMethodID(cls_Env, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); - jobject obj_File = env->CallObjectMethod(activity_->clazz, mid, NULL); - if (obj_File) - { - jclass cls_File = env->FindClass("java/io/File"); - jmethodID mid_getPath = env->GetMethodID(cls_File, "getPath", "()Ljava/lang/String;"); - obj_Path = (jstring)env->CallObjectMethod(obj_File, mid_getPath); - env->DeleteLocalRef(cls_File); - env->DeleteLocalRef(obj_File); - } - env->DeleteLocalRef(cls_Env); - return obj_Path; - } - - void DetachCurrentThread() - { - activity_->vm->DetachCurrentThread(); - } - - ANativeActivity* activity_ = nullptr; - std::string activity_class_name_; - AAssetManager* asset_manager_ = nullptr; - - // mutex for synchronization - // This class uses singleton pattern and can be invoked from multiple threads, - // each methods locks the mutex for a thread safety - mutable std::mutex mutex_; +private: + std::string m_ExternalFilesDir; + AAssetManager* m_AssetManager = nullptr; }; - } // namespace -namespace Diligent -{ - bool AndroidFile::Open(const char* FileName, std::ifstream& IFS, AAsset*& AssetFile, size_t& Size) { - return JNIMiniHelper::GetInstance().OpenFile(FileName, IFS, AssetFile, Size); + return AndroidFileSystemHelper::GetInstance().OpenFile(FileName, IFS, AssetFile, Size); } AndroidFile::AndroidFile(const FileOpenAttribs& OpenAttribs) : @@ -293,9 +197,10 @@ bool AndroidFile::SetPos(size_t Offset, FilePosOrigin Origin) } -void AndroidFileSystem::Init(ANativeActivity* NativeActivity, const char* NativeActivityClassName, AAssetManager* AssetManager) +void AndroidFileSystem::Init(const char* ExternalFilesPath, + struct AAssetManager* AssetManager) { - JNIMiniHelper::Init(NativeActivity, NativeActivityClassName != nullptr ? NativeActivityClassName : "", AssetManager); + AndroidFileSystemHelper::GetInstance().Init(ExternalFilesPath, AssetManager); } AndroidFile* AndroidFileSystem::OpenFile(const FileOpenAttribs& OpenAttribs)