[development] properly pal-ify Android utilities in AzCore (#7147)

Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com
This commit is contained in:
Scott Romero
2022-01-25 14:57:51 -08:00
committed by GitHub
parent f91c605144
commit 56e7e70735
22 changed files with 19 additions and 19 deletions
@@ -0,0 +1,439 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <errno.h> // for EACCES
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/IO/Path/Path.h>
//Note: Switching on verbose logging will give you a lot of detailed information about what files are being read from the APK
// but there is a likelihood it could cause logcat to terminate with a 'buffer full' error. Restarting logcat will resume logging
// but you may lose information
#define VERBOSE_IO_LOGGING 0
#if VERBOSE_IO_LOGGING
#define FILE_IO_LOG(...) AZ_Printf("LMBR", __VA_ARGS__)
#else
#define FILE_IO_LOG(...)
#endif
namespace AZ
{
namespace Android
{
AZ::EnvironmentVariable<APKFileHandler> APKFileHandler::s_instance;
bool APKFileHandler::Create()
{
if (!s_instance)
{
s_instance = AZ::Environment::CreateVariable<APKFileHandler>(AZ::AzTypeInfo<APKFileHandler>::Name());
}
if (s_instance->IsReady()) // already created in a different module
{
return true;
}
return s_instance->Initialize();
}
void APKFileHandler::Destroy()
{
s_instance.Reset();
}
bool APKFileHandler::ShouldLoadFileToMemory(const char* filePath)
{
if (!filePath)
{
return false;
}
for (const AZStd::string& fileName : m_memFileNames)
{
if (strstr(filePath, fileName.c_str()))
{
return true;
}
}
return false;
}
MemoryBuffer* APKFileHandler::GetInMemoryFileBuffer(void* asset)
{
for (auto it = m_memFileBuffers.begin(); it != m_memFileBuffers.end(); it++)
{
if (it->m_asset == asset)
{
return &(*it);
}
}
return nullptr;
}
void APKFileHandler::RemoveInMemoryFileBuffer(void* asset)
{
for (auto it = m_memFileBuffers.begin(); it != m_memFileBuffers.end(); it++)
{
if (it->m_asset == asset)
{
m_memFileBuffers.erase(it);
break;
}
}
}
FILE* APKFileHandler::Open(const char* filename, const char* mode, AZ::u64& size)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK Open");
FILE* fileHandle = nullptr;
if (mode[0] != 'w')
{
FILE_IO_LOG("******* Attempting to open file in APK:[%s] ", filename);
AAsset* asset = nullptr;
bool loadFileToMemory = Get().ShouldLoadFileToMemory(filename);
int assetMode = loadFileToMemory ? AASSET_MODE_BUFFER : AASSET_MODE_UNKNOWN;
asset = AAssetManager_open(Utils::GetAssetManager(), Utils::StripApkPrefix(filename).c_str(), assetMode);
if (asset != nullptr)
{
// the pointer returned by funopen will allow us to use fread, fseek etc
fileHandle = funopen(asset, APKFileHandler::Read, APKFileHandler::Write, APKFileHandler::Seek, APKFileHandler::Close);
if (loadFileToMemory)
{
MemoryBuffer buf;
buf.m_buffer = (char*)AAsset_getBuffer(asset);
buf.m_totalSize = AAsset_getLength(asset);
buf.m_asset = asset;
if (buf.m_buffer)
{
Get().m_memFileBuffers.push_back(buf);
}
else
{
AZ_Assert(false, "Failed to load %s to memory", filename)
}
}
// the file pointer we return from funopen can't be used to get the length of the file so we need to capture that info while we have the AAsset pointer available
size = static_cast<AZ::u64>(AAsset_getLength64(asset));
FILE_IO_LOG("File loaded successfully");
}
else
{
FILE_IO_LOG("####### Failed to open file in APK:[%s] ", filename);
}
}
return fileHandle;
}
int APKFileHandler::Read(void* asset, char* buffer, int size)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK Read");
APKFileHandler& apkHandler = Get();
if (apkHandler.m_numBytesToRead < size && apkHandler.m_numBytesToRead > 0)
{
size = apkHandler.m_numBytesToRead;
}
apkHandler.m_numBytesToRead -= size;
MemoryBuffer* buf = apkHandler.GetInMemoryFileBuffer(asset);
if (buf)
{
const char* tempBuf = buf->m_buffer + buf->m_offset;
memcpy(buffer, tempBuf, size);
return size;
}
return AAsset_read(static_cast<AAsset*>(asset), buffer, static_cast<size_t>(size));
}
int APKFileHandler::Write(void* asset, const char* buffer, int size)
{
return EACCES;
}
fpos_t APKFileHandler::Seek(void* asset, fpos_t offset, int origin)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK Seek");
MemoryBuffer* buf = Get().GetInMemoryFileBuffer(asset);
if (buf)
{
if (origin == SEEK_SET)
{
buf->m_offset = offset;
}
else if (origin == SEEK_CUR)
{
buf->m_offset += offset;
}
else if (origin == SEEK_END)
{
buf->m_offset = buf->m_totalSize - offset;
}
if (buf->m_offset > buf->m_totalSize)
{
buf->m_offset = buf->m_totalSize;
}
if (buf->m_offset < 0)
{
buf->m_offset = 0;
}
return buf->m_offset;
}
return AAsset_seek(static_cast<AAsset*>(asset), offset, origin);
}
int APKFileHandler::Close(void* asset)
{
Get().RemoveInMemoryFileBuffer(asset);
AAsset_close(static_cast<AAsset*>(asset));
return 0;
}
int APKFileHandler::FileLength(const char* filename)
{
AZ::u64 size = 0;
FILE* asset = Open(filename, "r", size);
if (asset != nullptr)
{
fclose(asset);
}
return static_cast<int>(size);
}
AZ::IO::Result APKFileHandler::ParseDirectory(const char* path, FindDirsCallbackType findCallback)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK ParseDirectory");
FILE_IO_LOG("********* About to search for file in [%s] ******* ", path);
APKFileHandler& apkHandler = Get();
DirectoryCache::const_iterator it = apkHandler.m_cachedDirectories.find(path);
if (it == apkHandler.m_cachedDirectories.end())
{
// The NDK version of the Asset Manager only returns files and not directories so we must use the Java version to get all the data we need
JNIEnv* jniEnv = JNI::GetEnv();
if (!jniEnv)
{
return AZ::IO::ResultCode::Error;
}
auto newDirectory = apkHandler.m_cachedDirectories.emplace(path, StringVector());
jstring dirPath = jniEnv->NewStringUTF(path);
jobjectArray javaFileListObject = apkHandler.m_javaInstance->InvokeStaticObjectMethod<jobjectArray>("GetFilesAndDirectoriesInPath", dirPath);
jniEnv->DeleteLocalRef(dirPath);
int numObjects = jniEnv->GetArrayLength(javaFileListObject);
bool parseResults = true;
for (int i = 0; i < numObjects; i++)
{
if (!parseResults)
{
break;
}
jstring str = static_cast<jstring>(jniEnv->GetObjectArrayElement(javaFileListObject, i));
const char* entryName = jniEnv->GetStringUTFChars(str, 0);
newDirectory.first->second.push_back(StringType(entryName));
parseResults = findCallback(entryName);
jniEnv->ReleaseStringUTFChars(str, entryName);
jniEnv->DeleteLocalRef(str);
}
jniEnv->DeleteGlobalRef(javaFileListObject);
}
else
{
bool parseResults = true;
for (int i = 0; i < it->second.size(); i++)
{
if (!parseResults)
{
break;
}
parseResults = findCallback(it->second[i].c_str());
}
}
return AZ::IO::ResultCode::Success;
}
bool APKFileHandler::IsDirectory(const char* path)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK IsDir");
APKFileHandler& apkHandler = Get();
DirectoryCache::const_iterator it = apkHandler.m_cachedDirectories.find(path);
if (it == apkHandler.m_cachedDirectories.end())
{
JNIEnv* jniEnv = JNI::GetEnv();
if (!jniEnv)
{
return false;
}
jstring dirPath = jniEnv->NewStringUTF(path);
jboolean isDir = apkHandler.m_javaInstance->InvokeStaticBooleanMethod("IsDirectory", dirPath);
jniEnv->DeleteLocalRef(dirPath);
FILE_IO_LOG("########### [%s] %s a directory ######### ", path, retVal ? "IS" : "IS NOT");
return (isDir == JNI_TRUE);
}
else
{
return (it->second.size() > 0);
}
}
bool APKFileHandler::DirectoryOrFileExists(const char* path)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK DirOrFileExists");
AZ::IO::FixedMaxPath insideApkPath(Utils::StripApkPrefix(path));
// Check for the case where the input path is equal to the APK Assets Prefix of /APK
// In that case the directory is the "root" of APK assets in which case the directory exist
if (insideApkPath.empty() && Utils::IsApkPath(path))
{
return true;
}
AZ::IO::FixedMaxPathString filename{ insideApkPath.Filename().Native() };
AZ::IO::FixedMaxPathString pathToFile{ insideApkPath.ParentPath().Native() };
bool foundFile = false;
ParseDirectory(pathToFile.c_str(), [&](const char* name)
{
if (strcasecmp(name, filename.c_str()) == 0)
{
foundFile = true;
}
return true;
});
FILE_IO_LOG("########### Directory or file [%s] %s exist ######### ", filename.c_str(), foundFile ? "DOES" : "DOES NOT");
return foundFile;
}
void APKFileHandler::SetNumBytesToRead(const size_t numBytesToRead)
{
// WARNING: This isn't a thread safe way of handling this problem, LY-65478 will fix it
APKFileHandler& apkHandler = Get();
apkHandler.m_numBytesToRead = numBytesToRead;
}
void APKFileHandler::SetLoadFilesToMemory(const char* fileNames)
{
AZStd::string names(fileNames);
size_t pos = 0;
bool stringProcessed = false;
APKFileHandler& apkHandler = Get();
while (!stringProcessed)
{
size_t newPos = names.find_first_of(',', pos);
size_t len = 0;
if (newPos == AZStd::string::npos)
{
len = newPos;
stringProcessed = true;
}
else
{
len = newPos - pos;
}
AZStd::string fileName = names.substr(pos, len);
pos = newPos + 1;
apkHandler.m_memFileNames.push_back(fileName);
}
}
APKFileHandler::APKFileHandler()
: m_javaInstance()
, m_cachedDirectories()
, m_numBytesToRead(0)
{
}
APKFileHandler::~APKFileHandler()
{
m_memFileBuffers.set_capacity(0);
m_memFileNames.set_capacity(0);
if (s_instance)
{
AZ_Assert(s_instance.IsOwner(), "The Android APK file handler instance is being destroyed by someone other than the owner.");
}
}
APKFileHandler& APKFileHandler::Get()
{
if (!s_instance)
{
s_instance = AZ::Environment::FindVariable<APKFileHandler>(AZ::AzTypeInfo<APKFileHandler>::Name());
AZ_Assert(s_instance, "The Android APK file handler is NOT ready for use! Call Create first!");
}
return *s_instance;
}
bool APKFileHandler::Initialize()
{
JniObject* apkHandler = aznew JniObject("com/amazon/lumberyard/io/APKHandler", "APKHandler");
if (!apkHandler)
{
return false;
}
m_javaInstance.reset(apkHandler);
m_javaInstance->RegisterStaticMethod("IsDirectory", "(Ljava/lang/String;)Z");
m_javaInstance->RegisterStaticMethod("GetFilesAndDirectoriesInPath", "(Ljava/lang/String;)[Ljava/lang/String;");
#if VERBOSE_IO_LOGGING
m_javaInstance->RegisterStaticField("s_debug", "Z");
m_javaInstance->SetStaticBooleanField("s_debug", JNI_TRUE);
#endif
return true;
}
bool APKFileHandler::IsReady() const
{
return (m_javaInstance != nullptr);
}
} // namespace Android
} // namespace AZ
@@ -0,0 +1,166 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Object_fwd.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/osstring.h>
//NOTE: When running with the RAD Telemetry Gem enabled, a lot of the file IO methods will be captured for analysis.
// However developers who want to profile performance of their game when using APK's containing assets can enable the flag
// below to instrument their game in even more detail
#define AZ_ENABLED_VERBOSE_ANDROID_IO_PROFILING 0
#if AZ_ENABLED_VERBOSE_ANDROID_IO_PROFILING
#include <AzCore/Debug/Profiler.h>
#define ANDROID_IO_PROFILE_SECTION AZ_PROFILE_FUNCTION(AzCore)
#define ANDROID_IO_PROFILE_SECTION_ARGS(...) AZ_PROFILE_SCOPE(AzCore, __VA_ARGS__)
#else
#define ANDROID_IO_PROFILE_SECTION
#define ANDROID_IO_PROFILE_SECTION_ARGS(...)
#endif
namespace AZ
{
namespace Android
{
struct MemoryBuffer
{
const char* m_buffer;
AAsset* m_asset;
int m_totalSize;
int m_offset;
MemoryBuffer()
{
m_offset = 0;
m_totalSize = 0;
m_buffer = nullptr;
m_asset = nullptr;
}
};
class APKFileHandler
{
public:
AZ_TYPE_INFO(APKFileHandler, "{D16233A2-A183-40FE-8CF4-ABE8D53AB5B5}")
AZ_CLASS_ALLOCATOR(APKFileHandler, AZ::OSAllocator, 0);
typedef AZStd::function<bool(const char*)> FindDirsCallbackType;
//! The preferred entry point for the construction of the global APKFileHandler instance
static bool Create();
//! Public accessor to destroy the APKFileHandler global instance
static void Destroy();
//! Opens a file using the native assets manager and maps standard c file i/o
//! \param filename The full path to the file
//! \param mode Access mode of the file to be opened, will ignore write operations
//! \param size[out] Returns the size of the file in bytes
//! \return A standard c file handle
static FILE* Open(const char* filename, const char* mode, AZ::u64& size);
//! Reads \p size bytes of a given open file. Is mapped to fread when a file is opened
//! \param asset Raw pointer to the AAsset
//! \param buffer Data blob to read into
//! \param size Number of bytes to read. When called from fread redirect, value could be ignored in favor of
//! using the internal cached version to ensure we are reading only the necessary number of bytes,
//! otherwise we would be reading more than necessary as the redirected seems to only pass in 1024.
//! \return The number of bytes read, zero on EOF, or < 0 on error.
static int Read(void* asset, char* buffer, int size);
//! Writing to files inside an APK is unsupported. Is mapped to fwrite when a file is opened in order to correctly
//! return an access error.
//! \return EACCES
static int Write(void* asset, const char* buffer, int size);
//! Same as, and is mapped to fseek when a file is opened.
static fpos_t Seek(void* asset, fpos_t offset, int origin);
//! Closes the file handle and frees it's allocated resources. Is mapped to fclose when a file is opened.
//! \return 0
static int Close(void* asset);
//! Get the size, in bytes, of a file
//! \param filename The full path to the file
//! \return The size of the file, in bytes. Returns
static int FileLength(const char* filename);
//! Uses JNI to cache the contents of a given directory at \p path while providing each entry to \p findCallback
//! \param path The full path to the desired directory
//! \param findCallback Callback used to find a specific file within a given directory
//! \return If the directory was already cached \ref AZ::IO::ResultCode::Success if the directory was already cached or
static AZ::IO::Result ParseDirectory(const char* path, FindDirsCallbackType findCallback);
//! Check to see if a given path is a directory or not
static bool IsDirectory(const char* path);
//! Checks to see if a path (file or directory) exists
static bool DirectoryOrFileExists(const char* path);
//! Set the correct number of bytes to be read when calls to fread are redirected to \ref APKFileHandler::Read
static void SetNumBytesToRead(const size_t numBytesToRead);
//! Set the names of the files that should be loaded to memory
static void SetLoadFilesToMemory(const char* fileNames);
APKFileHandler();
~APKFileHandler();
private:
MemoryBuffer* GetInMemoryFileBuffer(void* asset);
void RemoveInMemoryFileBuffer(void* asset);
bool ShouldLoadFileToMemory(const char* filePath);
typedef JNI::Internal::Object<AZ::OSAllocator> JniObject;
typedef AZ::OSStdAllocator StdAllocatorType;
typedef AZ::OSString StringType;
typedef AZStd::vector<StringType, StdAllocatorType> StringVector;
typedef AZStd::unordered_map<StringType, StringVector, AZStd::hash<StringType>, AZStd::equal_to<StringType>, StdAllocatorType> DirectoryCache;
//! Internal accessor to the global APKFileHandler instance
static APKFileHandler& Get();
AZ_DISABLE_COPY_MOVE(APKFileHandler);
bool Initialize();
bool IsReady() const;
static AZ::EnvironmentVariable<APKFileHandler> s_instance; //!< Reference to the global APK file handler object, created in the AndroidEnv
AZStd::vector<MemoryBuffer> m_memFileBuffers;
AZStd::vector<AZStd::string> m_memFileNames;
AZStd::unique_ptr<JniObject> m_javaInstance; //!< JNI instance of the com.amazon.lumberyard.io.APKHandler Java object
DirectoryCache m_cachedDirectories; //!< Cache of directories and their respective files already found through previous JNI calls
size_t m_numBytesToRead; //!< Temp cache of the correct number of bytes to read when fread is called on an asset
};
} // namespace Android
} // namespace AZ
@@ -0,0 +1,416 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/JNI/Object.h>
#include <android/configuration.h>
namespace AZ
{
namespace Android
{
static const char* s_loadClassMethodName = "loadClass";
pthread_key_t AndroidEnv::s_jniEnvKey;
AZ::EnvironmentVariable<AndroidEnv*> AndroidEnv::s_instance;
// ----
// AndroidEnv (public)
// ----
////////////////////////////////////////////////////////////////
// static
AndroidEnv* AndroidEnv::Get()
{
if (!s_instance)
{
s_instance = AZ::Environment::FindVariable<AndroidEnv*>(AZ::AzTypeInfo<AndroidEnv>::Name());
AZ_Assert(s_instance, "The Android environment is NOT ready for use! Call Create first!");
}
return *s_instance;
}
////////////////////////////////////////////////////////////////
// static
bool AndroidEnv::Create(const Descriptor& descriptor)
{
if (!s_instance)
{
s_instance = AZ::Environment::CreateVariable<AndroidEnv*>(AZ::AzTypeInfo<AndroidEnv>::Name());
(*s_instance) = aznew AndroidEnv();
}
if ((*s_instance)->IsReady()) // already created in a different module
{
return true;
}
return (*s_instance)->Initialize(descriptor);
}
////////////////////////////////////////////////////////////////
// static
void AndroidEnv::Destroy()
{
if (s_instance)
{
if (s_instance.IsOwner())
{
(*s_instance)->Cleanup();
delete (*s_instance);
}
s_instance.Reset();
}
else
{
AZ_Assert(false, "The Android environment is NOT ready for use! Call Create first!");
}
}
// ----
////////////////////////////////////////////////////////////////
JNIEnv* AndroidEnv::GetJniEnv() const
{
JNIEnv* jniEnv = static_cast<JNIEnv*>(pthread_getspecific(s_jniEnvKey));
if (!jniEnv)
{
jint status = m_jvm->GetEnv((void **) &jniEnv, JNI_VERSION_1_6);
if (status == JNI_EDETACHED)
{
AZ_TracePrintf("AndroidEnv", "JNI Env not attached to the VM");
if (m_jvm->AttachCurrentThread(&jniEnv, NULL) != JNI_OK)
{
AZ_Assert(false, "Failed to attach tread to the JVM");
return nullptr;
}
}
pthread_setspecific(s_jniEnvKey, jniEnv);
}
return jniEnv;
}
////////////////////////////////////////////////////////////////
const char* AndroidEnv::GetObbFileName(bool mainFile) const
{
return (mainFile ? m_mainObbFileName.c_str() : m_patchObbFileName.c_str());
}
////////////////////////////////////////////////////////////////
void AndroidEnv::UpdateConfiguration()
{
if (m_ownsConfiguration)
{
AConfiguration_fromAssetManager(m_configuration, m_assetManager);
}
}
////////////////////////////////////////////////////////////////
jclass AndroidEnv::LoadClass(const char *classPath)
{
JNIEnv* jniEnv = GetJniEnv();
if (!jniEnv)
{
return nullptr;
}
jstring classString = jniEnv->NewStringUTF(classPath);
if (!classString || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to convert cstring %s to jstring", classPath);
jniEnv->ExceptionDescribe();
return nullptr;
}
jclass returnClass = m_classLoader->InvokeObjectMethod<jclass>(s_loadClassMethodName, classString);
jniEnv->DeleteLocalRef(classString);
return returnClass;
}
// ----
// AndroidEnv (private)
// ----
////////////////////////////////////////////////////////////////
// static
void AndroidEnv::DestroyJniEnv(void *threadData)
{
JNIEnv* jniEnv = static_cast<JNIEnv*>(threadData);
if (jniEnv)
{
JavaVM *javaVm = nullptr;
jniEnv->GetJavaVM(&javaVm);
javaVm->DetachCurrentThread();
pthread_setspecific(s_jniEnvKey, nullptr);
}
}
// ----
////////////////////////////////////////////////////////////////
AndroidEnv::AndroidEnv()
: m_jvm(nullptr)
, m_activityRef(nullptr)
, m_activityClass(nullptr)
, m_classLoader()
, m_getClassNameMethod(nullptr)
, m_getSimpleClassNameMethod(nullptr)
, m_assetManager(nullptr)
, m_configuration(nullptr)
, m_window(nullptr)
, m_appPrivateStoragePath()
, m_appPublicStoragePath()
, m_obbStoragePath()
, m_mainObbFileName()
, m_patchObbFileName()
, m_packageName()
, m_appVersionCode(0)
, m_ownsActivityRef(false)
, m_ownsConfiguration(false)
, m_isReady(false)
, m_isRunning(false)
{
}
////////////////////////////////////////////////////////////////
AndroidEnv::~AndroidEnv()
{
if (s_instance)
{
AZ_Assert(s_instance.IsOwner(), "The Android Environment instance is being destroyed by someone other than the owner.");
}
}
////////////////////////////////////////////////////////////////
bool AndroidEnv::Initialize(const Descriptor& descriptor)
{
m_jvm = descriptor.m_jvm;
m_assetManager = descriptor.m_assetManager;
m_configuration = descriptor.m_configuration;
m_appPrivateStoragePath = descriptor.m_appPrivateStoragePath;
m_appPublicStoragePath = descriptor.m_appPublicStoragePath;
m_obbStoragePath = descriptor.m_obbStoragePath;
if (!m_configuration)
{
m_configuration = AConfiguration_new();
AConfiguration_fromAssetManager(m_configuration, m_assetManager);
m_ownsConfiguration = true;
}
int result = pthread_key_create(&s_jniEnvKey, DestroyJniEnv);
if (result)
{
AZ_Assert(false, "Something went wrong calling pthread_key_create... Error code: %d", result);
return false;
}
JNIEnv* jniEnv = GetJniEnv();
if (!jniEnv)
{
AZ_Error("AndroidEnv", false, "Failed to get JNIEnv* on thread to initialize the AndroidEnv instance");
return false;
}
if (!LoadClassNameMethods(jniEnv))
{
return false;
}
jobjectRefType refType = jniEnv->GetObjectRefType(descriptor.m_activityRef);
if (refType == JNIGlobalRefType)
{
m_activityRef = descriptor.m_activityRef;
}
else if (refType == JNILocalRefType)
{
m_activityRef = static_cast<jclass>(jniEnv->NewGlobalRef(descriptor.m_activityRef));
if (!m_activityRef || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to construct a global reference to the activity instance");
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
m_ownsActivityRef = true;
}
else
{
AZ_Error("AndroidEnv", false, "Unable to use 'activityRef' argument for global ref construction");
return false;
}
jclass activityClass = jniEnv->GetObjectClass(m_activityRef);
m_activityClass = static_cast<jclass>(jniEnv->NewGlobalRef(activityClass));
if (!m_activityClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to construct a global reference to the activity class");
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(activityClass);
return false;
}
jniEnv->DeleteLocalRef(activityClass);
if (!CacheActivityData(jniEnv))
{
return false;
}
if (m_obbStoragePath.empty())
{
AZ::OSString relPath = AZ::OSString::format("/data/%s/files", m_packageName.c_str());
AZ_Assert(m_appPublicStoragePath.find(relPath) != AZ::OSString::npos,
"Public application storage path appears to be invalid. The OBB path may be incorrect and lead to unexpected results.");
AZ::OSString publicAndroidRoot = m_appPublicStoragePath.substr(0, m_appPublicStoragePath.length() - relPath.length());
m_obbStoragePath = AZ::OSString::format("%s/obb/%s", publicAndroidRoot.c_str(), m_packageName.c_str());
}
m_mainObbFileName = AZ::OSString::format("main.%d.%s.obb", m_appVersionCode, m_packageName.c_str());
m_patchObbFileName = AZ::OSString::format("patch.%d.%s.obb", m_appVersionCode, m_packageName.c_str());
AZ_TracePrintf("AndroidEnv", "Application private storage path = %s", m_appPrivateStoragePath.c_str());
AZ_TracePrintf("AndroidEnv", "Application public storage path = %s", m_appPublicStoragePath.c_str());
AZ_TracePrintf("AndroidEnv", "Application OBB path = %s", m_obbStoragePath.c_str());
AZ_TracePrintf("AndroidEnv", "Main OBB file name = %s", m_mainObbFileName.c_str());
AZ_TracePrintf("AndroidEnv", "Patch OBB file name = %s", m_patchObbFileName.c_str());
if (!APKFileHandler::Create())
{
AZ_Error("AndroidEnv", false, "Failed to construct the global APK file handler");
return false;
}
m_isReady = true;
return true;
}
////////////////////////////////////////////////////////////////
void AndroidEnv::Cleanup()
{
if (m_ownsActivityRef)
{
JNI::DeleteRef(m_activityRef);
}
JNI::DeleteRef(m_activityClass);
if (m_ownsConfiguration)
{
AConfiguration_delete(m_configuration);
}
APKFileHandler::Destroy();
}
////////////////////////////////////////////////////////////////
bool AndroidEnv::LoadClassNameMethods(JNIEnv* jniEnv)
{
const char* javaClassPath = "java/lang/Class";
const char* getNameMethodName = "getName";
const char* getSimpleNameMethodName = "getSimpleName";
const char* getNameMethodSignature = "()Ljava/lang/String;";
// since we are requesting a system class, it should be safe to use FindClass instead
// of the ClassLoader.
jclass javaClass = jniEnv->FindClass(javaClassPath);
if (!javaClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to find class %s from the JNI environment", javaClassPath);
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
m_getClassNameMethod = jniEnv->GetMethodID(javaClass, getNameMethodName, getNameMethodSignature);
if (!m_getClassNameMethod || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to find method %s with signature %s in class %s", getNameMethodName, getNameMethodSignature, javaClassPath);
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(javaClass);
return false;
}
m_getSimpleClassNameMethod = jniEnv->GetMethodID(javaClass, getSimpleNameMethodName, getNameMethodSignature);
if (!m_getSimpleClassNameMethod || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to find method %s with signature %s in class %s", getSimpleNameMethodName, getNameMethodSignature, javaClassPath);
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(javaClass);
return false;
}
jniEnv->DeleteLocalRef(javaClass);
return true;
}
////////////////////////////////////////////////////////////////
bool AndroidEnv::CacheActivityData(JNIEnv* jniEnv)
{
JniObject activityObject(m_activityClass, m_activityRef);
activityObject.RegisterMethod("GetPackageName", "()Ljava/lang/String;");
activityObject.RegisterMethod("GetAppVersionCode", "()I");
activityObject.RegisterMethod("getClassLoader", "()Ljava/lang/ClassLoader;");
m_packageName = activityObject.InvokeStringMethod("GetPackageName");
m_appVersionCode = activityObject.InvokeIntMethod("GetAppVersionCode");
// construct the global class loader object
jobject classLoaderRef = activityObject.InvokeObjectMethod<jobject>("getClassLoader");
if (!classLoaderRef)
{
AZ_Error("AndroidEnv", false, "Failed to retrieve the class loader from the activity");
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
jclass localClassLoaderClass = jniEnv->GetObjectClass(classLoaderRef);
if (!localClassLoaderClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to get jclass from ClassLoader");
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
jclass classLoaderClass = static_cast<jclass>(jniEnv->NewGlobalRef(localClassLoaderClass));
if (!classLoaderClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to create a global reference to the class loader");
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(localClassLoaderClass);
return false;
}
jniEnv->DeleteLocalRef(localClassLoaderClass);
m_classLoader.reset(aznew JniObject(classLoaderClass, classLoaderRef, true));
m_classLoader->RegisterMethod(s_loadClassMethodName, "(Ljava/lang/String;)Ljava/lang/Class;");
return true;
}
}
}
@@ -0,0 +1,218 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Module/Environment.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/osstring.h>
#include <jni.h>
#include <pthread.h>
struct AAssetManager;
struct ANativeWindow;
struct AConfiguration;
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
template<typename Allocator>
class Object;
template<typename StringType>
class ClassName;
} // namespace Internal
} // namespace JNI
class AndroidEnv
{
public:
AZ_TYPE_INFO(AndroidEnv, "{E51A8876-7A26-4CB1-BA88-394A128728C7}")
AZ_CLASS_ALLOCATOR(AndroidEnv, AZ::OSAllocator, 0);
//! Creation POD for the AndroidEnv
struct Descriptor
{
Descriptor()
: m_jvm(nullptr)
, m_activityRef(nullptr)
, m_assetManager(nullptr)
, m_configuration(nullptr)
, m_appPrivateStoragePath()
, m_appPublicStoragePath()
, m_obbStoragePath()
{
}
JavaVM* m_jvm; //!< Global pointer to the Java virtual machine
jobject m_activityRef; //!< Local or global reference to the activity instance
AAssetManager* m_assetManager; //!< Global pointer to the Android asset manager, used for APK file i/o
AConfiguration* m_configuration; //!< Global pointer to the configuration of the device, e.g. orientation, screen density, locale, etc.
AZ::OSString m_appPrivateStoragePath; //!< Access restricted location. E.G. /data/data/<package_name>/files
AZ::OSString m_appPublicStoragePath; //!< Public storage specifically for the application. E.G. <public_storage>/Android/data/<package_name>/files
AZ::OSString m_obbStoragePath; //!< Public storage specifically for the application's obb files. E.G. <public_storage>/Android/obb/<package_name>/files
};
//! Public accessor to the global AndroidEnv instance
static AndroidEnv* Get();
//! The preferred entry point for the construction of the global AndroidEnv instance
static bool Create(const Descriptor& descriptor);
//! Public accessor to destroy the AndroidEnv global instance
static void Destroy();
// ----
//! Request a thread specific JNIEnv pointer from the JVM.
//! \return A pointer to the JNIEnv on the current thread.
JNIEnv* GetJniEnv() const;
//! Request the global reference to the activity class
jclass GetActivityClassRef() const { return m_activityClass; }
//! Request the global reference to the activity instance
jobject GetActivityRef() const { return m_activityRef; }
//! Get the global pointer to the Android asset manager, which is used for APK file i/o.
AAssetManager* GetAssetManager() const { return m_assetManager; }
//! Get the global pointer to the device/application configuration,
AConfiguration* GetConfiguration() const { return m_configuration; }
//! Set the global pointer to the Android window surface.
void SetWindow(ANativeWindow* window) { m_window = window; }
//! Get the global pointer to the Android window surface
ANativeWindow* GetWindow() const { return m_window; }
//! Get the hidden internal storage, typically this is where the application is installed on the device.
//! e.g. /data/data/<package_name/files
const char* GetAppPrivateStoragePath() const { return m_appPrivateStoragePath.c_str(); }
//! Get the application specific directory for public storage.
//! e.g. <public_storage>/Android/data/<package_name/files
const char* GetAppPublicStoragePath() const { return m_appPublicStoragePath.c_str(); }
//! Get the application specific directory for obb files.
//! e.g. <public_storage>/Android/obb/<package_name/files
const char* GetObbStoragePath() const { return m_obbStoragePath.c_str(); }
//! Get the dot separated package name for the current application.
//! e.g. org.o3de.samples for SamplesProject
const char* GetPackageName() const { return m_packageName.c_str(); }
//! Get the app version code (android:versionCode in the manifest).
int GetAppVersionCode() const { return m_appVersionCode; }
//! Get the filename of the obb. This doesn't include the path to the obb folder.
const char* GetObbFileName(bool mainFile) const;
//! Check if the AndroidEnv has been initialized
bool IsReady() const { return m_isReady; }
//! Set wheather or not the application should be running
void SetIsRunning(bool isRunning) { m_isRunning = isRunning; }
//! Check if the application has been backgrounded (false) or not (true)
bool IsRunning() const { return m_isRunning; }
//! If the AndroidEnv owns the native configuration, it will be updated with the latest configuration
//! information, otherwise nothing will happen.
void UpdateConfiguration();
//! Loads a Java class as opposed to attempting to find a loaded class from the call stack.
//! \param classPath The fully qualified forward slash separated Java class path.
//! \return A global reference to the desired jclass. Caller is responsible for making a
//! call to DeleteGlobalJniRef when the jclass is no longer needed.
jclass LoadClass(const char* classPath);
private:
template<typename StringType>
friend class JNI::Internal::ClassName;
typedef JNI::Internal::Object<OSAllocator> JniObject; //!< Internal usage of \ref AZ::Android::JNI::Internal::Object that uses the OSAllocator
//! Callback for when a thread exists to detach the jni env from the thread
//! \param threadData Expected to be the JNIEnv pointer
static void DestroyJniEnv(void* threadData);
// ----
AndroidEnv();
~AndroidEnv();
AZ_DISABLE_COPY_MOVE(AndroidEnv);
//! Public global accessor to the android application environment
//! \param descriptor
bool Initialize(const Descriptor& descriptor);
//! Handle the deletion of the global jni references
void Cleanup();
//! Finds the java/lang/Class jclass to get the method IDs to getName and getSimpleName
//! \return True if successfully, False otherwise
bool LoadClassNameMethods(JNIEnv* jniEnv);
//! Calls some java methods on the activity instance and constructs the class loader
//! \return True if successfully, False otherwise
bool CacheActivityData(JNIEnv* jniEnv);
// ----
static pthread_key_t s_jniEnvKey; //!< Thread key for accessing the thread specific jni env pointers
static AZ::EnvironmentVariable<AndroidEnv*> s_instance; //!< Reference to the global object, created in the main function (AndroidLauncher)
JavaVM* m_jvm; //!< Mostly used for [de/a]ttaching JNIEnv pointers to threads
jobject m_activityRef; //!< Reference to the global instance of the current activity object, used for instance method invocation, field access
jclass m_activityClass; //!< Reference to the global instance of the current activity class, used for method / field extraction, static method invocation
AZStd::unique_ptr<JniObject> m_classLoader; //!< Class loader instance, used for finding Java classes on any thread
jmethodID m_getClassNameMethod; //!< Method ID for getName from java/lang/Class which returns a fully qualified dot separated Java class path
jmethodID m_getSimpleClassNameMethod; //!< Method ID for getSimpleName from java/lang/Class which returns just the class name from a Java class path
AAssetManager* m_assetManager; //!< Global pointer to the Android asset manager, used for APK file i/o
AConfiguration* m_configuration; //!< Global pointer to the configuration of the device, e.g. orientation, screen density, locale, etc.
ANativeWindow* m_window; //!< Global pointer to the window surface created by Android, used for creating GL contexts
AZ::OSString m_appPrivateStoragePath; //!< Access restricted location. E.G. /data/data/<package_name>/files
AZ::OSString m_appPublicStoragePath; //!< Public storage specifically for the application. E.G. <public_storage>/Android/data/<package_name>/files
AZ::OSString m_obbStoragePath; //!< Public storage specifically for the application's obb files. E.G. <public_storage>/Android/obb/<package_name>/files
AZ::OSString m_mainObbFileName; //!< File name for the main OBB
AZ::OSString m_patchObbFileName; //!< File name for the patch OBB
AZ::OSString m_packageName; //!< The dot separated package id of the application
int m_appVersionCode; //!< The version code of the app (android:versionCode in the AndroidManifest.xml)
bool m_ownsActivityRef; //!< For when a local activity ref is passed into the construction and needs to be cleaned up
bool m_ownsConfiguration; //!< For when no configuration is passed into the construction and needs to be cleaned up
bool m_isReady; //!< Set only once the object has been successfully constructed
bool m_isRunning; //!< Internal flag indicating if the application is running, mainly used to determine if we shoudl be blocking on the event pump while paused
};
} // namespace Android
} // namespace AZ
@@ -0,0 +1,85 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <android/api-level.h>
#include <AzCore/Android/Utils.h>
// the following defines provide cross compatibility between NDK and header versions as they
// were only officially added to the unified headers in NDK r14
#ifndef __ANDROID_API_K__
#define __ANDROID_API_K__ 19
#endif
#ifndef __ANDROID_API_L__
#define __ANDROID_API_L__ 21
#endif
#ifndef __ANDROID_API_L_MR1__
#define __ANDROID_API_L_MR1__ 22
#endif
#ifndef __ANDROID_API_M__
#define __ANDROID_API_M__ 23
#endif
#ifndef __ANDROID_API_N__
#define __ANDROID_API_N__ 24
#endif
#ifndef __ANDROID_API_N_MR1__
#define __ANDROID_API_N_MR1__ 25
#endif
#ifndef __ANDROID_API_O__
#define __ANDROID_API_O__ 26
#endif
#ifndef __ANDROID_API_O_MR1__
#define __ANDROID_API_O_MR1__ 27
#endif
#ifndef __ANDROID_API_P__
#define __ANDROID_API_P__ 28
#endif
#ifndef __ANDROID_API_Q__
#define __ANDROID_API_Q__ 29
#endif
namespace AZ
{
namespace Android
{
//! Supported API level codes for runtime checks
enum class ApiLevel : unsigned char
{
KitKat = __ANDROID_API_K__,
Lollipop = __ANDROID_API_L__,
Lollipop_mr1 = __ANDROID_API_L_MR1__,
Marshmallow = __ANDROID_API_M__,
Nougat = __ANDROID_API_N__,
Nougat_mr1 = __ANDROID_API_N_MR1__,
Oreo = __ANDROID_API_O__,
Oreo_mr1 = __ANDROID_API_O_MR1__,
Pie = __ANDROID_API_P__,
Ten = __ANDROID_API_Q__,
};
//! Request the OS runtime API level of the device
AZ_INLINE ApiLevel GetRuntimeApiLevel()
{
AConfiguration* config = Utils::GetConfiguration();
ApiLevel sdkVersion = static_cast<ApiLevel>(AConfiguration_getSdkVersion(config));
AZ_Assert(sdkVersion >= ApiLevel::KitKat, "The Android runtime API level detected (%d) is unsupported", sdkVersion);
return sdkVersion;
}
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/JNI/Internal/JStringUtils.h>
namespace AZ { namespace Android { namespace JNI { namespace Internal
{
//! \brief Utility for getting the Java class names
//! \tparam StringType The type of string that should be return during generation. Defaults to AZStd::string
template<typename StringType = AZStd::string>
class ClassName
{
public:
//! Get the fully qualified forward slash separated Java class path of Java class ref.
//! e.g. android.app.NativeActivity ==> android/app/NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
static StringType GetName(jclass classRef)
{
StringType className;
AndroidEnv* androidEnv = AndroidEnv::Get();
AZ_Assert(androidEnv, "Attempting to use the AndroidEnv before it's created");
if (androidEnv)
{
className = GetNameImpl(classRef, androidEnv->m_getClassNameMethod);
}
return className;
}
//! Get just the name of the Java class from a Java class ref.
//! e.g. android.app.NativeActivity ==> NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
static StringType GetSimpleName(jclass classRef)
{
StringType className;
AndroidEnv* androidEnv = AndroidEnv::Get();
AZ_Assert(androidEnv, "Attempting to use the AndroidEnv before it's created");
if (androidEnv)
{
className = GetNameImpl(classRef, androidEnv->m_getSimpleClassNameMethod);
}
return className;
}
private:
static StringType GetNameImpl(jclass classRef, jmethodID methodId)
{
JNIEnv* jniEnv = GetEnv();
if (!jniEnv)
{
AZ_Error("JNI::ClassName", false, "Failed to get JNIEnv* on thread on call to GetClassNameImpl");
return StringType();
}
jstring rawStringValue = static_cast<jstring>(jniEnv->CallObjectMethod(classRef, methodId));
if (!rawStringValue || jniEnv->ExceptionCheck())
{
AZ_Error("JNI::ClassName", false, "Failed to invoke a GetName variant method on class Unknown");
HANDLE_JNI_EXCEPTION(jniEnv);
return StringType();
}
StringType className = ConvertJstringToStringImpl<StringType>(rawStringValue);
jniEnv->DeleteLocalRef(rawStringValue);
return className;
}
};
} // namespace Internal
} // namespace JNI
} // namespace Android
} // namespace AZ
@@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <jni.h>
namespace AZ { namespace Android { namespace JNI { namespace Internal
{
//! Converts a jstring to a string type
//! \param stringValue A local or global reference to a jstring object
//! \return A copy of the converted string
template<typename StringType>
StringType ConvertJstringToStringImpl(jstring stringValue);
//! Converts a string to a jstring
//! \param stringValue The native string value to be converted
//! \return A global reference to the converted jstring. The caller is responsible for
//! deleting it when no longer needed
template<typename StringType>
jstring ConvertStringToJstringImpl(const StringType& stringValue);
} // namespace Internal
} // namespace JNI
} // namespace Android
} // namespace AZ
#include <AzCore/Android/JNI/Internal/JStringUtils_impl.h>
@@ -0,0 +1,79 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ { namespace Android { namespace JNI { namespace Internal
{
//! Converts a jstring to a string type
//! \param stringValue A local or global reference to a jstring object
//! \return A copy of the converted string
template<typename StringType>
StringType ConvertJstringToStringImpl(jstring stringValue)
{
JNIEnv* jniEnv = GetEnv();
if (!jniEnv)
{
AZ_Error("AZ::Android::JNI", false, "Failed to get JNIEnv* on thread for jstring conversion");
return StringType();
}
const char* convertedStringValue = jniEnv->GetStringUTFChars(stringValue, nullptr);
if (!convertedStringValue || jniEnv->ExceptionCheck())
{
AZ_Error("AZ::Android::JNI", false, "Failed to convert a jstring to cstring");
HANDLE_JNI_EXCEPTION(jniEnv);
return StringType();
}
StringType localCopy(convertedStringValue);
jniEnv->ReleaseStringUTFChars(stringValue, convertedStringValue);
return localCopy;
}
//! Converts a string to a jstring
//! \param stringValue The native string value to be converted
//! \return A global reference to the converted jstring. The caller is responsible for
//! deleting it when no longer needed
template<typename StringType>
AZ_INLINE jstring ConvertStringToJstringImpl(const StringType& stringValue)
{
JNIEnv* jniEnv = GetEnv();
if (!jniEnv)
{
AZ_Error("AZ::Android::JNI", false, "Failed to get JNIEnv* on thread for jstring conversion");
return nullptr;
}
jstring localRef = jniEnv->NewStringUTF(stringValue.c_str());
if (!localRef || jniEnv->ExceptionCheck())
{
AZ_Error("AZ::Android::JNI", false, "Failed to convert the cstring to jstring");
HANDLE_JNI_EXCEPTION(jniEnv);
return nullptr;
}
jstring globalRef = static_cast<jstring>(jniEnv->NewGlobalRef(localRef));
if (!globalRef || jniEnv->ExceptionCheck())
{
AZ_Error("AZ::Android::JNI", false, "Failed to create a global reference to the return jstring");
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(localRef);
return nullptr;
}
jniEnv->DeleteLocalRef(localRef);
return globalRef;
}
} // namespace Internal
} // namespace JNI
} // namespace Android
} // namespace AZ
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,200 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/JNI/Internal/ClassName.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
template<typename StringType>
StringType GetTypeSignature(jobject value)
{
StringType signature("");
AZ_Error("JNI::Signature", value, "Call to GetTypeSignature with null jobject");
if (value)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Failed to get JNIEnv* on thread for get signature call");
if (jniEnv)
{
jclass objectClass = jniEnv->GetObjectClass(value);
StringType typeSig = ClassName<StringType>::GetName(objectClass);
signature.reserve(typeSig.size() + 3);
signature.append("L");
signature.append(typeSig);
signature.append(";");
jniEnv->DeleteLocalRef(objectClass);
AZStd::replace(signature.begin(), signature.end(), '.', '/');
}
}
return signature;
}
template<typename StringType>
StringType GetTypeSignature(jobjectArray value)
{
StringType signature("");
AZ_Error("JNI::Signature", value, "Call to GetTypeSignature with null jobjectArray");
if (value)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Failed to get JNIEnv* on thread for get signature call");
if (jniEnv)
{
jobject element = jniEnv->GetObjectArrayElement(value, 0);
if (!element || jniEnv->ExceptionCheck())
{
AZ_Error("JNI::Signature", false, "Unable to determine jobject array type");
HANDLE_JNI_EXCEPTION(jniEnv);
}
else
{
signature.append("[");
signature.append(GetTypeSignature<StringType>(element));
jniEnv->DeleteLocalRef(element);
}
}
}
return signature;
}
template<typename StringType, typename Type>
bool CompareTypeSignature(const StringType& baseSignature, Type param)
{
return (baseSignature.compare(GetTypeSignature(param)) == 0);
}
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobject param)
{
bool result = (!baseSignature.empty() && param);
if (result)
{
// check if the baseSignature is malformed e.g. doesn't start with 'L' or end with ';'
if (baseSignature[0] != 'L' || baseSignature[baseSignature.length() - 1] != ';')
{
return false;
}
// strip the preceding 'L' and trailing ';' from the class path
StringType classPath = baseSignature.substr(1, baseSignature.length() - 2);
if (JNIEnv* jniEnv = GetEnv())
{
// since it's valid to pass a derived java class through JNI we will need
// to check if the argument is an instance of the specified signature to
// accurately validate the signature
jclass signatureClass = LoadClass(classPath.c_str());
if (!signatureClass)
{
AZ_Assert(false, "Unable to load class in signature %s", classPath.c_str());
return false;
}
result = (jniEnv->IsInstanceOf(param, signatureClass) == JNI_TRUE);
DeleteRef(signatureClass);
}
else
{
result = false;
}
}
return result;
}
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobjectArray param)
{
bool result = (!baseSignature.empty() && param);
if (result)
{
// check if the baseSignature is malformed e.g. doesn't start with '['
if (baseSignature[0] != '[')
{
return false;
}
// strip the preceding '['
StringType typeSignature = baseSignature.substr(1, baseSignature.length() - 1);
if (JNIEnv* jniEnv = GetEnv())
{
jobject javaObject = jniEnv->GetObjectArrayElement(static_cast<jobjectArray>(param), 0);
result = CompareTypeSignature(typeSignature, javaObject);
DeleteRef(javaObject);
}
else
{
result = false;
}
}
return result;
}
} // namespace Internal
template<typename StringType>
template<typename Type, typename... Args>
bool Signature<StringType>::ValidateImpl(Type firstParam, Args&&... parameters)
{
const char* signature = m_signature.c_str();
const char* currentSignature = &(signature[m_currentIndex]);
int paramLength;
// extract the fully qualified class path for java objects
if (currentSignature[0] == 'L' || (strncmp(currentSignature, "[L", 2) == 0))
{
int endIndex = m_signature.find(';', m_currentIndex);
if (endIndex == StringType::npos)
{
AZ_Assert(false, "The base signature supplied (%s) for validation is malformed", m_signature.c_str());
return false;
}
paramLength = (endIndex - m_currentIndex) + 1; // +1 to include the trailing semicolon
}
// otherwise just extract the primitive type char(s)
else
{
paramLength = ((currentSignature[0] == '[') ? 2 : 1); // primitive types are 1 character signatures, arrays are 2
}
// extract the parameter signature and compare the value
StringType paramSignature = m_signature.substr(m_currentIndex, paramLength);
if (!Internal::CompareTypeSignature(paramSignature, firstParam))
{
return false;
}
m_currentIndex = m_currentIndex + paramLength;
if (m_currentIndex >= m_signatureLength)
{
return false;
}
return ValidateImpl(AZStd::forward<Args>(parameters)...);
}
} // namespace JNI
} // namespace Android
} // namespace AZ
@@ -0,0 +1,105 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Internal/JStringUtils.h>
#include <AzCore/Android/JNI/Internal/ClassName.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
////////////////////////////////////////////////////////////////
JNIEnv* GetEnv()
{
return AndroidEnv::Get()->GetJniEnv();
}
////////////////////////////////////////////////////////////////
jclass LoadClass(const char* classPath)
{
return AndroidEnv::Get()->LoadClass(classPath);
}
////////////////////////////////////////////////////////////////
AZStd::string GetClassName(jclass classRef)
{
return Internal::ClassName<AZStd::string>::GetName(classRef);
}
////////////////////////////////////////////////////////////////
AZStd::string GetSimpleClassName(jclass classRef)
{
return Internal::ClassName<AZStd::string>::GetSimpleName(classRef);
}
////////////////////////////////////////////////////////////////
AZStd::string ConvertJstringToString(jstring stringValue)
{
return Internal::ConvertJstringToStringImpl<AZStd::string>(stringValue);
}
////////////////////////////////////////////////////////////////
jstring ConvertStringToJstring(const AZStd::string& stringValue)
{
return Internal::ConvertStringToJstringImpl(stringValue);
}
////////////////////////////////////////////////////////////////
int GetRefType(jobject javaRef)
{
int refType = JNIInvalidRefType;
if (javaRef)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Unable to get JNIEnv pointer to determine JNI reference type.");
if (jniEnv)
{
refType = jniEnv->GetObjectRefType(javaRef);
}
}
return refType;
}
////////////////////////////////////////////////////////////////
void DeleteRef(jobject javaRef)
{
if (javaRef)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Unable to get JNIEnv pointer to free JNI reference.");
if (jniEnv)
{
jobjectRefType refType = jniEnv->GetObjectRefType(javaRef);
switch (refType)
{
case JNIGlobalRefType:
jniEnv->DeleteGlobalRef(javaRef);
break;
case JNILocalRefType:
jniEnv->DeleteLocalRef(javaRef);
break;
case JNIWeakGlobalRefType:
jniEnv->DeleteWeakGlobalRef(javaRef);
break;
default:
AZ_Error("AZ::Android::JNI", false, "Unknown or invalid reference type detected.");
break;
}
}
}
}
}
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <jni.h>
#include <android/asset_manager.h>
#define HANDLE_JNI_EXCEPTION(jniEnv) \
jniEnv->ExceptionDescribe(); \
jniEnv->ExceptionClear();
#if defined(AZ_DEBUG_BUILD)
#define JNI_SIGNATURE_VALIDATION
#endif
// redefine the JNI_FALSE and JNI_TRUE macros to ensure their correct types are represented when using them
#if defined(JNI_FALSE)
#undef JNI_FALSE
#define JNI_FALSE jboolean(0)
#endif // defined(JNI_FALSE)
#if defined(JNI_TRUE)
#undef JNI_TRUE
#define JNI_TRUE jboolean(1)
#endif // defined(JNI_TRUE)
namespace AZ
{
namespace Android
{
namespace JNI
{
//! Request a thread specific JNIEnv pointer from the Android environment.
//! \return A pointer to the JNIEnv on the current thread.
JNIEnv* GetEnv();
//! Loads a Java class as opposed to attempting to find a loaded class from the call stack.
//! \param classPath The fully qualified forward slash separated Java class path.
//! \return A global reference to the desired jclass. Caller is responsible for making a
//! call do DeleteGlobalJniRef when the jclass is no longer needed.
jclass LoadClass(const char* classPath);
//! Get the fully qualified forward slash separated Java class path of Java class ref.
//! e.g. android.app.NativeActivity ==> android/app/NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
AZStd::string GetClassName(jclass classRef);
//! Get just the name of the Java class from a Java class ref.
//! e.g. android.app.NativeActivity ==> NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
AZStd::string GetSimpleClassName(jclass classRef);
//! Converts a jstring to a AZStd::string
//! \param stringValue A local or global reference to a jstring object
//! \return A copy of the converted string
AZStd::string ConvertJstringToString(jstring stringValue);
//! Converts a string to a jstring
//! \param stringValue The native string value to be converted
//! \return A global reference to the converted jstring. The caller is responsible for
//! deleting it when no longer needed
jstring ConvertStringToJstring(const AZStd::string& stringValue);
//! Gets the reference type of the Java object. Can be Local, Global or Weak Global.
//! \param javaRef Raw Java object reference, can be null.
//! \return The result of GetObjectRefType as long as the object is valid,
//! otherwise JNIInvalidRefType.
int GetRefType(jobject javaRef);
//! Deletes a JNI object/class reference. Will handle local, global and weak global references.
//! \param javaRef Raw java object reference.
void DeleteRef(jobject javaRef);
}
}
}
@@ -0,0 +1,484 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/osstring.h>
#include <AzCore/std/typetraits/is_convertible.h>
#include <AzCore/std/typetraits/conditional.h>
#include <AzCore/std/utils.h>
#include <AzCore/Android/JNI/JNI.h>
#if defined(JNI_SIGNATURE_VALIDATION)
#include <AzCore/Android/JNI/Signature.h>
#include <AzCore/Outcome/Outcome.h>
#endif
namespace AZ { namespace Android
{
namespace JNI
{
namespace Internal
{
//! Utility to allow easier managing of JNI reference when hosting Java classes and objects
//! in native code. Provides the same functionality that is available when manipulating JNI
//! references directly with the raw JNIEnv pointer.
//! \tparam Allocator The type of allocator used for both it self and all it's internal
//! allocations. Defaults to AZ::SystemAllocator
template<typename Allocator = AZ::SystemAllocator>
class Object final
{
private:
//! special case if we are using the SystemAllocator to use the default AZStd::allocator instead of wrapping
//! the allocator in AZStdAlloc. This way the known string types (AZStd::string and AZ::OSString) are correctly
//! typedefed internally.
typedef typename AZStd::conditional<AZStd::is_same<Allocator, AZ::SystemAllocator>::value, AZStd::allocator, AZStdAlloc<Allocator>>::type AZStdAllocator;
public:
typedef AZStd::basic_string<char, AZStd::char_traits<char>, AZStdAllocator> string_type;
typedef AZStd::vector<JNINativeMethod, AZStdAllocator> vector_type;
AZ_CLASS_ALLOCATOR(Object<Allocator>, Allocator, 0);
//! Creates a custom jni object wrapper from a java class path. This JNI object
//! will take owner ship of all global refs used internally
//! \param classPath The full java class path for the object to be loaded
//! \param className The name of the java class, mostly use for logging purposes
explicit Object(const char* classPath, const char* className = nullptr);
//! Creates a JNI object wrapper based on an existing global ref
//! \param classRef The global reference to the jclass for the object
//! \param objectRef The global reference to the instance object
//! \param takeOwnership [Optional] Tell the object to clean up the argument global refs when destroyed
Object(jclass classRef, jobject objectRef, bool takeOwnership = false);
//! Automatically cleans up any global JNI reference with the JVM
~Object();
//! Register a non-static Java method with the associated Java object instance. These methods can only
//! be invoked with a valid jobject reference.
//! \param methodName The exact name of the java method to register
//! \param methodSignature The argument/return signature of the java method.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the method was registered successfully, False otherwise
bool RegisterMethod(const char* methodName, const char* methodSignature);
//! Register a static Java method with the associated Java class reference. These methods
//! can be invoked as long as the class reference is valid.
//! \param methodName The exact name of the java method to register
//! \param methodSignature The argument/return signature of the Java method.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the method was registered successfully, False otherwise
bool RegisterStaticMethod(const char* methodName, const char* methodSignature);
//! Register native callback with Java 'native' method.
//! \param nativeMethods All the native methods to register with the Java class.
//! \return True if all the methods were registered successfully, False otherwise
bool RegisterNativeMethods(vector_type nativeMethods);
//! Register a instance member field with the object.
//! \param fieldName The exact name of the java field to register.
//! \param fieldSignature The type signature of the Java field.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the field was registered successfully, False otherwise.
bool RegisterField(const char* fieldName, const char* fieldSignature);
//! Register a static member field with the object.
//! \param fieldName The exact name of the java static field to register.
//! \param fieldSignature The type signature of the Java static field.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the static field was registered successfully, False otherwise.
bool RegisterStaticField(const char* fieldName, const char* fieldSignature);
//! Creates a global reference to an java instance object
//! \param constructorSignature The function signature of the constructor desired for creating the object
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \param parameters All the arguments required for the function call
//! \return True if the global instance was created successfully, False otherwise
template<typename... Args>
bool CreateInstance(const char* constructorSignature, Args&&... parameters);
//! Destroys the global instance of the java object only. Static method calls can still be made, while instance methods will
//! fail until a new instance is constructed through CreateInstance
void DestroyInstance();
//!@{
//! All the Invoke<TYPE>Method functions are for calling registered instance methods on
//! a java object where <TYPE> is the return type of the java method.
//! \param methodName The exact name of the java method to call
//! \param parameters All the arguments required for the function call
template<typename... Args>
void InvokeVoidMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jboolean InvokeBooleanMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jbyte InvokeByteMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jchar InvokeCharMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jshort InvokeShortMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jint InvokeIntMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jlong InvokeLongMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jfloat InvokeFloatMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jdouble InvokeDoubleMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
string_type InvokeStringMethod(const char* methodName, Args&&... parameters);
//!@}
//! Call a java instance method that returns a customs java object such as an String, Array
//! or other java class type. This function is restricted to types derived from _jobject.
//! The return value will be a global reference and the caller is responsible for deleting
//! through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
InvokeObjectMethod(const char* methodName, Args&&... parameters);
//!@{
//! All the InvokeStatic<TYPE>Method functions are for calling registered static methods on
//! a java object where <TYPE> is the return type of the java method.
//! \param methodName The exact name of the java method to call
//! \param parameters All the arguments required for the function call
template<typename... Args>
void InvokeStaticVoidMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jboolean InvokeStaticBooleanMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jbyte InvokeStaticByteMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jchar InvokeStaticCharMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jshort InvokeStaticShortMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jint InvokeStaticIntMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jlong InvokeStaticLongMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jfloat InvokeStaticFloatMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jdouble InvokeStaticDoubleMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
string_type InvokeStaticStringMethod(const char* methodName, Args&&... parameters);
//!@}
//! Call a java static method that returns a customs java object such as an String, Array or
//! other java class type. This function is restricted to types derived from _jobject.
//! The return value will be a global reference and the caller is responsible for deleting
//! through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
InvokeStaticObjectMethod(const char* methodName, Args&&... parameters);
//!@{
//! All the Set<TYPE>Field functions are for setting a registered instance member on
//! a java object where <TYPE> is the type of the instance member.
//! \param fieldName The exact name of the java instance member to set.
//! \param value The new value to set the instance member.
void SetBooleanField(const char* fieldName, jboolean value);
void SetByteField(const char* fieldName, jbyte value);
void SetCharField(const char* fieldName, jchar value);
void SetShortField(const char* fieldName, jshort value);
void SetIntField(const char* fieldName, jint value);
void SetLongField(const char* fieldName, jlong value);
void SetFloatField(const char* fieldName, jfloat value);
void SetDoubleField(const char* fieldName, jdouble value);
void SetStringField(const char* fieldName, const string_type& value);
//!@}
//! Set a custom java object instance field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc.
template<typename ValueType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ValueType, jobject>::value>::type
SetObjectField(const char* fieldName, ValueType value);
//!@{
//! All the Get<TYPE>Field functions are for getting a registered instance member on
//! a java object where <TYPE> is the type of the instance member.
//! \param fieldName The exact name of the java instance member to get.
jboolean GetBooleanField(const char* fieldName);
jbyte GetByteField(const char* fieldName);
jchar GetCharField(const char* fieldName);
jshort GetShortField(const char* fieldName);
jint GetIntField(const char* fieldName);
jlong GetLongField(const char* fieldName);
jfloat GetFloatField(const char* fieldName);
jdouble GetDoubleField(const char* fieldName);
string_type GetStringField(const char* fieldName);
//!@}
//! Get a custom java object instance field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc. A global refernece will be returned and the caller is
//! responsible for deleting through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
GetObjectField(const char* fieldName);
//!@{
//! All the Set<TYPE>Field functions are for setting a registered instance member on
//! a java object where <TYPE> is the type of the instance member.
//! \param fieldName The exact name of the java instance member to set.
//! \param value The new value to set the static member.
void SetStaticBooleanField(const char* fieldName, jboolean value);
void SetStaticByteField(const char* fieldName, jbyte value);
void SetStaticCharField(const char* fieldName, jchar value);
void SetStaticShortField(const char* fieldName, jshort value);
void SetStaticIntField(const char* fieldName, jint value);
void SetStaticLongField(const char* fieldName, jlong value);
void SetStaticFloatField(const char* fieldName, jfloat value);
void SetStaticDoubleField(const char* fieldName, jdouble value);
void SetStaticStringField(const char* fieldName, const string_type& value);
//!@}
//! Set a custom java object static field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc.
template<typename ValueType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ValueType, jobject>::value>::type
SetStaticObjectField(const char* fieldName, ValueType value);
//!@{
//! All the Get<TYPE>Field functions are for getting a registered static member on
//! a java object where <TYPE> is the type of the static member.
//! \param fieldName The exact name of the java instance member to get.
jboolean GetStaticBooleanField(const char* fieldName);
jbyte GetStaticByteField(const char* fieldName);
jchar GetStaticCharField(const char* fieldName);
jshort GetStaticShortField(const char* fieldName);
jint GetStaticIntField(const char* fieldName);
jlong GetStaticLongField(const char* fieldName);
jfloat GetStaticFloatField(const char* fieldName);
jdouble GetStaticDoubleField(const char* fieldName);
string_type GetStaticStringField(const char* fieldName);
//!@}
//! Get a custom java object static field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc. A global reference will be returned and the caller is
//! responsible for deleting through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
GetStaticObjectField(const char* fieldName);
private:
//! Simple structure containing core information about a registered Java method
struct JMethodCache
{
jmethodID m_methodId; //!< Pointer to the method reference on the JVM
#if defined(JNI_SIGNATURE_VALIDATION)
string_type m_methodName; //!< Name of the Java method, mostly used for debug logs
string_type m_argumentSignature; //!< Java type signature of all method arguments e.g. (int, String) => ILjava/lang/String;
string_type m_returnSignature; //!< Java type signature of the return value
#endif
};
//! Simple structure containing core information about a registered Java field
struct JFieldCache
{
jfieldID m_fieldId; //!< Pointer to the field reference on the JVM
#if defined(JNI_SIGNATURE_VALIDATION)
string_type m_fieldName; //!< Name of the Java field, mostly used for debug logs
string_type m_signature; //!< Java type signature of the field
#endif
};
typedef AZStd::shared_ptr<JMethodCache> JMethodCachePtr;
typedef AZStd::shared_ptr<JFieldCache> JFieldCachePtr;
template<typename ValueType>
using CacheMap = AZStd::unordered_map<string_type, ValueType, AZStd::hash<string_type>, AZStd::equal_to<string_type>, AZStdAllocator>;
typedef CacheMap<JMethodCachePtr> JMethodMap;
typedef CacheMap<JFieldCachePtr> JFieldMap;
template<typename ReturnType>
using JniMethodCallback = AZStd::function<ReturnType(JNIEnv*, jobject, jmethodID)>;
template<typename ReturnType>
using JniStaticMethodCallback = AZStd::function<ReturnType(JNIEnv*, jclass, jmethodID)>;
template<typename ReturnType>
using JniFieldCallback = AZStd::function<ReturnType(JNIEnv*, jobject, jfieldID)>;
template<typename ReturnType>
using JniStaticFieldCallback = AZStd::function<ReturnType(JNIEnv*, jclass, jfieldID)>;
#if defined(JNI_SIGNATURE_VALIDATION)
using SignatureOutcome = AZ::Outcome<void, string_type>;
typedef Signature<string_type> SigUtil;
#endif
// ----
//! Helper to find a register instance method
JMethodCachePtr GetMethod(const string_type& methodName) const;
//! Helper to find a register static method
JMethodCachePtr GetStaticMethod(const string_type& methodName) const;
//! Helper to find a register instance field
JFieldCachePtr GetField(const string_type& fieldName) const;
//! Helper to find a register static field
JFieldCachePtr GetStaticField(const string_type& fieldName) const;
#if defined(JNI_SIGNATURE_VALIDATION)
//! Helper to extract the argument and return signatures from a complete method signature
//! and set the respective properties within the specified JMethodCache pointer.
//! \param methodCache JMethodCache pointer to set
//! \param methodName The exact name of the java method
//! \param methodSignature The full method signature
void SetMethodSignature(JMethodCachePtr methodCache, const char* methodName, const char* methodSignature);
//! Helper to extract the argument and return signatures from a complete method signature
//! and set the respective properties within the specified JMethodCache pointer.
//! \param fieldCache JFieldCache pointer to set
//! \param fieldName The exact name of the java field
//! \param signature The signature, or type, of the java field
void SetFieldSignature(JFieldCachePtr fieldCache, const char* fieldName, const char* signature);
//! Performs a full signature validation for all types in Args
//! \param baseSignature The base signature used to register the JNI method or field
//! \param parameters Pending arguments to a JNI call needing validation
//! \return \ref AZ::Success if all parameters pass validation, \ref AZ::Failure<string_type> containing the
//! error message otherwise
template<typename... Args>
SignatureOutcome ValidateSignature(const string_type& baseSignature, Args&&... parameters);
//! Performs a partial signature validation when \p Type is jobject or jobjectArray, and a full signature validation
//! for other types. Primarily used for basic validation of return type values when the requested type
//! can't be deduced without making the JNI call first.
//! \param baseSignature The base signature used to register the JNI method or field
//! \param param Pending type to a JNI call needing validation
//! \return \ref AZ::Success if all parameters pass validation, \ref AZ::Failure<string_type> containing the
//! error message otherwise
template<typename Type>
SignatureOutcome ValidateSignaturePartial(const string_type& baseSignature, Type param);
#endif // defined(JNI_SIGNATURE_VALIDATION)
//! Helper for invoking a primitive type instance method on the JNI object
//! \param methodName The name of the instance method to invoke
//! \param jniCallback Lambda wrapper to the actual JNIEnv::Call<type>Method
//! \param parameters Java method call arguments list
//! \return The primitive return value from the Java method
template<typename ReturnType, typename... Args>
ReturnType InvokePrimitiveTypeMethodInternal(const char* methodName, JniMethodCallback<ReturnType> jniCallback, Args&&... parameters);
//! Helper for invoking a primitive type instance method on the JNI object
//! \param methodName The name of the instance method to invoke
//! \param jniCallback Lambda wrapper to the actual JNIEnv::CallStatic<type>Method
//! \param parameters Java method call arguments list
//! \return The primitive return value from the Java method
template<typename ReturnType, typename... Args>
ReturnType InvokePrimitiveTypeStaticMethodInternal(const char* methodName, JniStaticMethodCallback<ReturnType> jniCallback, Args&&... parameters);
//! Helper for setting a primitive type instance field
//! \param fieldName The name of the instance field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::Set<type>Field
//! \param value New value of the instance field
template<typename ValueType>
void SetPrimitiveTypeFieldInternal(const char* fieldName, JniFieldCallback<void> jniCallback, ValueType value);
//! Helper for getting a primitive type instance field
//! \param fieldName The name of the instance field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::Get<type>Field
//! \return The current value of the primitive instance field
template<typename ReturnType>
ReturnType GetPrimitiveTypeFieldInternal(const char* fieldName, JniFieldCallback<ReturnType> jniCallback);
//! Helper for setting a primitive type static field
//! \param fieldName The name of the static field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::SetStatic<type>Field
//! \param value New value of the static field
template<typename ValueType>
void SetPrimitiveTypeStaticFieldInternal(const char* fieldName, JniStaticFieldCallback<void> jniCallback, ValueType value);
//! Helper for getting a primitive type static field
//! \param fieldName The name of the static field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::GetStatic<type>Field
//! \return The current value of the primitive static field
template<typename ReturnType>
ReturnType GetPrimitiveTypeStaticFieldInternal(const char* fieldName, JniStaticFieldCallback<ReturnType> jniCallback);
// ----
string_type m_className; //!< The simple name of the Java class, used for debugging
AZStdAllocator m_stdAllocator; //!< Allocator instance used for allocating the JMethodCachePtr/JFieldCachePtr shared pointers
jclass m_classRef; //!< A global reference to the java class, used for method/filed extraction, static method invocation
jobject m_objectRef; //!< A global reference to the java object instance, used for instance method invocation, field access
JMethodMap m_methods; //!< Container of all the instance methods currently registered for the java class
JMethodMap m_staticMethods; //!< Container of all the static methods currently registered for the java class
JFieldMap m_fields; //!< Container of all the instance fields currently registered for the java class
JFieldMap m_staticFields; //!< Container of all the static fields currently registered for the java class
bool m_ownsGlobalRefs; //!< Should the global references be destroyed automatically or manually
bool m_instanceConstructed; //!< Can we invoke instance methods, has CreateInstance ben called
};
} // namespace Internal
//! \brief The default \ref AZ::Android::JNI::Internal::Object type which uses the SystemAllocator
typedef Internal::Object<AZ::SystemAllocator> Object;
} // namespace JNI
} // namespace Android
} // namespace AZ
#include <AzCore/Android/JNI/Internal/Object_impl.h>
@@ -0,0 +1,29 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
template<typename Allocator>
class Object;
}
//! \brief The default \ref AZ::Android::JNI::Internal::Object type which uses the SystemAllocator
typedef Internal::Object<AZ::SystemAllocator> Object;
}
}
}
@@ -0,0 +1,269 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/osstring.h>
#include <AzCore/std/utils.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
//! \brief Templated interface for getting specific JNI type signatures. This is intentionally left empty
//! to enforce usage to only known template specializations.
//! \tparam Type The JNI type desired for the signature request
//! \tparam StringType The type of string that should be returned. Defaults to 'const char*'
//! \return String containing the type specific JNI signature
template<typename Type, typename StringType = const char*>
StringType GetTypeSignature(Type);
///!@{
//! \brief Known type specializations for \ref AZ::Android::JNI::Internal::GetTypeSignature.
template<> inline const char* GetTypeSignature(jboolean) { return "Z"; }
template<> inline const char* GetTypeSignature(bool) { return "Z"; }
template<> inline const char* GetTypeSignature(jbooleanArray) { return "[Z"; }
template<> inline const char* GetTypeSignature(jbyte) { return "B"; }
template<> inline const char* GetTypeSignature(jbyteArray) { return "[B"; }
template<> inline const char* GetTypeSignature(jchar) { return "C"; }
template<> inline const char* GetTypeSignature(jcharArray) { return "[C"; }
template<> inline const char* GetTypeSignature(jshort) { return "S"; }
template<> inline const char* GetTypeSignature(jshortArray) { return "[S"; }
template<> inline const char* GetTypeSignature(jint) { return "I"; }
template<> inline const char* GetTypeSignature(jintArray) { return "[I"; }
template<> inline const char* GetTypeSignature(jlong) { return "J"; }
template<> inline const char* GetTypeSignature(jlongArray) { return "[J"; }
template<> inline const char* GetTypeSignature(jfloat) { return "F"; }
template<> inline const char* GetTypeSignature(jfloatArray) { return "[F"; }
template<> inline const char* GetTypeSignature(jdouble) { return "D"; }
template<> inline const char* GetTypeSignature(jdoubleArray) { return "[D"; }
template<> inline const char* GetTypeSignature(jstring) { return "Ljava/lang/String;"; }
template<> inline const char* GetTypeSignature(jclass) { return "Ljava/lang/Class;"; }
template<typename StringType>
StringType GetTypeSignature(jobject value);
template<typename StringType>
StringType GetTypeSignature(jobjectArray value);
//!@}
//! \brief Templated interface for comparing JNI type signatures. This leverages
//! \ref AZ::Android::JNI::Internal::GetTypeSignature under the hood
//! to weed out unsupported types
//! \tparam StringType The type of string that should used for base comparison
//! \tparam Type The JNI type desired for the signature verification
//! \param baseSignature The string representation of the expected type \p param should be
//! \param param The raw JNI type to be validated
//! \return True if \p param is an acceptable type for the type specified in \p baseSignature,
//! false otherwise
template<typename StringType, typename Type>
bool CompareTypeSignature(const StringType& baseSignature, Type param);
///!@{
//! \brief Explicit definitions for jobject and jobjectArray need to be defined in order to support
//! subclass validation for Java classes
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobject param);
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobjectArray param);
//!@}
}
//! \brief Utility for generating and validating JNI signatures
//! \tparam StringType The type of string used internally for generation and validation. Defaults to AZStd::string
template<typename StringType = AZStd::string>
class Signature
{
public:
//! \brief Required for handling cases when an empty set of variadic arguments are forwarded from
//! \ref AZ::Android::JNI::GetSignature calls
//! \return An empty string
static StringType Generate()
{
Signature sig;
return sig.m_signature;
}
//! \brief Gets the signature from n-number of parameters
//! \param parameters Variables only used to forward their types on to \ref AZ::Android::JNI::Signature::GenerateImpl
//! \return String containing a fully qualified Java signature
template<typename... Args>
static StringType Generate(Args&&... parameters)
{
Signature sig;
sig.GenerateImpl(AZStd::forward<Args>(parameters)...);
return sig.m_signature;
}
//! \brief Required for handling cases when an empty set of variadic arguments are forwarded from
//! \ref AZ::Android::JNI::ValidateSignature calls
//! \param baseSignature The string representation of the expected type signature. Should be an empty string in
//! this case.
//! \return True if the string is empty (e.g. nothing to validate), false otherwise
static bool Validate(const StringType& baseSignature)
{
Signature sig(baseSignature);
return sig.m_signature.empty();
}
//! \brief Validates n-number of parameters type signatures
//! \param baseSignature The string representation of the expected JNI type signatures in \p parameters
//! \param parameters The input arguments to be validated
//! \return True if all arguments in \p parameters match the expected type signature in \p baseSignature, False otherwise
template<typename... Args>
static bool Validate(const StringType& baseSignature, Args&&... parameters)
{
Signature sig(baseSignature);
return (sig.m_signature.empty() ?
false :
sig.ValidateImpl(AZStd::forward<Args>(parameters)...));
}
private:
//! \brief Internal constructor of the signature util used for generation. Pre-allocates some memory for the internal
//! cache to try and prevent the hammering of reallocations in most cases.
Signature()
: m_signature()
, m_signatureLength(0)
, m_currentIndex(0)
{
m_signature.reserve(8);
}
//! \brief Internal constructor of the signature util used for validation.
//! \param baseSignature The signature to compare against
explicit Signature(const StringType& baseSignature)
: m_signature(baseSignature)
, m_signatureLength(0)
, m_currentIndex(0)
{
m_signatureLength = m_signature.length();
}
//! \brief Appends the desired type to the internal signature cache
//! \param value Throwaway variable only used to forward the type on to \ref AZ::Android::JNI::Internal::GetTypeSignature
template<typename Type>
void GenerateImpl(Type value)
{
m_signature.append(Internal::GetTypeSignature(value));
}
///!@{
//! \brief Explicit definitions for jobject and jobjectArray need to be defined in order to route their
//! calls to the correct version of \ref AZ::Android::JNI::Internal::GetTypeSignature which returns
//! a string instead of a c-string.
void GenerateImpl(jobject value)
{
m_signature.append(Internal::GetTypeSignature<StringType>(value));
}
void GenerateImpl(jobjectArray value)
{
m_signature.append(Internal::GetTypeSignature<StringType>(value));
}
//!@}
//! \brief Appends the desired type to the internal signature cache
//! \param first Variable only used to forward the type on to \ref AZ::Android::JNI::Signature::GenerateImpl
//! \param parameters Additional variables only used to forward their types into recursive calls
template<typename Type, typename... Args>
void GenerateImpl(Type first, Args&&... parameters)
{
GenerateImpl(first);
GenerateImpl(AZStd::forward<Args>(parameters)...);
}
//! \brief Validates a single (or final) type against the remaining signature(s) in the internal signature cache
//! \param param The type to be validated
//! \return True if the type of \p param matches the remaining signature(s) in the internal signature cache,
//! False otherwise
template<typename Type>
bool ValidateImpl(Type param)
{
int paramLength = m_signatureLength - m_currentIndex;
if (paramLength > 0)
{
StringType paramSignature = m_signature.substr(m_currentIndex, paramLength);
return Internal::CompareTypeSignature<StringType>(paramSignature, param);
}
return false;
}
//! \brief Validates n-number of parameters type signatures
//! \param first The first type to be validated
//! \param parameters The remaining types to be validated recursively
//! \return True if all the types in \p parameters match the expected type signature stored internally,
//! False otherwise
template<typename Type, typename... Args>
bool ValidateImpl(Type first, Args&&... parameters);
// ----
StringType m_signature; //!< Internal cache for signature generation/validation
int m_signatureLength; //!< Cache of the total length of the base signature for validation
int m_currentIndex; //!< Current index in walking the base signature for validation
};
//! \brief Default Signature template (AZStd::string), primarily used in \ref AZ::Android::JNI::GetSignature
//! and \ref AZ::Android::JNI::ValidateSignature
typedef Signature<AZStd::string> SignatureUtil;
//! \brief Generates a fully qualified Java signature from n-number of parameters. This is the preferred implementation
//! for generating JNI signatures
//! \param parameters Variables only used to forward their type info on to \ref AZ::Android::Signature::Generate
//! \return String containing a fully qualified Java signature
template<typename... Args>
AZ_INLINE AZStd::string GetSignature(Args&&... parameters)
{
return SignatureUtil::Generate(AZStd::forward<Args>(parameters)...);
}
//! \brief Validates a JNI signature with n-number of parameters. Will walk the signature validating
//! each parameter individually. The validation will exit once an argument fails validation.
//! \param baseSignature Base JNI signature to be comparing against
//! \param parameters The input arguments to be validated
//! \return True if all arguments match the signature, False otherwise
template<typename... Args>
AZ_INLINE bool ValidateSignature(const AZStd::string& baseSignature, Args&&... parameters)
{
return SignatureUtil::Validate(baseSignature, AZStd::forward<Args>(parameters)...);
};
} // namespace JNI
} // namespace Android
} // namespace AZ
#include <AzCore/Android/JNI/Internal/Signature_impl.h>
@@ -0,0 +1,132 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/typetraits/typetraits.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
//! A scoped_ref works in the same way a AZStd::scoped_ptr except it's specificially
//! designed for the opaque pointer JNI types (e.g. jobject, jarray, etc.). Guarantees
//! the java object is released from the JNI environment when the scoped_ref falls
//! out of scope.
template<typename JniType>
class scoped_ref
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
typedef scoped_ref<JniType> ThisType;
public:
typedef JniType ThisType::* UnspecifiedBoolType;
// ---
//! Only explicit scoped_refs are allowed to be constructed
//! \param javaObject Raw pointer to the java object. Currently only supports Local and
//! Global reference types. Weak Global reference are NOT supported.
explicit scoped_ref(JniType javaObject = nullptr)
: m_javaObject(javaObject)
{
#if defined(AZ_ENABLE_TRACING)
if (m_javaObject)
{
int refType = GetRefType(m_javaObject);
AZ_Error("JNI::scoped_ref",
refType == JNIGlobalRefType || refType == JNILocalRefType,
"Unsupported JNI reference type (%d) used in JNI::scoped_ref. "
"Supported reference types are JNIGlobalRefType and JNILocalRefType. "
"This may lead to unexpected behaviour.", refType);
}
#endif // defined(AZ_ENABLE_TRACING)
}
//! Automatically release the reference with the JNI environment when the object
//! goes out of scope
~scoped_ref()
{
DeleteRef(m_javaObject);
}
//! Compatibilty with the 'not' operator for validity checks. Only checkes for raw
//! pointer validity, NOT if it's pointing to a null reference (weak global ref).
bool operator !() const
{
return (m_javaObject == nullptr);
}
//! Operator for implicit bool conversions
//! \return 'True' if internal reference is valid, False otherwise
operator UnspecifiedBoolType() const
{
return (m_javaObject == nullptr ? nullptr : &ThisType::m_javaObject);
}
//! Explicit accessor of the raw pointer to the java reference.
//! \return The raw pointer to the java object reference
JniType get() const
{
return m_javaObject;
}
//! Swap the internal reference with another scoped_ref of the same type
//! \param lhs The scoped_ref (of same type) to be swaped with
void swap(scoped_ref& lhs)
{
AZStd::swap(m_javaObject, lhs.m_javaObject);
}
//! Reset the internal reference with a new pointer
//! \param javaObject Raw pointer to the java object. Must be of same type.
void reset(JniType javaObject = nullptr)
{
// Pointer level self reset. Triggering this assert will cause a crash when either this
// reference is used, or when the this scoped ref is cleaned up (double/invalid delete).
AZ_Assert(javaObject == nullptr || javaObject != m_javaObject, "JNI::scoped_ref pointer level self reset!");
// JNI reference level "self" reset. The references themselves are different so this is a
// valid reset, however the underlining java object the references are pointing to
// is the same in this case. As far as the JNI environment is concerned this is ok
// but we should still make note of these occurrences.
// NOTE: This warning will also trigger in the event the pointers are the same.
AZ_Warning("JNI::scoped_ref", GetEnv()->IsSameObject(m_javaObject, javaObject) == JNI_FALSE, "JNI::scoped_ref JNI reference level self reset.");
ThisType(javaObject).swap(*this);
}
private:
//! Disable copy/move
///@{
AZ_DISABLE_COPY_MOVE(scoped_ref);
///@}
//! Disable direct comparisons of other scoped_refs
///@{
void operator==(scoped_ref const&) const;
void operator!=(scoped_ref const&) const;
///@}
// ----
JniType m_javaObject; //!< Raw pointer of the java object reference (e.g. jobject, jarray, etc.)
};
}
}
}
@@ -0,0 +1,445 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/typetraits/is_convertible.h>
#include <AzCore/std/smart_ptr/shared_count.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
using sp_counted_base = AZStd::Internal::sp_counted_base;
using sp_typeinfo = AZStd::type_id;
//! Similar to the AZStd::Internal::sp_counted_impl_pa in that accepts the data type and
//! custom allocator type, however the data type is restricted to types that inherit from
//! jobject. See AzCore/std/smartptr/shared_count.h for more details
template<typename JniType, typename AllocatorType>
class sr_counted_impl
: public AZStd::Internal::sp_counted_base
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
public:
//! Only explicit contruction of the shared_count private impl for shared_refs
//! \param javaObject The raw JNI pointer
//! \param allocator Custom allocator, used in the deallocation of this particular object,
//! NOT the JNI pointer
sr_counted_impl(JniType javaObject, const AllocatorType& allocator)
: m_javaObject(javaObject)
, m_allocator(allocator)
{
}
//! Called when the use count drops to zero. DOES release the JNI referene.
void dispose() override
{
DeleteRef(m_javaObject);
}
//! Called when the weak count drops to zero. Does NOT release the JNI referene.
void destroy() override
{
this->~ThisType();
m_allocator.deallocate(this, sizeof(ThisType), AZStd::alignment_of<ThisType>::value);
}
//! Throwaway pure-virtual. Custom deleters are not supported for shared_refs since
//! they have to be released from the JNI environment
void* get_deleter(sp_typeinfo const&) override
{
return nullptr;
}
private:
typedef sr_counted_impl<JniType, AllocatorType> ThisType;
//! Disable copy/move
///@{
AZ_DISABLE_COPY_MOVE(sr_counted_impl);
///@}
// ----
JniType m_javaObject; //!< The raw JNI pointer from the JVM
AllocatorType m_allocator; //!< Custom alloctor used for the [de]allocation of this object
};
//! Similar to the AZStd::Internal::shared_count, however the data type is restricted to
//! types that inherit from jobject. See AzCore/std/smartptr/shared_count.h for more details
class shared_count
{
public:
//! Default contruction, no private impl will be created e.i. the count is not valid
shared_count()
: m_impl(nullptr)
{
}
//! Explicit construction of the shared_count requiring the raw JNI pointer to manage
//! and a custom allocator to handle the private impl count [de]allocations
//! \param javaObject Raw JNI pointer from the JVM
//! \param allocator Custom allocator used only for the private impl count [de]allocations
template<typename JniType, typename Allocator>
shared_count(JniType javaObject, const Allocator& allocator)
: m_impl(nullptr)
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
typedef sr_counted_impl<JniType, Allocator> impl_type;
Allocator a2(allocator);
m_impl = reinterpret_cast<sp_counted_base*>(a2.allocate(sizeof(impl_type), AZStd::alignment_of<impl_type>::value));
if (m_impl)
{
new(m_impl)impl_type(javaObject, allocator);
}
else
{
AZ_Assert(false, "Failed to allocate the shared count for JNI::shared_ref. Releasing reference from JVM.");
DeleteRef(javaObject);
}
}
//! Copy the shared count, increase the count if valid
shared_count(shared_count const& rhs)
: m_impl(rhs.m_impl)
{
if (m_impl)
{
m_impl->add_ref_copy();
}
}
//! Move the shared count, will invalidate the private impl of of the moved shared_count
shared_count(shared_count&& rhs)
: m_impl(rhs.m_impl)
{
rhs.m_impl = nullptr;
}
//! Decrease the shared count, if valid, on deletion
~shared_count()
{
if (m_impl)
{
m_impl->release();
}
}
//! Copy the shared count. Increases the new count, if valid; decreases the old count, if valid
shared_count& operator =(shared_count const& rhs)
{
sp_counted_base* tmp = rhs.m_impl;
if (tmp != m_impl)
{
if (tmp)
{
tmp->add_ref_copy();
}
if (m_impl)
{
m_impl->release();
}
m_impl = tmp;
}
return *this;
}
//! Check to see if two shared_counts are managing the same private impl pointer
bool operator ==(shared_count const& rhs)
{
return (m_impl == rhs.m_impl);
}
//! Swap the private impl pointers between two shared_counts
void swap(shared_count& rhs)
{
AZStd::swap(m_impl, rhs.m_impl);
}
//! Get the number of reference held by the shared count, if valid
long use_count() const
{
return (m_impl != nullptr ? m_impl->use_count() : 0);
}
//! Check to see if the shared_count is the only one holding on to the private
//! impl pointer
bool unique() const
{
return (use_count() == 1);
}
private:
//! The private impl of the shared_count. This object is the one responsible for
//! releasing the JNI reference with the JVM.
sp_counted_base* m_impl;
};
}
//! A shared_ref works in the same way a AZStd::shared_ptr except it's specificially
//! designed for the opaque pointer JNI types (e.g. jobject, jarray, etc.). Guarantees
//! the java object is released from the JNI environment once the last shared_ref pointing
//! to is released.
template<typename JniType>
class shared_ref
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
typedef shared_ref<JniType> ThisType;
public:
typedef JniType ThisType::* UnspecifiedBoolType;
// ---
//! Construct a default shared_ref with a null raw JNI pointer
shared_ref()
: m_javaObject(nullptr)
, m_count()
{
}
//! Explicit construction of shared_ref with a null raw JNI pointer
shared_ref(AZStd::nullptr_t)
: shared_ref()
{
}
//! Only allow explicit construction from the raw pointer to the java object reference.
//! Will use the AZ::SytemAllocator for the shared count allocations
//! \param javaObject Raw pointer to the java object. Currently only supports Local and
//! Global reference types. Weak Global reference are NOT supported.
explicit shared_ref(JniType javaObject)
: m_javaObject(javaObject)
, m_count(javaObject, AZStd::allocator())
{
#if defined(AZ_ENABLE_TRACING)
if (m_javaObject)
{
int refType = GetRefType(m_javaObject);
AZ_Error("JNI::shared_ref",
refType == JNIGlobalRefType || refType == JNILocalRefType,
"Unsupported JNI reference type (%d) used in JNI::shared_ref. "
"Supported reference types are JNIGlobalRefType and JNILocalRefType. "
"This may lead to unexpected behaviour.", refType);
}
#endif // defined(AZ_ENABLE_TRACING)
}
//! Create a shared_ref with a custom allocator.
//! NOTE: The custom allocator is only used for allocating the shared_count
//! \param javaObject Raw pointer to the java object. Currently only supports Local and
//! Global reference types. Weak Global reference are NOT supported.
//! \param allocator Custom allocator for usage within the shared count.
template<typename Allocator>
shared_ref(JniType javaObject, const Allocator& allocator)
: m_javaObject(javaObject)
, m_count(javaObject, allocator)
{
#if defined(AZ_ENABLE_TRACING)
if (m_javaObject)
{
int refType = GetRefType(m_javaObject);
AZ_Error("JNI::shared_ref",
refType == JNIGlobalRefType || refType == JNILocalRefType,
"Unsupported JNI reference type (%d) used in JNI::shared_ref. "
"Supported reference types are JNIGlobalRefType and JNILocalRefType. "
"This may lead to unexpected behaviour.", refType);
}
#endif // defined(AZ_ENABLE_TRACING)
}
//! Make a copy of the shared_ref, increase the reference count
//! \param rhs The shared_ref to copy
explicit shared_ref(const shared_ref& rhs)
: m_javaObject(rhs.m_javaObject)
, m_count(rhs.m_count)
{
}
//! Polymorphic copy of a shared_ref
//! \param rhs The shared_ref of a derived JNI pointer type to be copied
template<typename Y>
shared_ref(const shared_ref<Y>& rhs, typename AZStd::enable_if<AZStd::is_convertible<Y, JniType>::value, Y>::type = AZStd::nullptr_t())
: m_javaObject(rhs.m_javaObject)
, m_count(rhs.m_count)
{
}
//! Move the shared_ref from one shared_ref to another, Ctor
//! \param rhs The shared_ref to be moved
shared_ref(shared_ref&& rhs)
: m_javaObject(rhs.m_javaObject)
, m_count()
{
m_count.swap(rhs.m_count);
rhs.m_javaObject = nullptr;
}
//! Polymorphic move the shared_ref from one shared_ref to another, Ctor
//! \param rhs The shared_ref to be moved
template<typename Y>
shared_ref(shared_ref<Y>&& rhs)
: m_javaObject(rhs.m_javaObject)
, m_count()
{
m_count.swap(rhs.m_count);
rhs.m_javaObject = nullptr;
}
//! Make a copy of the shared_ref, increase the reference count
//! \param rhs The shared_ref to copy
shared_ref& operator=(const shared_ref& rhs) // never throws
{
ThisType(rhs).swap(*this);
return *this;
}
//! Make a polymorphic copy of the shared_ref, increase the reference count
//! \param rhs The shared_ref to copy
template<typename Y>
shared_ref& operator=(const shared_ref<Y>& rhs)
{
ThisType(rhs).swap(*this);
return *this;
}
//! Move the shared_ref from one shared_ref to another
//! \param rhs The shared_ref to be moved
shared_ref& operator=(shared_ref&& rhs)
{
ThisType(rhs).swap(*this);
return *this;
}
//! Polymorphic move of a shared_ref from one shared_ref to another
//! \param rhs The shared_ref to be moved
template<typename Y>
shared_ref& operator=(shared_ref<Y>&& rhs)
{
ThisType(rhs).swap(*this);
return *this;
}
//! Determine if two shared_refs are the same
//! \param rhs The shared_ref to compare against, may be of another JNI pointer type
//! \return True if the raw pointers are the same, False otherwise
template<typename Y>
bool operator==(const shared_ref<Y>& rhs) const
{
return this->get() == rhs.get();
}
//! Determine if two shared_refs are not the same
//! \param rhs The shared_ref to compare against, may be of another JNI pointer type
//! \return True if the raw pointers are the same, False otherwise
template<typename Y>
bool operator!=(const shared_ref<Y>& rhs) const
{
return this->get() != rhs.get();
}
//! Compatibilty with the 'not' operator for validity checks. Only checkes for raw
//! pointer validity, NOT if it's pointing to a null reference (weak global ref).
bool operator !() const
{
return (m_javaObject == nullptr);
}
//! Operator for implicit bool conversions
//! \return 'True' if internal reference is valid, False otherwise
operator UnspecifiedBoolType() const
{
return (m_javaObject == nullptr ? nullptr : &ThisType::m_javaObject);
}
//! Explicit accessor of the raw pointer to the java reference.
//! \return The raw pointer to the java object reference
JniType get() const
{
return m_javaObject;
}
//! Check to see if the shared_ref is the only one holding on to the raw JNI pointer
//! \return True if the only refernece, False othewise
bool unique() const
{
return m_count.unique();
}
//! Get the number of reference held on the raw JNI pointer
long use_count() const
{
return m_count.use_count();
}
//! Swap the internal reference with another shared_ref of the same type
//! \param lhs The shared_ref (of same type) to be swaped with
void swap(shared_ref& lhs)
{
AZStd::swap(m_javaObject, lhs.m_javaObject);
m_count.swap(lhs.m_count);
}
//! Default reset of the internal reference to nullptr
void reset()
{
ThisType().swap(*this);
}
//! Reset the internal reference with a new pointer
//! \param javaObject Raw pointer to the java object. Must be of same type.
void reset(JniType javaObject)
{
// Pointer level self reset. Triggering this assert will cause a crash when either this
// reference is used, or when the this scoped ref is cleaned up (double/invalid delete).
AZ_Assert(javaObject == nullptr || javaObject != m_javaObject, "JNI::shared_ref pointer level self reset!");
// JNI reference level "self" reset. The references themselves are different so this is a
// valid reset, however the underlining java object the references are pointing to
// is the same in this case. As far as the JNI environment is concerned this is ok
// but we should still make note of these occurrences.
// NOTE: This warning will also trigger in the event the pointers are the same.
AZ_Warning("JNI::shared_ref", GetEnv()->IsSameObject(m_javaObject, javaObject) == JNI_FALSE, "JNI::shared_ref JNI reference level self reset.");
ThisType(javaObject).swap(*this);
}
private:
template<class Y> friend class shared_ref;
// ----
JniType m_javaObject; //!< Raw pointer of the java object reference (e.g. jobject, jarray, etc.)
Internal::shared_count m_count; //!< Shared reference count, responsible for releaseing the JNI reference from the JVM
};
}
}
}
@@ -0,0 +1,197 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Android/Utils.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/IO/Path/Path.h>
namespace AZ
{
namespace Android
{
namespace Utils
{
namespace
{
////////////////////////////////////////////////////////////////
constexpr const char* GetApkAssetsPrefix()
{
return "/APK";
}
}
////////////////////////////////////////////////////////////////
jclass GetActivityClassRef()
{
return AndroidEnv::Get()->GetActivityClassRef();
}
////////////////////////////////////////////////////////////////
jobject GetActivityRef()
{
return AndroidEnv::Get()->GetActivityRef();
}
////////////////////////////////////////////////////////////////
AAssetManager* GetAssetManager()
{
return AndroidEnv::Get()->GetAssetManager();
}
////////////////////////////////////////////////////////////////
AConfiguration* GetConfiguration()
{
return AndroidEnv::Get()->GetConfiguration();
}
////////////////////////////////////////////////////////////////
void UpdateConfiguration()
{
return AndroidEnv::Get()->UpdateConfiguration();
}
////////////////////////////////////////////////////////////////
const char* GetAppPrivateStoragePath()
{
return AndroidEnv::Get()->GetAppPrivateStoragePath();
}
////////////////////////////////////////////////////////////////
const char* GetAppPublicStoragePath()
{
return AndroidEnv::Get()->GetAppPublicStoragePath();
}
////////////////////////////////////////////////////////////////
const char* GetObbStoragePath()
{
return AndroidEnv::Get()->GetObbStoragePath();
}
////////////////////////////////////////////////////////////////
const char* GetPackageName()
{
return AndroidEnv::Get()->GetPackageName();
}
////////////////////////////////////////////////////////////////
int GetAppVersionCode()
{
return AndroidEnv::Get()->GetAppVersionCode();
}
////////////////////////////////////////////////////////////////
const char* GetObbFileName(bool mainFile)
{
return AndroidEnv::Get()->GetObbFileName(mainFile);
}
////////////////////////////////////////////////////////////////
bool IsApkPath(const char* filePath)
{
return AZ::IO::PathView(filePath).IsRelativeTo(AZ::IO::PathView(GetApkAssetsPrefix()));
}
////////////////////////////////////////////////////////////////
AZ::IO::FixedMaxPath StripApkPrefix(const char* filePath)
{
constexpr AZ::IO::PathView apkPrefixView = GetApkAssetsPrefix();
return AZ::IO::PathView(filePath).LexicallyProximate(apkPrefixView);
}
////////////////////////////////////////////////////////////////
const char* FindAssetsDirectory()
{
#if defined(LY_NO_ASSETS)
// The TestRunner app which runs unit tests does not have any assets.
return GetAppPublicStoragePath();
#endif
#if !defined(_RELEASE)
// first check to see if they are in public storage (application specific)
const char* publicAppStorage = GetAppPublicStoragePath();
OSString path = OSString::format("%s/engine.json", publicAppStorage);
AZ_TracePrintf("Android::Utils", "Searching for %s\n", path.c_str());
FILE* f = fopen(path.c_str(), "r");
if (f != nullptr)
{
fclose(f);
return publicAppStorage;
}
#endif // !defined(_RELEASE)
// if they aren't in public storage, they are in private storage (APK)
AAssetManager* mgr = GetAssetManager();
if (mgr)
{
AAsset* asset = AAssetManager_open(mgr, "engine.json", AASSET_MODE_UNKNOWN);
if (asset)
{
AAsset_close(asset);
return GetApkAssetsPrefix();
}
}
AZ_Assert(false, "Failed to locate the engine.json path");
return nullptr;
}
////////////////////////////////////////////////////////////////
void ShowSplashScreen()
{
JNI::Internal::Object<AZ::OSAllocator> activity(GetActivityClassRef(), GetActivityRef());
activity.RegisterMethod("ShowSplashScreen", "()V");
activity.InvokeVoidMethod("ShowSplashScreen");
}
////////////////////////////////////////////////////////////////
void DismissSplashScreen()
{
JNI::Internal::Object<AZ::OSAllocator> activity(GetActivityClassRef(), GetActivityRef());
activity.RegisterMethod("DismissSplashScreen", "()V");
activity.InvokeVoidMethod("DismissSplashScreen");
}
////////////////////////////////////////////////////////////////
ANativeWindow* GetWindow()
{
return AndroidEnv::Get()->GetWindow();
}
////////////////////////////////////////////////////////////////
bool GetWindowSize(int& widthPixels, int& heightPixels)
{
ANativeWindow* window = GetWindow();
if (window)
{
widthPixels = ANativeWindow_getWidth(window);
heightPixels = ANativeWindow_getHeight(window);
// should an error occur from the above functions a negative value will be returned
return (widthPixels > 0 && heightPixels > 0);
}
return false;
}
////////////////////////////////////////////////////////////////
void SetLoadFilesToMemory(const char* fileNames)
{
APKFileHandler::SetLoadFilesToMemory(fileNames);
}
}
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/std/string/string.h>
#include <jni.h>
#include <android/asset_manager.h>
#include <android/configuration.h>
#include <android/native_window.h>
namespace AZ
{
namespace Android
{
namespace Utils
{
//! Request the global reference to the activity class
jclass GetActivityClassRef();
//! Request the global reference to the activity instance
jobject GetActivityRef();
//! Get the global pointer to the Android asset manager, which is used for APK file i/o.
AAssetManager* GetAssetManager();
//! Get the global pointer to the device/application configuration,
AConfiguration* GetConfiguration();
//! If the AndroidEnv owns the native configuration, it will be updated with the latest configuration
//! information, otherwise nothing will happen.
void UpdateConfiguration();
//! Get the hidden internal storage, typically this is where the application is installed
//! on the device.
//! e.g. /data/data/<package_name>/files
const char* GetAppPrivateStoragePath();
//! Get the application specific directory for public public storage.
//! e.g. <public_storage>/Android/data/<package_name>/files
const char* GetAppPublicStoragePath();
//! Get the application specific directory for obb files.
//! e.g. <public_storage>/Android/obb/<package_name>/files
const char* GetObbStoragePath();
//! Get the dot separated package name for the current application.
//! e.g. org.o3de.samples for SamplesProject
const char* GetPackageName();
//! Get the app version code (android:versionCode in the manifest).
int GetAppVersionCode();
//! Get the filename of the obb. This doesn't include the path to the obb folder.
const char* GetObbFileName(bool mainFile);
//! Check to see if the path is prefixed with "/APK"
bool IsApkPath(const char* filePath);
//! Will first check to verify the argument is an apk asset path and if so
//! will strip the prefix from the path.
//! \return The pointer position of the relative asset path
AZ::IO::FixedMaxPath StripApkPrefix(const char* filePath);
//! Searches application storage and the APK for engine.json. Will return nullptr
//! if engine.json is not found.
const char* FindAssetsDirectory();
//! Calls into Java to show the splash screen on the main UI (Java) thread
void ShowSplashScreen();
//! Calls into Java to dismiss the splash screen on the main UI (Java) thread
void DismissSplashScreen();
//! Get the native android window
ANativeWindow* GetWindow();
//! Query the pixel dimensions of the window
//! \param[out] widthPixels Returns the pixel width of the window
//! \param[out] heightPixels Returns the pixel height of the window
//! \return True if successful, False otherwise
bool GetWindowSize(int& widthPixels, int& heightPixels);
//! Set the filenames for files to be loaded to memory
void SetLoadFilesToMemory(const char* fileNames);
}
}
}
@@ -62,25 +62,25 @@ set(FILES
../Common/UnixLike/AzCore/std/time_UnixLike.cpp
AzCore/Utils/Utils_Android.cpp
../Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp
../../AzCore/Android/AndroidEnv.cpp
../../AzCore/Android/AndroidEnv.h
../../AzCore/Android/APKFileHandler.cpp
../../AzCore/Android/APKFileHandler.h
../../AzCore/Android/ApiLevel.h
../../AzCore/Android/Utils.cpp
../../AzCore/Android/Utils.h
../../AzCore/Android/JNI/JNI.cpp
../../AzCore/Android/JNI/JNI.h
../../AzCore/Android/JNI/Object.h
../../AzCore/Android/JNI/Object_fwd.h
../../AzCore/Android/JNI/scoped_ref.h
../../AzCore/Android/JNI/shared_ref.h
../../AzCore/Android/JNI/Signature.h
../../AzCore/Android/JNI/Internal/ClassName.h
../../AzCore/Android/JNI/Internal/JStringUtils.h
../../AzCore/Android/JNI/Internal/JStringUtils_impl.h
../../AzCore/Android/JNI/Internal/Object_impl.h
../../AzCore/Android/JNI/Internal/Signature_impl.h
AzCore/Android/AndroidEnv.cpp
AzCore/Android/AndroidEnv.h
AzCore/Android/APKFileHandler.cpp
AzCore/Android/APKFileHandler.h
AzCore/Android/ApiLevel.h
AzCore/Android/Utils.cpp
AzCore/Android/Utils.h
AzCore/Android/JNI/JNI.cpp
AzCore/Android/JNI/JNI.h
AzCore/Android/JNI/Object.h
AzCore/Android/JNI/Object_fwd.h
AzCore/Android/JNI/scoped_ref.h
AzCore/Android/JNI/shared_ref.h
AzCore/Android/JNI/Signature.h
AzCore/Android/JNI/Internal/ClassName.h
AzCore/Android/JNI/Internal/JStringUtils.h
AzCore/Android/JNI/Internal/JStringUtils_impl.h
AzCore/Android/JNI/Internal/Object_impl.h
AzCore/Android/JNI/Internal/Signature_impl.h
AzCore/Debug/Profiler_Platform.inl
AzCore/Debug/Profiler_Android.inl
)