Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,78 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
namespace AzFramework
{
class AndroidLifecycleEvents
: public AZ::EBusTraits
{
public:
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~AndroidLifecycleEvents() {}
using Bus = AZ::EBus<AndroidLifecycleEvents>;
virtual void OnLostFocus() {} // Constrain
virtual void OnGainedFocus() {} // Unconstrain
// Android can also generate onStop/onRestart events which (at first glance) would appear to match better with our
// suspend/resume events. However there is no guarantee of either these methods being called, and the behavior of
// onPause/onResume more closely matches that of suspend/resume on our other platforms.
virtual void OnPause() {} // Suspend
virtual void OnResume() {} // Resume
virtual void OnDestroy() {} // Terminate
virtual void OnLowMemory() {} // Low memory
virtual void OnWindowInit() {} // Application window was created
virtual void OnWindowDestroy() {} // Application window is going to be destroyed
virtual void OnWindowRedrawNeeded() {} // Application window needs to be redrawn. This is called after a window resize occurs as well. So, we can(reliably) use this for handling orientation changes.
};
class AndroidEventDispatcher
{
public:
virtual ~AndroidEventDispatcher() = default;
virtual void PumpAllEvents() = 0;
virtual void PumpEventLoopOnce() = 0;
};
class AndroidAppRequests
: public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<AndroidAppRequests>;
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~AndroidAppRequests() {}
//! Sets the Android event dispatcher required to pumping the event loop
virtual void SetEventDispatcher(AndroidEventDispatcher* eventDispatcher) = 0;
//! Requests permissions at runtime
virtual bool RequestPermission(const AZStd::string& permission, const AZStd::string& rationale) = 0;
};
} // namespace AzFramework
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/API/ApplicationAPI_Android.h>
@@ -0,0 +1,266 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/JNI/scoped_ref.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/std/parallel/condition_variable.h>
#include <android/input.h>
#include <android/keycodes.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
namespace
{
class PermissionRequestResultNotification
: public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<PermissionRequestResultNotification>;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~PermissionRequestResultNotification() = default;
virtual void OnRequestPermissionsResult(bool granted) { AZ_UNUSED(granted); };
};
void JNI_OnRequestPermissionsResult(JNIEnv* env, jobject obj, bool granted)
{
AzFramework::PermissionRequestResultNotification::Bus::Broadcast(&AzFramework::PermissionRequestResultNotification::OnRequestPermissionsResult, granted);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationAndroid
: public Application::Implementation
, public AndroidLifecycleEvents::Bus::Handler
, public AndroidAppRequests::Bus::Handler
, public PermissionRequestResultNotification::Bus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
AZ_CLASS_ALLOCATOR(ApplicationAndroid, AZ::SystemAllocator, 0);
ApplicationAndroid();
~ApplicationAndroid() override;
////////////////////////////////////////////////////////////////////////////////////////////
// AndroidAppRequests
void SetEventDispatcher(AndroidEventDispatcher* eventDispatcher) override;
bool RequestPermission(const AZStd::string& permission, const AZStd::string& rationale) override;
////////////////////////////////////////////////////////////////////////////////////////////
// AndroidLifecycleEvents
void OnLostFocus() override;
void OnGainedFocus() override;
void OnPause() override;
void OnResume() override;
void OnDestroy() override;
void OnLowMemory() override;
void OnWindowInit() override;
void OnWindowDestroy() override;
void OnWindowRedrawNeeded() override;
////////////////////////////////////////////////////////////////////////////////////////////
// PermissionRequestResultNotification
void OnRequestPermissionsResult(bool granted) override;
////////////////////////////////////////////////////////////////////////////////////////////
// Application::Implementation
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
private:
AndroidEventDispatcher* m_eventDispatcher;
ApplicationLifecycleEvents::Event m_lastEvent;
AZStd::atomic<bool> m_requestResponseReceived;
AZStd::unique_ptr<AZ::Android::JNI::Object> m_lumberyardActivity;
AZStd::condition_variable m_conditionVar;
AZStd::mutex m_mutex;
bool m_permissionGranted;
};
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
return aznew ApplicationAndroid();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationAndroid::ApplicationAndroid()
: m_eventDispatcher(nullptr)
, m_lastEvent(ApplicationLifecycleEvents::Event::None)
{
m_lumberyardActivity.reset(aznew AZ::Android::JNI::Object(AZ::Android::Utils::GetActivityClassRef(), AZ::Android::Utils::GetActivityRef()));
m_lumberyardActivity->RegisterNativeMethods(
{ { "nativeOnRequestPermissionsResult", "(Z)V", (void*)JNI_OnRequestPermissionsResult } }
);
m_lumberyardActivity->RegisterMethod("RequestPermission", "(Ljava/lang/String;Ljava/lang/String;)V");
AndroidLifecycleEvents::Bus::Handler::BusConnect();
AndroidAppRequests::Bus::Handler::BusConnect();
PermissionRequestResultNotification::Bus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationAndroid::~ApplicationAndroid()
{
m_lumberyardActivity.reset();
PermissionRequestResultNotification::Bus::Handler::BusDisconnect();
AndroidAppRequests::Bus::Handler::BusDisconnect();
AndroidLifecycleEvents::Bus::Handler::BusDisconnect();
m_conditionVar.notify_all();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::SetEventDispatcher(AndroidEventDispatcher* eventDispatcher)
{
AZ_Assert(!m_eventDispatcher, "Duplicate call to setting the Android event dispatcher!");
m_eventDispatcher = eventDispatcher;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool ApplicationAndroid::RequestPermission(const AZStd::string& permission, const AZStd::string& rationale)
{
AZ::Android::JNI::scoped_ref<jstring> permissionString(AZ::Android::JNI::ConvertStringToJstring(permission));
AZ::Android::JNI::scoped_ref<jstring> permissionRationaleString(AZ::Android::JNI::ConvertStringToJstring(rationale));
m_requestResponseReceived = false;
m_permissionGranted = false;
m_lumberyardActivity->InvokeVoidMethod("RequestPermission", permissionString.get(), permissionRationaleString.get());
bool looperExistsForThread = (ALooper_forThread() != nullptr);
// Make sure a looper exists for thread before pumping events.
// For threads that are not the main thread, we just block and
// the events will be pumped by the main thread and unblock this
// thread when the user responds.
if (looperExistsForThread)
{
while (!m_requestResponseReceived.load())
{
PumpSystemEventLoopOnce();
}
}
else
{
AZStd::unique_lock<AZStd::mutex> lock(m_mutex);
m_conditionVar.wait(lock, [&] { return m_requestResponseReceived.load(); });
}
return m_permissionGranted;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnLostFocus()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationConstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Constrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnGainedFocus()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationUnconstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Unconstrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnPause()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationSuspended, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Suspend;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnResume()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationResumed, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Resume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnDestroy()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnMobileApplicationWillTerminate);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnLowMemory()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnMobileApplicationLowMemoryWarning);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnWindowInit()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationWindowCreated);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnWindowDestroy()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationWindowDestroy);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnWindowRedrawNeeded()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationWindowRedrawNeeded);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnRequestPermissionsResult(bool granted)
{
m_permissionGranted = granted;
m_requestResponseReceived = true;
m_conditionVar.notify_all();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::PumpSystemEventLoopOnce()
{
AZ_ErrorOnce("ApplicationAndroid", m_eventDispatcher, "The Android event dispatcher is not valid, unable to pump the event loop properly.");
if (m_eventDispatcher)
{
m_eventDispatcher->PumpEventLoopOnce();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::PumpSystemEventLoopUntilEmpty()
{
AZ_ErrorOnce("ApplicationAndroid", m_eventDispatcher, "The Android event dispatcher is not valid, unable to pump the event loop properly.");
if (m_eventDispatcher)
{
m_eventDispatcher->PumpAllEvents();
}
}
} // namespace AzFramework
@@ -0,0 +1,16 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define STREAM_CACHE_DEFAULT 0
#define FRONTEND_SHADER_CACHE_DEFAULT 0
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Archive/ArchiveVars_Android.h>
@@ -0,0 +1,18 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORK_AWS_ENABLE_TCP_KEEP_ALIVE_SUPPORTED (true)
#define AZ_TRAIT_AZFRAMEWORK_BOOTSTRAP_CFG_CURRENT_PLATFORM "android"
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 1
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 0
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/AzFramework_Traits_Android.h>
@@ -0,0 +1,256 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <dirent.h>
#include <sys/stat.h>
#include <fstream>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/functional.h>
#include <android/api-level.h>
#if __ANDROID_API__ == 19
// The following were apparently introduced in API 21, however in earlier versions of the
// platform specific headers they were defines. In the move to unified headers, the following
// defines were removed from stat.h
#ifndef stat64
#define stat64 stat
#endif
#ifndef fstat64
#define fstat64 fstat
#endif
#ifndef lstat64
#define lstat64 lstat
#endif
#endif // __ANDROID_API__ == 19
namespace AZ
{
namespace IO
{
bool LocalFileIO::IsDirectory(const char* filePath)
{
ANDROID_IO_PROFILE_SECTION_ARGS("IsDir:%s", filePath);
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(resolvedPath))
{
return AZ::Android::APKFileHandler::IsDirectory(AZ::Android::Utils::StripApkPrefix(resolvedPath));
}
struct stat result;
if (stat(resolvedPath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourcePath[AZ_MAX_PATH_LEN];
char resolvedDestPath[AZ_MAX_PATH_LEN];
ResolvePath(sourceFilePath, resolvedSourcePath, AZ_MAX_PATH_LEN);
ResolvePath(destinationFilePath, resolvedDestPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(sourceFilePath) || AZ::Android::Utils::IsApkPath(destinationFilePath))
{
return ResultCode::Error; //copy from APK still to be implemented (and of course you can't copy to an APK)
}
// note: Android, without root, has no reliable way to update modtimes
// on files on internal storage - this includes "emulated" SDCARD storage
// that actually resides on internal, and thus we can't depend on modtimes.
{
std::ifstream sourceFile(resolvedSourcePath, std::ios::binary);
if (sourceFile.fail())
{
return ResultCode::Error;
}
std::ofstream destFile(resolvedDestPath, std::ios::binary);
if (destFile.fail())
{
return ResultCode::Error;
}
destFile << sourceFile.rdbuf();
}
return ResultCode::Success;
}
Result LocalFileIO::FindFiles(const char* filePath, const char* filter, LocalFileIO::FindFilesCallbackType callback)
{
ANDROID_IO_PROFILE_SECTION_ARGS("FindFiles:%s", filePath);
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
AZ::OSString pathWithoutSlash = RemoveTrailingSlash(resolvedPath);
bool isInAPK = AZ::Android::Utils::IsApkPath(pathWithoutSlash.c_str());
if (isInAPK)
{
AZ::OSString strippedPath = AZ::Android::Utils::StripApkPrefix(pathWithoutSlash.c_str());
char tempBuffer[AZ_MAX_PATH_LEN] = {0};
AZ::Android::APKFileHandler::ParseDirectory(strippedPath.c_str(), [&](const char* name)
{
AZStd::string_view filenameView = name;
// Skip over the current and parent directory paths
if (filenameView != "." && filenameView != ".." && NameMatchesFilter(name, filter))
{
AZ::OSString foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += name;
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
if (!callback(tempBuffer))
{
return false;
}
}
return true;
});
}
else
{
DIR* dir = opendir(pathWithoutSlash.c_str());
if (dir != nullptr)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
// clear the errno state so we can distinguish between errors and end of stream
errno = 0;
struct dirent* entry = readdir(dir);
// List all the other files in the directory.
while (entry != nullptr)
{
AZStd::string_view filenameView = entry->d_name;
// Skip over the current and parent directory paths
if (filenameView != "." && filenameView != ".." && NameMatchesFilter(entry->d_name, filter))
{
AZ::OSString foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += entry->d_name;
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
if (!callback(tempBuffer))
{
break;
}
}
entry = readdir(dir);
}
if (errno != 0)
{
closedir(dir);
return ResultCode::Error;
}
closedir(dir);
return ResultCode::Success;
}
else
{
return ResultCode::Error;
}
}
return ResultCode::Success;
}
Result LocalFileIO::CreatePath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(resolvedPath))
{
return ResultCode::Error; //you can't write to the APK
}
// create all paths up to that directory.
// its not an error if the path exists.
if ((Exists(resolvedPath)) && (!IsDirectory(resolvedPath)))
{
return ResultCode::Error; // that path exists, but is not a directory.
}
// make directories from bottom to top.
AZ::OSString pathBuffer;
size_t pathLength = strlen(resolvedPath);
pathBuffer.reserve(pathLength);
for (size_t pathPos = 0; pathPos < pathLength; ++pathPos)
{
if ((resolvedPath[pathPos] == '\\') || (resolvedPath[pathPos] == '/'))
{
if (pathPos > 0)
{
mkdir(pathBuffer.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (!IsDirectory(pathBuffer.c_str()))
{
return ResultCode::Error;
}
}
}
pathBuffer.push_back(resolvedPath[pathPos]);
}
mkdir(pathBuffer.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
return IsDirectory(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
return path && path[0] == '/';
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
if (AZ::Android::Utils::IsApkPath(path))
{
azstrncpy(absolutePath, maxLength, path, maxLength);
return true;
}
AZ_Assert(maxLength >= AZ_MAX_PATH_LEN, "Path length is larger than AZ_MAX_PATH_LEN");
if (!IsAbsolutePath(path))
{
// note that realpath fails if the path does not exist and actually changes the return value
// to be the actual place that FAILED, which we don't want.
// if we fail, we'd prefer to fall through and at least use the original path.
const char* result = realpath(path, absolutePath);
if (result)
{
return true;
}
}
azstrcpy(absolutePath, maxLength, path);
return IsAbsolutePath(absolutePath);
}
} // namespace IO
}//namespace AZ
@@ -0,0 +1,66 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
struct AInputEvent;
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for raw android input as broadcast by the system. Applications
//! that want android events to be processed by the AzFramework input system must broadcast all
//! input events received by the native input handle, which is the lowest level we can get input.
//!
//! It's possible to receive multiple events per index (finger) per frame, and it is likely that
//! android input events will not be dispatched from the main thread, so care should be taken to
//! ensure thread safety when implementing event handlers that connect to this android event bus.
//!
//! This EBus is intended primarily for the AzFramework input system to process raw input events.
//! Most systems that need to process input should use the generic AzFramework input interfaces,
//! but if necessary it is perfectly valid to connect directly to this EBus for android events.
class RawInputNotificationsAndroid : public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: raw input notifications are addressed to a single address
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: raw input notifications can be handled by multiple listeners
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
virtual ~RawInputNotificationsAndroid() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw input events (assumed to be dispatched on any thread)
//! \param[in] rawInputEvent The raw input event data
virtual void OnRawInputEvent(const AInputEvent* /*rawInputEvent*/) {}
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw text events (assumed to be dispatched on any thread)
//! \param[in] charsModifiedUTF8 The raw chars (encoded using modified UTF-8)
virtual void OnRawInputTextEvent(const char* /*charsModifiedUTF8*/) {}
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw virtual keyboard events (assumed to be dispatched on any thread)
//! \param[in] keyCode The key code of the virtual keyboard event
//! \param[in] keyAction The key action of the virtual keyboard event
virtual void OnRawInputVirtualKeyboardEvent(int /*keyCode*/, int /*keyAction*/) {}
};
using RawInputNotificationBusAndroid = AZ::EBus<RawInputNotificationsAndroid>;
} // namespace AzFramework
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Android.h>
@@ -0,0 +1,107 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for android game-pad input devices
class InputDeviceGamepadAndroid : public InputDeviceGamepad::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceGamepadAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceGamepadAndroid(InputDeviceGamepad& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceGamepadAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::SetVibration
void SetVibration(float leftMotorSpeedNormalized, float rightMotorSpeedNormalized) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::u32 InputDeviceGamepad::GetMaxSupportedGamepads()
{
// ToDo: Return the maximum number of supported gamepads
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepad::Implementation* InputDeviceGamepad::Implementation::Create(
InputDeviceGamepad& inputDevice)
{
return aznew InputDeviceGamepadAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadAndroid::InputDeviceGamepadAndroid(InputDeviceGamepad& inputDevice)
: InputDeviceGamepad::Implementation(inputDevice)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadAndroid::~InputDeviceGamepadAndroid()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceGamepadAndroid::IsConnected() const
{
// ToDo: Figure out how/whether we can determine the availability of this game-pad,
// and implement this function (along with dispatching connect/disconnect events)
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadAndroid::SetVibration(float leftMotorSpeedNormalized,
float rightMotorSpeedNormalized)
{
// ToDo: Implement force-feedback if supported?
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadAndroid::TickInputDevice()
{
// ToDo: Process raw game-pad input and update input channels
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// ToDo: Process raw game-pad input events
}
} // namespace AzFramework
@@ -0,0 +1,447 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/Utils.h>
#include <android/input.h>
namespace
{
using namespace AzFramework;
// Table of key ids indexed by their android key code
const AZStd::array<const InputChannelId*, 285> InputChannelIdByKeyCodeTable =
{{
nullptr, // 0 AKEYCODE_UNKNOWN
nullptr, // 1 AKEYCODE_SOFT_LEFT
nullptr, // 2 AKEYCODE_SOFT_RIGHT
nullptr, // 3 AKEYCODE_HOME
nullptr, // 4 AKEYCODE_BACK
nullptr, // 5 AKEYCODE_CALL
nullptr, // 6 AKEYCODE_ENDCALL
&InputDeviceKeyboard::Key::Alphanumeric0, // 7 AKEYCODE_0
&InputDeviceKeyboard::Key::Alphanumeric1, // 8 AKEYCODE_1
&InputDeviceKeyboard::Key::Alphanumeric2, // 9 AKEYCODE_2
&InputDeviceKeyboard::Key::Alphanumeric3, // 10 AKEYCODE_3
&InputDeviceKeyboard::Key::Alphanumeric4, // 11 AKEYCODE_4
&InputDeviceKeyboard::Key::Alphanumeric5, // 12 AKEYCODE_5
&InputDeviceKeyboard::Key::Alphanumeric6, // 13 AKEYCODE_6
&InputDeviceKeyboard::Key::Alphanumeric7, // 14 AKEYCODE_7
&InputDeviceKeyboard::Key::Alphanumeric8, // 15 AKEYCODE_8
&InputDeviceKeyboard::Key::Alphanumeric9, // 16 AKEYCODE_9
nullptr, // 17 AKEYCODE_STAR
nullptr, // 18 AKEYCODE_POUND
&InputDeviceKeyboard::Key::NavigationArrowUp, // 19 AKEYCODE_DPAD_UP
&InputDeviceKeyboard::Key::NavigationArrowDown, // 20 AKEYCODE_DPAD_DOWN
&InputDeviceKeyboard::Key::NavigationArrowLeft, // 21 AKEYCODE_DPAD_LEFT
&InputDeviceKeyboard::Key::NavigationArrowRight, // 22 AKEYCODE_DPAD_RIGHT
nullptr, // 23 AKEYCODE_DPAD_CENTER
nullptr, // 24 AKEYCODE_VOLUME_UP
nullptr, // 25 AKEYCODE_VOLUME_DOWN
nullptr, // 26 AKEYCODE_POWER
nullptr, // 27 AKEYCODE_CAMERA
nullptr, // 28 AKEYCODE_CLEAR
&InputDeviceKeyboard::Key::AlphanumericA, // 29 AKEYCODE_A
&InputDeviceKeyboard::Key::AlphanumericB, // 30 AKEYCODE_B
&InputDeviceKeyboard::Key::AlphanumericC, // 31 AKEYCODE_C
&InputDeviceKeyboard::Key::AlphanumericD, // 32 AKEYCODE_D
&InputDeviceKeyboard::Key::AlphanumericE, // 33 AKEYCODE_E
&InputDeviceKeyboard::Key::AlphanumericF, // 34 AKEYCODE_F
&InputDeviceKeyboard::Key::AlphanumericG, // 35 AKEYCODE_G
&InputDeviceKeyboard::Key::AlphanumericH, // 36 AKEYCODE_H
&InputDeviceKeyboard::Key::AlphanumericI, // 37 AKEYCODE_I
&InputDeviceKeyboard::Key::AlphanumericJ, // 38 AKEYCODE_J
&InputDeviceKeyboard::Key::AlphanumericK, // 39 AKEYCODE_K
&InputDeviceKeyboard::Key::AlphanumericL, // 40 AKEYCODE_L
&InputDeviceKeyboard::Key::AlphanumericM, // 41 AKEYCODE_M
&InputDeviceKeyboard::Key::AlphanumericN, // 42 AKEYCODE_N
&InputDeviceKeyboard::Key::AlphanumericO, // 43 AKEYCODE_O
&InputDeviceKeyboard::Key::AlphanumericP, // 44 AKEYCODE_P
&InputDeviceKeyboard::Key::AlphanumericQ, // 45 AKEYCODE_Q
&InputDeviceKeyboard::Key::AlphanumericR, // 46 AKEYCODE_R
&InputDeviceKeyboard::Key::AlphanumericS, // 47 AKEYCODE_S
&InputDeviceKeyboard::Key::AlphanumericT, // 48 AKEYCODE_T
&InputDeviceKeyboard::Key::AlphanumericU, // 49 AKEYCODE_U
&InputDeviceKeyboard::Key::AlphanumericV, // 50 AKEYCODE_V
&InputDeviceKeyboard::Key::AlphanumericW, // 51 AKEYCODE_W
&InputDeviceKeyboard::Key::AlphanumericX, // 52 AKEYCODE_X
&InputDeviceKeyboard::Key::AlphanumericY, // 53 AKEYCODE_Y
&InputDeviceKeyboard::Key::AlphanumericZ, // 54 AKEYCODE_Z
&InputDeviceKeyboard::Key::PunctuationComma, // 55 AKEYCODE_COMMA
&InputDeviceKeyboard::Key::PunctuationPeriod, // 56 AKEYCODE_PERIOD
&InputDeviceKeyboard::Key::ModifierAltL, // 57 AKEYCODE_ALT_LEFT
&InputDeviceKeyboard::Key::ModifierAltR, // 58 AKEYCODE_ALT_RIGHT
&InputDeviceKeyboard::Key::ModifierShiftL, // 59 AKEYCODE_SHIFT_LEFT
&InputDeviceKeyboard::Key::ModifierShiftR, // 60 AKEYCODE_SHIFT_RIGHT
&InputDeviceKeyboard::Key::EditTab, // 61 AKEYCODE_TAB
&InputDeviceKeyboard::Key::EditSpace, // 62 AKEYCODE_SPACE
nullptr, // 63 AKEYCODE_SYM
nullptr, // 64 AKEYCODE_EXPLORER
nullptr, // 65 AKEYCODE_ENVELOPE
&InputDeviceKeyboard::Key::EditEnter, // 66 AKEYCODE_ENTER
&InputDeviceKeyboard::Key::EditBackspace, // 67 AKEYCODE_DEL
&InputDeviceKeyboard::Key::PunctuationTilde, // 68 AKEYCODE_GRAVE
&InputDeviceKeyboard::Key::PunctuationHyphen, // 69 AKEYCODE_MINUS
&InputDeviceKeyboard::Key::PunctuationEquals, // 70 AKEYCODE_EQUALS
&InputDeviceKeyboard::Key::PunctuationBracketL, // 71 AKEYCODE_LEFT_BRACKET
&InputDeviceKeyboard::Key::PunctuationBracketR, // 72 AKEYCODE_RIGHT_BRACKET
&InputDeviceKeyboard::Key::PunctuationBackslash, // 73 AKEYCODE_BACKSLASH
&InputDeviceKeyboard::Key::PunctuationSemicolon, // 74 AKEYCODE_SEMICOLON
&InputDeviceKeyboard::Key::PunctuationApostrophe, // 75 AKEYCODE_APOSTROPHE
&InputDeviceKeyboard::Key::PunctuationSlash, // 76 AKEYCODE_SLASH
nullptr, // 77 AKEYCODE_AT
nullptr, // 78 AKEYCODE_NUM
nullptr, // 79 AKEYCODE_HEADSETHOOK
nullptr, // 80 AKEYCODE_FOCUS
nullptr, // 81 AKEYCODE_PLUS
nullptr, // 82 AKEYCODE_MENU
nullptr, // 83 AKEYCODE_NOTIFICATION
nullptr, // 84 AKEYCODE_SEARCH
nullptr, // 85 AKEYCODE_MEDIA_PLAY_PAUSE
nullptr, // 86 AKEYCODE_MEDIA_STOP
nullptr, // 87 AKEYCODE_MEDIA_NEXT
nullptr, // 88 AKEYCODE_MEDIA_PREVIOUS
nullptr, // 89 AKEYCODE_MEDIA_REWIND
nullptr, // 90 AKEYCODE_MEDIA_FAST_FORWARD
nullptr, // 91 AKEYCODE_MUTE
&InputDeviceKeyboard::Key::NavigationPageUp, // 92 AKEYCODE_PAGE_UP
&InputDeviceKeyboard::Key::NavigationPageDown, // 93 AKEYCODE_PAGE_DOWN
nullptr, // 94 AKEYCODE_PICTSYMBOLS
nullptr, // 95 AKEYCODE_SWITCH_CHARSET
nullptr, // 96 AKEYCODE_BUTTON_A
nullptr, // 97 AKEYCODE_BUTTON_B
nullptr, // 98 AKEYCODE_BUTTON_C
nullptr, // 99 AKEYCODE_BUTTON_X
nullptr, // 100 AKEYCODE_BUTTON_Y
nullptr, // 101 AKEYCODE_BUTTON_Z
nullptr, // 102 AKEYCODE_BUTTON_L1
nullptr, // 103 AKEYCODE_BUTTON_R1
nullptr, // 104 AKEYCODE_BUTTON_L2
nullptr, // 105 AKEYCODE_BUTTON_R2
nullptr, // 106 AKEYCODE_BUTTON_THUMBL
nullptr, // 107 AKEYCODE_BUTTON_THUMBR
nullptr, // 108 AKEYCODE_BUTTON_START
nullptr, // 109 AKEYCODE_BUTTON_SELECT
nullptr, // 110 AKEYCODE_BUTTON_MODE
&InputDeviceKeyboard::Key::Escape, // 111 AKEYCODE_ESCAPE
&InputDeviceKeyboard::Key::NavigationDelete, // 112 AKEYCODE_FORWARD_DEL
&InputDeviceKeyboard::Key::ModifierCtrlL, // 113 AKEYCODE_CTRL_LEFT
&InputDeviceKeyboard::Key::ModifierCtrlR, // 114 AKEYCODE_CTRL_RIGHT
&InputDeviceKeyboard::Key::EditCapsLock, // 115 AKEYCODE_CAPS_LOCK
&InputDeviceKeyboard::Key::WindowsSystemScrollLock, // 116 AKEYCODE_SCROLL_LOCK
nullptr, // 117 AKEYCODE_META_LEFT
nullptr, // 118 AKEYCODE_META_RIGHT
nullptr, // 119 AKEYCODE_FUNCTION
nullptr, // 120 AKEYCODE_SYSRQ
nullptr, // 121 AKEYCODE_BREAK
&InputDeviceKeyboard::Key::NavigationHome, // 122 AKEYCODE_MOVE_HOME
&InputDeviceKeyboard::Key::NavigationEnd, // 123 AKEYCODE_MOVE_END
&InputDeviceKeyboard::Key::NavigationInsert, // 124 AKEYCODE_INSERT
nullptr, // 125 AKEYCODE_FORWARD
nullptr, // 126 AKEYCODE_MEDIA_PLAY
nullptr, // 127 AKEYCODE_MEDIA_PAUSE
nullptr, // 128 AKEYCODE_MEDIA_CLOSE
nullptr, // 129 AKEYCODE_MEDIA_EJECT
nullptr, // 130 AKEYCODE_MEDIA_RECORD
&InputDeviceKeyboard::Key::Function01, // 131 AKEYCODE_F1
&InputDeviceKeyboard::Key::Function02, // 132 AKEYCODE_F2
&InputDeviceKeyboard::Key::Function03, // 133 AKEYCODE_F3
&InputDeviceKeyboard::Key::Function04, // 134 AKEYCODE_F4
&InputDeviceKeyboard::Key::Function05, // 135 AKEYCODE_F5
&InputDeviceKeyboard::Key::Function06, // 136 AKEYCODE_F6
&InputDeviceKeyboard::Key::Function07, // 137 AKEYCODE_F7
&InputDeviceKeyboard::Key::Function08, // 138 AKEYCODE_F8
&InputDeviceKeyboard::Key::Function09, // 139 AKEYCODE_F9
&InputDeviceKeyboard::Key::Function10, // 140 AKEYCODE_F10
&InputDeviceKeyboard::Key::Function11, // 141 AKEYCODE_F11
&InputDeviceKeyboard::Key::Function12, // 142 AKEYCODE_F12
&InputDeviceKeyboard::Key::NumLock, // 143 AKEYCODE_NUM_LOCK
&InputDeviceKeyboard::Key::NumPad0, // 144 AKEYCODE_NUMPAD_0
&InputDeviceKeyboard::Key::NumPad1, // 145 AKEYCODE_NUMPAD_1
&InputDeviceKeyboard::Key::NumPad2, // 146 AKEYCODE_NUMPAD_2
&InputDeviceKeyboard::Key::NumPad3, // 147 AKEYCODE_NUMPAD_3
&InputDeviceKeyboard::Key::NumPad4, // 148 AKEYCODE_NUMPAD_4
&InputDeviceKeyboard::Key::NumPad5, // 149 AKEYCODE_NUMPAD_5
&InputDeviceKeyboard::Key::NumPad6, // 150 AKEYCODE_NUMPAD_6
&InputDeviceKeyboard::Key::NumPad7, // 151 AKEYCODE_NUMPAD_7
&InputDeviceKeyboard::Key::NumPad8, // 152 AKEYCODE_NUMPAD_8
&InputDeviceKeyboard::Key::NumPad9, // 153 AKEYCODE_NUMPAD_9
&InputDeviceKeyboard::Key::NumPadDivide, // 154 AKEYCODE_NUMPAD_DIVIDE
&InputDeviceKeyboard::Key::NumPadMultiply, // 155 AKEYCODE_NUMPAD_MULTIPLY
&InputDeviceKeyboard::Key::NumPadSubtract, // 156 AKEYCODE_NUMPAD_SUBTRACT
&InputDeviceKeyboard::Key::NumPadAdd, // 157 AKEYCODE_NUMPAD_ADD
&InputDeviceKeyboard::Key::NumPadDecimal, // 158 AKEYCODE_NUMPAD_DOT
nullptr, // 159 AKEYCODE_NUMPAD_COMMA
&InputDeviceKeyboard::Key::NumPadEnter, // 160 AKEYCODE_NUMPAD_ENTER
nullptr, // 161 AKEYCODE_NUMPAD_EQUALS
nullptr, // 162 AKEYCODE_NUMPAD_LEFT_PAREN
nullptr, // 163 AKEYCODE_NUMPAD_RIGHT_PAREN
nullptr, // 164 AKEYCODE_VOLUME_MUTE
nullptr, // 165 AKEYCODE_INFO
nullptr, // 166 AKEYCODE_CHANNEL_UP
nullptr, // 167 AKEYCODE_CHANNEL_DOWN
nullptr, // 168 AKEYCODE_ZOOM_IN
nullptr, // 169 AKEYCODE_ZOOM_OUT
nullptr, // 170 AKEYCODE_TV
nullptr, // 171 AKEYCODE_WINDOW
nullptr, // 172 AKEYCODE_GUIDE
nullptr, // 173 AKEYCODE_DVR
nullptr, // 174 AKEYCODE_BOOKMARK
nullptr, // 175 AKEYCODE_CAPTIONS
nullptr, // 176 AKEYCODE_SETTINGS
nullptr, // 177 AKEYCODE_TV_POWER
nullptr, // 178 AKEYCODE_TV_INPUT
nullptr, // 179 AKEYCODE_STB_POWER
nullptr, // 180 AKEYCODE_STB_INPUT
nullptr, // 181 AKEYCODE_AVR_POWER
nullptr, // 182 AKEYCODE_AVR_INPUT
nullptr, // 183 AKEYCODE_PROG_RED
nullptr, // 184 AKEYCODE_PROG_GREEN
nullptr, // 185 AKEYCODE_PROG_YELLOW
nullptr, // 186 AKEYCODE_PROG_BLUE
nullptr, // 187 AKEYCODE_APP_SWITCH
nullptr, // 188 AKEYCODE_BUTTON_1
nullptr, // 189 AKEYCODE_BUTTON_2
nullptr, // 190 AKEYCODE_BUTTON_3
nullptr, // 191 AKEYCODE_BUTTON_4
nullptr, // 192 AKEYCODE_BUTTON_5
nullptr, // 193 AKEYCODE_BUTTON_6
nullptr, // 194 AKEYCODE_BUTTON_7
nullptr, // 195 AKEYCODE_BUTTON_8
nullptr, // 196 AKEYCODE_BUTTON_9
nullptr, // 197 AKEYCODE_BUTTON_10
nullptr, // 198 AKEYCODE_BUTTON_11
nullptr, // 199 AKEYCODE_BUTTON_12
nullptr, // 200 AKEYCODE_BUTTON_13
nullptr, // 201 AKEYCODE_BUTTON_14
nullptr, // 202 AKEYCODE_BUTTON_15
nullptr, // 203 AKEYCODE_BUTTON_16
nullptr, // 204 AKEYCODE_LANGUAGE_SWITCH
nullptr, // 205 AKEYCODE_MANNER_MODE
nullptr, // 206 AKEYCODE_3D_MODE
nullptr, // 207 AKEYCODE_CONTACTS
nullptr, // 208 AKEYCODE_CALENDAR
nullptr, // 209 AKEYCODE_MUSIC
nullptr, // 210 AKEYCODE_CALCULATOR
nullptr, // 211 AKEYCODE_ZENKAKU_HANKAKU
nullptr, // 212 AKEYCODE_EISU
nullptr, // 213 AKEYCODE_MUHENKAN
nullptr, // 214 AKEYCODE_HENKAN
nullptr, // 215 AKEYCODE_KATAKANA_HIRAGANA
nullptr, // 216 AKEYCODE_YEN
nullptr, // 217 AKEYCODE_RO
nullptr, // 218 AKEYCODE_KANA
nullptr, // 219 AKEYCODE_ASSIST
nullptr, // 220 AKEYCODE_BRIGHTNESS_DOWN
nullptr, // 221 AKEYCODE_BRIGHTNESS_UP
nullptr, // 222 AKEYCODE_MEDIA_AUDIO_TRACK
nullptr, // 223 AKEYCODE_SLEEP
nullptr, // 224 AKEYCODE_WAKEUP
nullptr, // 225 AKEYCODE_PAIRING
nullptr, // 226 AKEYCODE_MEDIA_TOP_MENU
nullptr, // 227 AKEYCODE_11
nullptr, // 228 AKEYCODE_12
nullptr, // 229 AKEYCODE_LAST_CHANNEL
nullptr, // 230 AKEYCODE_TV_DATA_SERVICE
nullptr, // 231 AKEYCODE_VOICE_ASSIST
nullptr, // 232 AKEYCODE_TV_RADIO_SERVICE
nullptr, // 233 AKEYCODE_TV_TELETEXT
nullptr, // 234 AKEYCODE_TV_NUMBER_ENTRY
nullptr, // 235 AKEYCODE_TV_TERRESTRIAL_ANALOG
nullptr, // 236 AKEYCODE_TV_TERRESTRIAL_DIGITAL
nullptr, // 237 AKEYCODE_TV_SATELLITE
nullptr, // 238 AKEYCODE_TV_SATELLITE_BS
nullptr, // 239 AKEYCODE_TV_SATELLITE_CS
nullptr, // 240 AKEYCODE_TV_SATELLITE_SERVICE
nullptr, // 241 AKEYCODE_TV_NETWORK
nullptr, // 242 AKEYCODE_TV_ANTENNA_CABLE
nullptr, // 243 AKEYCODE_TV_INPUT_HDMI_1
nullptr, // 244 AKEYCODE_TV_INPUT_HDMI_2
nullptr, // 245 AKEYCODE_TV_INPUT_HDMI_3
nullptr, // 246 AKEYCODE_TV_INPUT_HDMI_4
nullptr, // 247 AKEYCODE_TV_INPUT_COMPOSITE_1
nullptr, // 248 AKEYCODE_TV_INPUT_COMPOSITE_2
nullptr, // 249 AKEYCODE_TV_INPUT_COMPONENT_1
nullptr, // 250 AKEYCODE_TV_INPUT_COMPONENT_2
nullptr, // 251 AKEYCODE_TV_INPUT_VGA_1
nullptr, // 252 AKEYCODE_TV_AUDIO_DESCRIPTION
nullptr, // 253 AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP
nullptr, // 254 AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN
nullptr, // 255 AKEYCODE_TV_ZOOM_MODE
nullptr, // 256 AKEYCODE_TV_CONTENTS_MENU
nullptr, // 257 AKEYCODE_TV_MEDIA_CONTEXT_MENU
nullptr, // 258 AKEYCODE_TV_TIMER_PROGRAMMING
nullptr, // 259 AKEYCODE_HELP
nullptr, // 260 AKEYCODE_NAVIGATE_PREVIOUS
nullptr, // 261 AKEYCODE_NAVIGATE_NEXT
nullptr, // 262 AKEYCODE_NAVIGATE_IN
nullptr, // 263 AKEYCODE_NAVIGATE_OUT
nullptr, // 264 AKEYCODE_STEM_PRIMARY
nullptr, // 265 AKEYCODE_STEM_1
nullptr, // 266 AKEYCODE_STEM_2
nullptr, // 267 AKEYCODE_STEM_3
nullptr, // 268 AKEYCODE_DPAD_UP_LEFT
nullptr, // 269 AKEYCODE_DPAD_DOWN_LEFT
nullptr, // 270 AKEYCODE_DPAD_UP_RIGHT
nullptr, // 271 AKEYCODE_DPAD_DOWN_RIGHT
nullptr, // 272 AKEYCODE_MEDIA_SKIP_FORWARD
nullptr, // 273 AKEYCODE_MEDIA_SKIP_BACKWARD
nullptr, // 274 AKEYCODE_MEDIA_STEP_FORWARD
nullptr, // 275 AKEYCODE_MEDIA_STEP_BACKWARD
nullptr, // 276 AKEYCODE_SOFT_SLEEP
nullptr, // 277 AKEYCODE_CUT
nullptr, // 278 AKEYCODE_COPY
nullptr, // 279 AKEYCODE_PASTE
nullptr, // 280 AKEYCODE_SYSTEM_NAVIGATION_UP
nullptr, // 281 AKEYCODE_SYSTEM_NAVIGATION_DOWN
nullptr, // 282 AKEYCODE_SYSTEM_NAVIGATION_LEFT
nullptr, // 283 AKEYCODE_SYSTEM_NAVIGATION_RIGHT
nullptr, // 284 AKEYCODE_ALL_APPS
}};
}
namespace AzFramework
{
//! Platform specific implementation for Android physical keyboard input devices. This
//! includes devices connected through USB or Bluetooth. This input device is responsible
//! for sending key events only, the virtual keyboard is the one responsible for sending
//! text events. Should this behaviour need to be changed, LY-69260 will correct it
class InputDeviceKeyboardAndroid
: public InputDeviceKeyboard::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
AZ_CLASS_ALLOCATOR(InputDeviceKeyboardAndroid, AZ::SystemAllocator, 0);
InputDeviceKeyboardAndroid(InputDeviceKeyboard& inputDevice);
~InputDeviceKeyboardAndroid() override;
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
private:
//! \ref AzFramework::InputDeviceKeyboard::Implementation::IsConnected
bool IsConnected() const override;
//! \ref AzFramework::InputDeviceKeyboard::Implementation::HasTextEntryStarted
bool HasTextEntryStarted() const override;
//! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStart
void TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options) override;
//! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStop
void TextEntryStop() override;
//! \ref AzFramework::InputDeviceKeyboard::Implementation::TickInputDevice
void TickInputDevice() override;
bool m_hasTextEntryStarted = false; //!< Has text entry been started?
};
InputDeviceKeyboardAndroid::InputDeviceKeyboardAndroid(InputDeviceKeyboard& inputDevice)
: InputDeviceKeyboard::Implementation(inputDevice)
, m_hasTextEntryStarted(false)
{
RawInputNotificationBusAndroid::Handler::BusConnect();
}
InputDeviceKeyboardAndroid::~InputDeviceKeyboardAndroid()
{
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
void InputDeviceKeyboardAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// don't bother with physical keyboard events if it's not connected
if (!IsConnected())
{
return;
}
// only care about key events
int eventType = AInputEvent_getType(rawInputEvent);
if (eventType != AINPUT_EVENT_TYPE_KEY)
{
return;
}
int keyCode = AKeyEvent_getKeyCode(rawInputEvent);
const InputChannelId* channelId = (keyCode < InputChannelIdByKeyCodeTable.size()) ?
InputChannelIdByKeyCodeTable[keyCode] : nullptr;
if (channelId)
{
int action = AKeyEvent_getAction(rawInputEvent);
switch (action)
{
case AKEY_EVENT_ACTION_DOWN:
QueueRawKeyEvent(*channelId, true);
break;
case AKEY_EVENT_ACTION_UP:
QueueRawKeyEvent(*channelId, false);
break;
// The multiple event is sent for 2 reasons, both of which we don't care about
// 1. When the keycode is unknown - a complex string event e.g non-standard english characters
// 2. When the keycode is known - there were multiple duplicate key events (unlikely)
case AKEY_EVENT_ACTION_MULTIPLE:
default:
break;
}
}
}
bool InputDeviceKeyboardAndroid::IsConnected() const
{
AConfiguration* config = AZ::Android::Utils::GetConfiguration();
int keyboard = AConfiguration_getKeyboard(config);
return (keyboard != ACONFIGURATION_KEYBOARD_NOKEYS); // nokeys == no physical keyboard connected
}
bool InputDeviceKeyboardAndroid::HasTextEntryStarted() const
{
return m_hasTextEntryStarted;
}
void InputDeviceKeyboardAndroid::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options)
{
m_hasTextEntryStarted = true;
}
void InputDeviceKeyboardAndroid::TextEntryStop()
{
m_hasTextEntryStarted = false;
}
void InputDeviceKeyboardAndroid::TickInputDevice()
{
ProcessRawEventQueues();
}
InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice)
{
return aznew InputDeviceKeyboardAndroid(inputDevice);
}
} // namespace AzFramework
@@ -0,0 +1,447 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Devices/Motion/InputDeviceMotion.h>
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for android motion input devices
class InputDeviceMotionAndroid : public InputDeviceMotion::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceMotionAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceMotionAndroid(InputDeviceMotion& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceMotionAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::RefreshMotionSensors
void RefreshMotionSensors(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the sensor state that is expected by the Java motion manager's RefreshMotionSensors
//! \param[in] wasRequired Was the sensor required when RefreshMotionSensors was last called?
//! \param[in] isRequired Is the sensor now required?
//! \return 1 if the sensor should be enabled, -1 if it should be disabled, 0 for no change
int GetUpdatedSensorState(bool wasRequired, bool isRequired) const;
////////////////////////////////////////////////////////////////////////////////////////////
///@{
//! Check whether a particular sensor is required based on the values of m_enabledChannelIds
//! \return True if the sensor should be enabled, false otherwise
bool IsRequiredAccelerationRaw() const;
bool IsRequiredAccelerationUser() const;
bool IsRequiredAccelerationGravity() const;
bool IsRequiredRotationRateRaw() const;
bool IsRequiredRotationRateUnbiased() const;
bool IsRequiredMagneticFieldRaw() const;
bool IsRequiredMagneticFieldUnbiased() const;
bool IsRequiredOrientationCurrent() const;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Indicies into the motion sensor data returned from the Java code as a packed float array.
//!
//! While we would (ideally) like to encapsulate and return all this data using a Java class,
//! we would then need to 'reach back' through the JNI for each field to access the raw data.
//! Packing all the data into a float array is more efficient, but at the expense of needing
//! to access data using pre-defined array indices, instead of explicitly named class fields.
//!
//! But while explicitly named class fields provide a modicum of safety, lots of boilerplate
//! code is needed, and while support for additional sensor data may be added in the future,
//! the existing set is unlikely to change, so combined with the above mentioned performance
//! consideration this approach to obtaining raw sensor data seems preferable on most fronts.
enum PackedSensorDataIndex
{
Index_AccelerationRawUpdated = 0,
Index_AccelerationRawX = 1,
Index_AccelerationRawY = 2,
Index_AccelerationRawZ = 3,
Index_AccelerationUserUpdated = 4,
Index_AccelerationUserX = 5,
Index_AccelerationUserY = 6,
Index_AccelerationUserZ = 7,
Index_AccelerationGravityUpdated = 8,
Index_AccelerationGravityX = 9,
Index_AccelerationGravityY = 10,
Index_AccelerationGravityZ = 11,
Index_RotationRateRawUpdated = 12,
Index_RotationRateRawX = 13,
Index_RotationRateRawY = 14,
Index_RotationRateRawZ = 15,
Index_RotationRateUnbiasedUpdated = 16,
Index_RotationRateUnbiasedX = 17,
Index_RotationRateUnbiasedY = 18,
Index_RotationRateUnbiasedZ = 19,
Index_MagneticFieldRawUpdated = 20,
Index_MagneticFieldRawX = 21,
Index_MagneticFieldRawY = 22,
Index_MagneticFieldRawZ = 23,
Index_MagneticFieldUnbiasedUpdated = 24,
Index_MagneticFieldUnbiasedX = 25,
Index_MagneticFieldUnbiasedY = 26,
Index_MagneticFieldUnbiasedZ = 27,
Index_OrientationUpdated = 28,
Index_OrientationX = 29,
Index_OrientationY = 30,
Index_OrientationZ = 31,
Index_OrientationW = 32,
Index_OrientationAdjustmentRadiansZ = 33,
// Length of the packed sensor data array
PackedSensorDataLength
};
////////////////////////////////////////////////////////////////////////////////////////////
//! The set of currently enabled input channel ids
InputDeviceRequests::InputChannelIdSet m_enabledChannelIds;
////////////////////////////////////////////////////////////////////////////////////////////
//! The packed sensor data array. This is a member variable to avoid creating it every frame.
float* m_packedSensorDataArray;
////////////////////////////////////////////////////////////////////////////////////////////
//! JNI object reference to the motion sensor manager
AZStd::unique_ptr<AZ::Android::JNI::Object> m_motionSensorManager;
////////////////////////////////////////////////////////////////////////////////////////////
///@{
//! Java field / function name
static constexpr const char* s_javaFuntionNameGetMotionSensorManager = "GetMotionSensorManager";
static constexpr const char* s_javaFuntionNameRefreshMotionSensors = "RefreshMotionSensors";
static constexpr const char* s_javaFuntionNameRequestLatestMotionSensorData = "RequestLatestMotionSensorData";
static constexpr const char* s_javaFieldNameMotionSensorDataPackedLength = "MOTION_SENSOR_DATA_PACKED_LENGTH";
///@}
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotion::Implementation* InputDeviceMotion::Implementation::Create(
InputDeviceMotion& inputDevice)
{
return aznew InputDeviceMotionAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotionAndroid::InputDeviceMotionAndroid(InputDeviceMotion& inputDevice)
: InputDeviceMotion::Implementation(inputDevice)
, m_enabledChannelIds()
, m_packedSensorDataArray(nullptr)
, m_motionSensorManager()
{
// Create a JNI object reference to the motion sensor manager
m_motionSensorManager.reset(aznew AZ::Android::JNI::Object("com/amazon/lumberyard/input/MotionSensorManager"));
// Regsiter the Java methods that are required to enable and query for motion sensor data
m_motionSensorManager->RegisterMethod(s_javaFuntionNameRefreshMotionSensors, "(FIIIIIIII)V");
m_motionSensorManager->RegisterMethod(s_javaFuntionNameRequestLatestMotionSensorData, "()[F");
m_motionSensorManager->RegisterStaticField(s_javaFieldNameMotionSensorDataPackedLength, "I");
// Create the packed sensor data array
const int packedSensorDataLength = m_motionSensorManager->GetStaticIntField(s_javaFieldNameMotionSensorDataPackedLength);
AZ_Assert(packedSensorDataLength == PackedSensorDataLength,
"InputDeviceMotionAndroid::PackedSensorDataLength != MotionSensorManager::MOTION_SENSOR_DATA_PACKED_LENGTH");
m_packedSensorDataArray = new float[packedSensorDataLength];
// create the java instance
bool ret = m_motionSensorManager->CreateInstance("(Landroid/app/Activity;)V", AZ::Android::Utils::GetActivityRef());
AZ_Assert(ret, "Failed to create the MotionSensorManager Java instance.");
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotionAndroid::~InputDeviceMotionAndroid()
{
// Destroy the packed sensor data array
delete[] m_packedSensorDataArray;
// Destroy the JNI object reference to the motion sensor manager
m_motionSensorManager->DestroyInstance();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsConnected() const
{
// Motion input is always available on android
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionAndroid::TickInputDevice()
{
if (m_enabledChannelIds.empty())
{
// Early out to avoid expensive JNI calls.
return;
}
if (JNIEnv* jniEnv = AZ::Android::JNI::GetEnv())
{
// Get the latest sensor data through JNI
jfloatArray latestSensorData = m_motionSensorManager->InvokeObjectMethod<jfloatArray>(s_javaFuntionNameRequestLatestMotionSensorData);
AZ_Assert(jniEnv->GetArrayLength(latestSensorData) == PackedSensorDataLength,
"InputDeviceMotionAndroid::PackedSensorDataLength != MotionSensorManager::MOTION_SENSOR_DATA_PACKED_LENGTH");
jniEnv->GetFloatArrayRegion(latestSensorData, 0, PackedSensorDataLength, m_packedSensorDataArray);
AZ_Assert(!jniEnv->ExceptionCheck(), "JNI exception thrown while getting the latest sensor data");
jniEnv->DeleteGlobalRef(latestSensorData);
}
else
{
// The JNI environment is not valid
return;
}
// While we would (ideally) like to encapsulate and return all this data using a Java class,
// we would then need to 'reach back' through the JNI for each field to access the raw data.
// Packing all the data into a float array is more efficient, but at the expense of needing
// to access data using pre-defined array indices, instead of explicitly named class fields.
//
// But while explicitly named class fields provide a modicum of safety, lots of boilerplate
// code is needed, and while support for additional sensor data may be added in the future,
// the existing set is unlikely to change, so combined with the above mentioned performance
// consideration this approach to obtaining raw sensor data seems preferable on most fronts.
if (m_packedSensorDataArray[Index_AccelerationRawUpdated])
{
// Process raw acceleration
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_AccelerationRawX],
m_packedSensorDataArray[Index_AccelerationRawY],
m_packedSensorDataArray[Index_AccelerationRawZ]);
ProcessAccelerationData(InputDeviceMotion::Acceleration::Raw, sensorData);
}
if (m_packedSensorDataArray[Index_AccelerationUserUpdated])
{
// Process user acceleration
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_AccelerationUserX],
m_packedSensorDataArray[Index_AccelerationUserY],
m_packedSensorDataArray[Index_AccelerationUserZ]);
ProcessAccelerationData(InputDeviceMotion::Acceleration::User, sensorData);
}
if (m_packedSensorDataArray[Index_AccelerationGravityUpdated])
{
// Process gravity acceleration
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_AccelerationGravityX],
m_packedSensorDataArray[Index_AccelerationGravityY],
m_packedSensorDataArray[Index_AccelerationGravityZ]);
ProcessAccelerationData(InputDeviceMotion::Acceleration::Gravity, sensorData);
}
if (m_packedSensorDataArray[Index_RotationRateRawUpdated])
{
// Process raw rotation rate
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_RotationRateRawX],
m_packedSensorDataArray[Index_RotationRateRawY],
m_packedSensorDataArray[Index_RotationRateRawZ]);
ProcessRotationRateData(InputDeviceMotion::RotationRate::Raw, sensorData);
}
if (m_packedSensorDataArray[Index_RotationRateUnbiasedUpdated])
{
// Process unbiased rotation rate
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_RotationRateUnbiasedX],
m_packedSensorDataArray[Index_RotationRateUnbiasedY],
m_packedSensorDataArray[Index_RotationRateUnbiasedZ]);
ProcessRotationRateData(InputDeviceMotion::RotationRate::Unbiased, sensorData);
}
if (m_packedSensorDataArray[Index_MagneticFieldRawUpdated])
{
// Process raw magnetic field
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_MagneticFieldRawX],
m_packedSensorDataArray[Index_MagneticFieldRawY],
m_packedSensorDataArray[Index_MagneticFieldRawZ]);
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::Raw, sensorData);
}
if (m_packedSensorDataArray[Index_MagneticFieldUnbiasedUpdated])
{
// Process unbiased magnetic field
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_MagneticFieldUnbiasedX],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedY],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedZ]);
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::Unbiased, sensorData);
}
if (m_packedSensorDataArray[Index_AccelerationGravityUpdated] &&
m_packedSensorDataArray[Index_MagneticFieldUnbiasedUpdated])
{
// Calculate and process magnetic north
const AZ::Vector3 gravity(m_packedSensorDataArray[Index_AccelerationGravityX],
m_packedSensorDataArray[Index_AccelerationGravityY],
m_packedSensorDataArray[Index_AccelerationGravityZ]);
const AZ::Vector3 magneticField(m_packedSensorDataArray[Index_MagneticFieldUnbiasedX],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedY],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedZ]);
const AZ::Vector3 gravityNormalized = gravity.GetNormalized();
const AZ::Vector3 magneticFieldNormalized = magneticField.GetNormalized();
const AZ::Vector3 magneticEastNormalized = gravityNormalized.Cross(magneticFieldNormalized).GetNormalized();
const AZ::Vector3 magneticNorthNormalized = magneticEastNormalized.Cross(gravityNormalized).GetNormalized();
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::North, magneticNorthNormalized);
}
if (m_packedSensorDataArray[Index_OrientationUpdated])
{
// Process current orientation
const AZ::Vector3 sensorDataXYZ(m_packedSensorDataArray[Index_OrientationX],
m_packedSensorDataArray[Index_OrientationY],
m_packedSensorDataArray[Index_OrientationZ]);
const float sensorDataImaginary(m_packedSensorDataArray[Index_OrientationW]);
AZ::Quaternion sensorData = AZ::Quaternion::CreateFromVector3AndValue(sensorDataXYZ,
sensorDataImaginary);
// Android doesn't provide us with any quaternion math,
// so we adjust alignment here instead of the Java code.
const float rotationAdjustmentZ(m_packedSensorDataArray[Index_OrientationAdjustmentRadiansZ]);
sensorData *= AZ::Quaternion::CreateRotationZ(rotationAdjustmentZ);
ProcessOrientationData(InputDeviceMotion::Orientation::Current, sensorData);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionAndroid::RefreshMotionSensors(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
if (m_enabledChannelIds == enabledChannelIds)
{
// Early out to avoid expensive JNI calls.
return;
}
const bool wasRequiredAccelerationRaw = IsRequiredAccelerationRaw();
const bool wasRequiredAccelerationUser = IsRequiredAccelerationUser();
const bool wasRequiredAccelerationGravity = IsRequiredAccelerationGravity();
const bool wasRequiredRotationRateRaw = IsRequiredRotationRateRaw();
const bool wasRequiredRotationRateUnbiased = IsRequiredRotationRateUnbiased();
const bool wasRequiredMagneticFieldRaw = IsRequiredMagneticFieldRaw();
const bool wasRequiredMagneticFieldUnbiased = IsRequiredMagneticFieldUnbiased();
const bool wasRequiredOrientationCurrent = IsRequiredOrientationCurrent();
m_enabledChannelIds = enabledChannelIds;
const bool isRequiredAccelerationRaw = IsRequiredAccelerationRaw();
const bool isRequiredAccelerationUser = IsRequiredAccelerationUser();
const bool isRequiredAccelerationGravity = IsRequiredAccelerationGravity();
const bool isRequiredRotationRateRaw = IsRequiredRotationRateRaw();
const bool isRequiredRotationRateUnbiased = IsRequiredRotationRateUnbiased();
const bool isRequiredMagneticFieldRaw = IsRequiredMagneticFieldRaw();
const bool isRequiredMagneticFieldUnbiased = IsRequiredMagneticFieldUnbiased();
const bool isRequiredOrientationCurrent = IsRequiredOrientationCurrent();
static const float s_desiredMotionSensorUpdateIntervalSeconds = 1.0f / 30.0f;
m_motionSensorManager->InvokeVoidMethod(
s_javaFuntionNameRefreshMotionSensors,
s_desiredMotionSensorUpdateIntervalSeconds,
GetUpdatedSensorState(wasRequiredAccelerationRaw, isRequiredAccelerationRaw),
GetUpdatedSensorState(wasRequiredAccelerationUser, isRequiredAccelerationUser),
GetUpdatedSensorState(wasRequiredAccelerationGravity, isRequiredAccelerationGravity),
GetUpdatedSensorState(wasRequiredRotationRateRaw, isRequiredRotationRateRaw),
GetUpdatedSensorState(wasRequiredRotationRateUnbiased, isRequiredRotationRateUnbiased),
GetUpdatedSensorState(wasRequiredMagneticFieldRaw, isRequiredMagneticFieldRaw),
GetUpdatedSensorState(wasRequiredMagneticFieldUnbiased, isRequiredMagneticFieldUnbiased),
GetUpdatedSensorState(wasRequiredOrientationCurrent, isRequiredOrientationCurrent));
}
////////////////////////////////////////////////////////////////////////////////////////////////
int InputDeviceMotionAndroid::GetUpdatedSensorState(bool wasRequired, bool isRequired) const
{
if (wasRequired == isRequired)
{
return 0; // Unchanged
}
else if (isRequired)
{
return 1; // Enable
}
else
{
return -1; // Disable
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredAccelerationRaw() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Acceleration::Raw) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredAccelerationUser() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Acceleration::User) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredAccelerationGravity() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Acceleration::Gravity) != m_enabledChannelIds.end() ||
m_enabledChannelIds.find(InputDeviceMotion::MagneticField::North) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredRotationRateRaw() const
{
return m_enabledChannelIds.find(InputDeviceMotion::RotationRate::Raw) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredRotationRateUnbiased() const
{
return m_enabledChannelIds.find(InputDeviceMotion::RotationRate::Unbiased) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredMagneticFieldRaw() const
{
return m_enabledChannelIds.find(InputDeviceMotion::MagneticField::Raw) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredMagneticFieldUnbiased() const
{
return m_enabledChannelIds.find(InputDeviceMotion::MagneticField::Unbiased) != m_enabledChannelIds.end() ||
m_enabledChannelIds.find(InputDeviceMotion::MagneticField::North) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredOrientationCurrent() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Orientation::Current) != m_enabledChannelIds.end();
}
} // namespace AzFramework
@@ -0,0 +1,370 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/ApiLevel.h>
#include <AzCore/Android/Utils.h>
#include <android/input.h>
namespace
{
// the following values were added in later APIs however their native counterparts
// were only added to the unified headers in NDK r14+
// dedicated mouse button events were added in API 23
const int EVENT_ACTION_BUTTON_PRESS = 11; // AMOTION_EVENT_ACTION_BUTTON_PRESS
const int EVENT_ACTION_BUTTON_RELEASE = 12; // AMOTION_EVENT_ACTION_BUTTON_RELEASE
// relative pointer queries were added in API 24
const int EVENT_AXIS_RELATIVE_X = 27; // AMOTION_EVENT_AXIS_RELATIVE_X
const int EVENT_AXIS_RELATIVE_Y = 28; // AMOTION_EVENT_AXIS_RELATIVE_Y
class RawMouseConnectionNotificationsAndroid
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
virtual ~RawMouseConnectionNotificationsAndroid() = default;
virtual void OnMouseConnected() = 0;
virtual void OnMouseDisconnected() = 0;
};
using RawMouseConnectionNotificationsBusAndroid = AZ::EBus<RawMouseConnectionNotificationsAndroid>;
void JNI_OnMouseConnected(JNIEnv* jniEnv, jobject objectRef)
{
RawMouseConnectionNotificationsBusAndroid::Broadcast(&RawMouseConnectionNotificationsAndroid::OnMouseConnected);
}
void JNI_OnMouseDisconnected(JNIEnv* jniEnv, jobject objectRef)
{
RawMouseConnectionNotificationsBusAndroid::Broadcast(&RawMouseConnectionNotificationsAndroid::OnMouseDisconnected);
}
}
namespace AzFramework
{
//! Platform specific implementation for Android physical mouse input devices. This
//! includes devices connected through USB or Bluetooth.
class InputDeviceMouseAndroid
: public InputDeviceMouse::Implementation
, public RawInputNotificationBusAndroid::Handler
, public RawMouseConnectionNotificationsBusAndroid::Handler
{
public:
AZ_CLASS_ALLOCATOR(InputDeviceMouseAndroid, AZ::SystemAllocator, 0);
InputDeviceMouseAndroid(InputDeviceMouse& inputDevice);
~InputDeviceMouseAndroid() override;
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
private:
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
bool IsConnected() const override { return m_isConnected; }
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
void SetSystemCursorState(SystemCursorState systemCursorState) override;
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
SystemCursorState GetSystemCursorState() const override;
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
//!@{
//! \brief Connection state callbacks from java
void OnMouseConnected() override { m_isConnected = true; }
void OnMouseDisconnected() override { m_isConnected = false; }
//!@}
//! Special case processing of key events for certain devices that treat some mouse buttons
//! as simulated key events
void HandleKeyEvent(const AInputEvent* rawInputEvent);
//! Helper for processing the common mouse button event processing
void ProcessMouseButtonEvent(int buttonState, bool isPressed);
AZStd::unique_ptr<AZ::Android::JNI::Object> m_mouseDevice; //!< Java interface for getting connection state info
AZ::Vector2 m_mouseWindowPixelPosition; //!< Cached location of the mouse, in pixel window space
int m_mouseButtonState; //!< Cached flags of which mouse buttons are currently active
volatile bool m_isConnected; //!< Cached connection state, ie. at least 1 mouse is connected
};
InputDeviceMouseAndroid::InputDeviceMouseAndroid(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_mouseWindowPixelPosition(AZ::Vector2::CreateZero())
, m_mouseButtonState(0)
, m_isConnected(false)
{
// Initialize the mouse device handler
m_mouseDevice.reset(aznew AZ::Android::JNI::Object("com/amazon/lumberyard/input/MouseDevice"));
m_mouseDevice->RegisterMethod("IsConnected", "()Z");
m_mouseDevice->RegisterNativeMethods(
{
{ "OnMouseConnected", "()V", (void*)JNI_OnMouseConnected },
{ "OnMouseDisconnected", "()V", (void*)JNI_OnMouseDisconnected }
});
// create the java instance
bool ret = m_mouseDevice->CreateInstance("(Landroid/app/Activity;)V", AZ::Android::Utils::GetActivityRef());
AZ_Assert(ret, "Failed to create the MouseDevice Java instance.");
if (ret)
{
m_isConnected = m_mouseDevice->InvokeBooleanMethod("IsConnected");
}
RawInputNotificationBusAndroid::Handler::BusConnect();
RawMouseConnectionNotificationsBusAndroid::Handler::BusConnect();
}
InputDeviceMouseAndroid::~InputDeviceMouseAndroid()
{
RawMouseConnectionNotificationsBusAndroid::Handler::BusDisconnect();
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
void InputDeviceMouseAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
if (!m_isConnected)
{
return;
}
// special case for some devices that treat the right click as a back button event
const int eventType = AInputEvent_getType(rawInputEvent);
if (eventType == AINPUT_EVENT_TYPE_KEY)
{
HandleKeyEvent(rawInputEvent);
return;
}
// Get the action code and pointer index
const int action = AMotionEvent_getAction(rawInputEvent);
const int actionCode = (action & AMOTION_EVENT_ACTION_MASK);
const int pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
// ensure we are only dispatching pure mouse events and not simulated ones
const int toolType = AMotionEvent_getToolType(rawInputEvent, pointerIndex);
if (toolType != AMOTION_EVENT_TOOL_TYPE_MOUSE)
{
return;
}
const AZ::Android::ApiLevel sdkVersion = AZ::Android::GetRuntimeApiLevel();
const int buttonState = AMotionEvent_getButtonState(rawInputEvent);
switch (actionCode)
{
case AMOTION_EVENT_ACTION_POINTER_DOWN:
// guard against duplicate events when running on API 23 or above
if (sdkVersion >= AZ::Android::ApiLevel::Marshmallow)
{
break;
}
// fallthrough
case EVENT_ACTION_BUTTON_PRESS:
{
// extract the mouse button that was added to the flags
int newButtonState = buttonState & ~m_mouseButtonState;
ProcessMouseButtonEvent(newButtonState, true);
m_mouseButtonState = buttonState;
break;
}
case AMOTION_EVENT_ACTION_POINTER_UP:
// guard against duplicate events when running on API 23 or above
if (sdkVersion >= AZ::Android::ApiLevel::Marshmallow)
{
break;
}
// fallthrough
case EVENT_ACTION_BUTTON_RELEASE:
{
// extract the mouse button that was removed from the flags
int removedButtonState = m_mouseButtonState & ~buttonState;
ProcessMouseButtonEvent(removedButtonState, false);
m_mouseButtonState = buttonState;
break;
}
case AMOTION_EVENT_ACTION_SCROLL:
{
const float verticalScroll = AMotionEvent_getAxisValue(rawInputEvent, AMOTION_EVENT_AXIS_VSCROLL, pointerIndex);
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, verticalScroll);
break;
}
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
{
const AZ::Vector2 mousePostion {
AMotionEvent_getX(rawInputEvent, pointerIndex),
AMotionEvent_getY(rawInputEvent, pointerIndex)
};
AZ::Vector2 mouseDeltaPostion(AZ::Vector2::CreateZero());
if (sdkVersion >= AZ::Android::ApiLevel::Nougat)
{
mouseDeltaPostion.SetX(AMotionEvent_getAxisValue(rawInputEvent, EVENT_AXIS_RELATIVE_X, pointerIndex));
mouseDeltaPostion.SetY(AMotionEvent_getAxisValue(rawInputEvent, EVENT_AXIS_RELATIVE_Y, pointerIndex));
}
else
{
mouseDeltaPostion = mousePostion - m_mouseWindowPixelPosition;
}
QueueRawMovementEvent(InputDeviceMouse::Movement::X, mouseDeltaPostion.GetX());
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, mouseDeltaPostion.GetY());
m_mouseWindowPixelPosition = mousePostion;
break;
}
default:
// do nothing
break;
}
}
void InputDeviceMouseAndroid::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (m_isConnected)
{
AZ_WarningOnce("InputDeviceMouseAndroid", false, "Calls to SetSystemCursorState are unsupported on Android.");
}
}
SystemCursorState InputDeviceMouseAndroid::GetSystemCursorState() const
{
// Returning the correct state here is a bit tricky because the system will auto show and hide
// the cursor based on usage of the physical mouse. The application has no control over
// the visibily of the cursor, nor do we have control of constraining the cursor to inside the
// applicaiton window. Always returning UnconstrainedAndVisible is going to be the closest
// to accurate value we can send when a physical mouse is connected.
return (m_isConnected ? SystemCursorState::UnconstrainedAndVisible : SystemCursorState::Unknown);
}
void InputDeviceMouseAndroid::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
if (m_isConnected)
{
AZ_WarningOnce("InputDeviceMouseAndroid", false, "Calls to SetSystemCursorPositionNormalized are unsupported on Android.");
}
}
AZ::Vector2 InputDeviceMouseAndroid::GetSystemCursorPositionNormalized() const
{
int windowWidth = 0;
int windowHeight = 0;
if (!AZ::Android::Utils::GetWindowSize(windowWidth, windowHeight))
{
AZ_ErrorOnce("InputDeviceMouseAndroid", false, "Unable to get the window size, the mouse location will NOT be normalized.");
windowWidth = 1;
windowHeight = 1;
}
return AZ::Vector2(m_mouseWindowPixelPosition.GetX() / static_cast<float>(windowWidth),
m_mouseWindowPixelPosition.GetY() / static_cast<float>(windowHeight));
}
void InputDeviceMouseAndroid::TickInputDevice()
{
if (m_isConnected)
{
ProcessRawEventQueues();
}
}
void InputDeviceMouseAndroid::HandleKeyEvent(const AInputEvent* rawInputEvent)
{
const int inputSource = AInputEvent_getSource(rawInputEvent);
if ((inputSource & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE)
{
const int keyCode = AKeyEvent_getKeyCode(rawInputEvent);
if (keyCode == AKEYCODE_BACK)
{
const int keyAction = AKeyEvent_getAction(rawInputEvent);
switch (keyAction)
{
case AKEY_EVENT_ACTION_DOWN:
// guard against duplicate events when holding down the button
if (AKeyEvent_getRepeatCount(rawInputEvent) == 0)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, true);
}
break;
case AKEY_EVENT_ACTION_UP:
QueueRawButtonEvent(InputDeviceMouse::Button::Right, false);
break;
default:
// do nothing
break;
}
}
}
}
void InputDeviceMouseAndroid::ProcessMouseButtonEvent(int buttonState, bool isPressed)
{
if ((buttonState & AMOTION_EVENT_BUTTON_PRIMARY) == AMOTION_EVENT_BUTTON_PRIMARY)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Left, isPressed);
}
else if ((buttonState & AMOTION_EVENT_BUTTON_SECONDARY) == AMOTION_EVENT_BUTTON_SECONDARY)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, isPressed);
}
else if ((buttonState & AMOTION_EVENT_BUTTON_TERTIARY) == AMOTION_EVENT_BUTTON_TERTIARY)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Middle, isPressed);
}
}
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
return aznew InputDeviceMouseAndroid(inputDevice);
}
} // namespace AzFramework
@@ -0,0 +1,231 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/std/parallel/mutex.h>
#include <android/input.h>
#include <android/keycodes.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for android touch input devices
class InputDeviceTouchAndroid : public InputDeviceTouch::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceTouchAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceTouchAndroid(InputDeviceTouch& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceTouchAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceTouch::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceTouch::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to process raw touch events
//! \param[in] rawInputEvent The raw input event data
//! \param[in] pointerIndex The index of the touch event
//! \param[in] rawTouchState The state of the touch event
void OnRawTouchEvent(const AInputEvent* rawInputEvent,
int pointerIndex,
RawTouchEvent::State rawTouchState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Raw input events on android can (and likely will) be dispatched from a thread other than
//! main, so we can't immediately call InputDeviceTouch::Implementation::QueueRawTouchEvent.
//! Instead, we'll store them in m_threadAwareRawTouchEvents then process in TickInputDevice
//! on the main thread, ensuring all access is locked using m_threadAwareRawTouchEventsMutex.
///@{
AZStd::vector<RawTouchEvent> m_threadAwareRawTouchEvents;
AZStd::mutex m_threadAwareRawTouchEventsMutex;
///@}
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouch::Implementation* InputDeviceTouch::Implementation::Create(InputDeviceTouch& inputDevice)
{
return aznew InputDeviceTouchAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouchAndroid::InputDeviceTouchAndroid(InputDeviceTouch& inputDevice)
: InputDeviceTouch::Implementation(inputDevice)
, m_threadAwareRawTouchEvents()
, m_threadAwareRawTouchEventsMutex()
{
RawInputNotificationBusAndroid::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouchAndroid::~InputDeviceTouchAndroid()
{
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceTouchAndroid::IsConnected() const
{
// Touch input is always available on android
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchAndroid::TickInputDevice()
{
// The input event loop is pumped by another thread on android so all raw input events this
// frame have already been dispatched. But they are all queued until ProcessRawEventQueues
// is called below so that all raw input events are processed at the same time every frame.
// Because m_threadAwareRawTouchEvents is updated from another thread, but also needs to be
// processed and cleared in this function, swapping it with an empty vector kills two birds
// with one stone in a very efficient and elegant manner (kudos to scottr for the idea).
AZStd::vector<RawTouchEvent> rawTouchEvents;
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTouchEventsMutex);
rawTouchEvents.swap(m_threadAwareRawTouchEvents);
}
// Queue the raw touch events that were received over the last frame
for (const RawTouchEvent& rawTouchEvent : rawTouchEvents)
{
QueueRawTouchEvent(rawTouchEvent);
}
// Process the raw event queues once each frame
ProcessRawEventQueues();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// Reference: https://developer.android.com/ndk/reference/group___input.html
// Discard non-touch events
const int eventType = AInputEvent_getType(rawInputEvent);
if (eventType != AINPUT_EVENT_TYPE_MOTION)
{
return;
}
// Get the action code and pointer index
const int action = AMotionEvent_getAction(rawInputEvent);
const int actionCode = (action & AMOTION_EVENT_ACTION_MASK);
const int pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
switch (actionCode)
{
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
{
OnRawTouchEvent(rawInputEvent, pointerIndex, RawTouchEvent::State::Began);
}
break;
case AMOTION_EVENT_ACTION_MOVE:
{
// Android doesn't send individual move events for each separate touch like it does
// for down and up events, instead sending them all as part of the same input event.
const size_t pointerCount = AMotionEvent_getPointerCount(rawInputEvent);
for (int i = 0; i < pointerCount; ++i)
{
OnRawTouchEvent(rawInputEvent, i, RawTouchEvent::State::Moved);
}
}
break;
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_CANCEL:
{
OnRawTouchEvent(rawInputEvent, pointerIndex, RawTouchEvent::State::Ended);
}
break;
default:
{
return;
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchAndroid::OnRawTouchEvent(const AInputEvent* rawInputEvent,
int pointerIndex,
RawTouchEvent::State rawTouchState)
{
// Allow simulated touch events (generated by "adb shell monkey") or real finger touch events.
// (but not stylus, mouse or eraser)
const int toolType = AMotionEvent_getToolType(rawInputEvent, pointerIndex);
if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER && toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN)
{
return;
}
// Get the rest of the touch event data
const float x = AMotionEvent_getX(rawInputEvent, pointerIndex);
const float y = AMotionEvent_getY(rawInputEvent, pointerIndex);
float pressure = AMotionEvent_getPressure(rawInputEvent, pointerIndex);
const int pointerId = AMotionEvent_getPointerId(rawInputEvent, pointerIndex);
// The simulated events always have a pressure of zero. An analog input channel is only active
// if the value is != 0.0f. Touch input was implemented as an analog input channel because both
// iOS and Android report touch input with a pressure value, and for anything that doesn't report
// a pressure value you have to explicitly set it as 1.0f (or really anything != 0.0f) for 'active'
// and 0.0f for not active.
if (toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN && pressure == 0.0f)
{
pressure = 1.0f;
}
// normalize the touch location
int windowWidth = 0;
int windowHeight = 0;
if (!AZ::Android::Utils::GetWindowSize(windowWidth, windowHeight))
{
AZ_ErrorOnce("InputDeviceTouchAndroid", false, "Unable to get the window size, touch input may not behave correctly.");
windowWidth = 1;
windowHeight = 1;
}
// Push the raw touch event onto the thread safe queue for processing in TickInputDevice
const RawTouchEvent rawTouchEvent(x / static_cast<float>(windowWidth),
y / static_cast<float>(windowHeight),
rawTouchState == RawTouchEvent::State::Ended ? 0.0f : pressure,
pointerId,
rawTouchState);
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTouchEventsMutex);
m_threadAwareRawTouchEvents.push_back(rawTouchEvent);
}
}
@@ -0,0 +1,296 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/Utils.h>
#include <android/input.h>
#include <android/keycodes.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////////
void JNI_OnRawTextEvent(JNIEnv* jniEnv, jobject objectRef, jstring stringUTF16)
{
const char* charsModifiedUTF8 = jniEnv->GetStringUTFChars(stringUTF16, nullptr);
RawInputNotificationBusAndroid::Broadcast(&RawInputNotificationsAndroid::OnRawInputTextEvent,
charsModifiedUTF8);
jniEnv->ReleaseStringUTFChars(stringUTF16, charsModifiedUTF8);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Android virtual keyboard input devices. This input device
//! is responsible for sending text events only, the physical keyboard is the one responsible
//! for sending key events. Should this behaviour need to be changed, LY-69260 will correct it
class InputDeviceVirtualKeyboardAndroid : public InputDeviceVirtualKeyboard::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceVirtualKeyboardAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceVirtualKeyboardAndroid(InputDeviceVirtualKeyboard& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceVirtualKeyboardAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::HasTextEntryStarted
bool HasTextEntryStarted() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TextEntryStart
void TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TextEntryStop
void TextEntryStop() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputTextEvent
void OnRawInputTextEvent(const char* charsModifiedUTF8) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Raw input events on android can (and likely will) be dispatched from a thread other than
//! main, and InputDeviceVirtualKeyboard::Implementation::QueueRawCommandEvent is not thread
//! safe. So we'll store them in m_threadAwareRawCommandEvents to process in TickInputDevice
//! on the main thread, ensuring all access is locked by m_threadAwareRawCommandEventsMutex.
///@{
AZStd::vector<InputChannelId> m_threadAwareRawCommandEvents;
AZStd::mutex m_threadAwareRawCommandEventsMutex;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Raw input events on android can (and likely will) be dispatched from a thread other than
//! main, and InputDeviceVirtualKeyboard::Implementation::QueueRawTextEvent is not thread
//! safe. So we'll store them in m_threadAwareRawTextEvents to process in TickInputDevice
//! on the main thread, ensuring all access is locked by m_threadAwareRawTextEventsMutex.
///@{
AZStd::vector<AZStd::string> m_threadAwareRawTextEvents;
AZStd::mutex m_threadAwareRawTextEventsMutex;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Unique pointer to the keyboard handler JNI object
AZStd::unique_ptr<AZ::Android::JNI::Object> m_keyboardHandler;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboard::Implementation* InputDeviceVirtualKeyboard::Implementation::Create(
InputDeviceVirtualKeyboard& inputDevice)
{
return aznew InputDeviceVirtualKeyboardAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboardAndroid::InputDeviceVirtualKeyboardAndroid(
InputDeviceVirtualKeyboard& inputDevice)
: InputDeviceVirtualKeyboard::Implementation(inputDevice)
{
// Initialize the keyboard handler
m_keyboardHandler.reset(aznew AZ::Android::JNI::Object("com/amazon/lumberyard/input/KeyboardHandler"));
m_keyboardHandler->RegisterMethod("ShowTextInput", "()V");
m_keyboardHandler->RegisterMethod("HideTextInput", "()V");
m_keyboardHandler->RegisterMethod("IsShowing", "()Z");
m_keyboardHandler->RegisterNativeMethods(
{
{ "SendUnicodeText", "(Ljava/lang/String;)V", (void*)JNI_OnRawTextEvent }
});
// create the java instance
bool ret = m_keyboardHandler->CreateInstance("(Landroid/app/Activity;)V", AZ::Android::Utils::GetActivityRef());
AZ_Assert(ret, "Failed to create the KeyboardHandler Java instance.");
// Connect to the raw input notifications bus
RawInputNotificationBusAndroid::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboardAndroid::~InputDeviceVirtualKeyboardAndroid()
{
// Disconnect from the raw input notifications bus
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceVirtualKeyboardAndroid::IsConnected() const
{
// Virtual keyboard input is always available
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceVirtualKeyboardAndroid::HasTextEntryStarted() const
{
return m_keyboardHandler ? m_keyboardHandler->InvokeBooleanMethod("IsShowing") == JNI_TRUE : false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions&)
{
if (m_keyboardHandler && !m_keyboardHandler->InvokeBooleanMethod("IsShowing"))
{
m_keyboardHandler->InvokeVoidMethod("ShowTextInput");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::TextEntryStop()
{
if (m_keyboardHandler && m_keyboardHandler->InvokeBooleanMethod("IsShowing"))
{
m_keyboardHandler->InvokeVoidMethod("HideTextInput");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::TickInputDevice()
{
// The input event loop is pumped by another thread on android so all raw input events this
// frame have already been dispatched. But they are all queued until ProcessRawEventQueues
// is called below so that all raw input events are processed at the same time every frame.
// Because m_threadAwareRawCommandEvents is updated from another thread but also needs to be
// processed and cleared in this function, swapping it with an empty vector kills two birds
// with one stone in a very efficient and elegant manner (kudos to scottr for the idea).
AZStd::vector<InputChannelId> rawCommandEvents;
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
rawCommandEvents.swap(m_threadAwareRawCommandEvents);
}
// Queue the raw command events that were received over the last frame
for (const InputChannelId& rawCommandEvent : rawCommandEvents)
{
QueueRawCommandEvent(rawCommandEvent);
}
// Because m_threadAwareRawTextEvents is updated from another thread, but also needs to be
// processed and cleared in this function, swapping it with an empty vector kills two birds
// with one stone in a very efficient and elegant manner (kudos to scottr for the idea).
AZStd::vector<AZStd::string> rawTextEvents;
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
rawTextEvents.swap(m_threadAwareRawTextEvents);
}
// Queue the raw text events that were received over the last frame
for (const AZStd::string& rawTextEvent : rawTextEvents)
{
QueueRawTextEvent(rawTextEvent);
}
// Process the raw event queues once each frame
ProcessRawEventQueues();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// only care about key events
if (AInputEvent_getType(rawInputEvent) != AINPUT_EVENT_TYPE_KEY)
{
return;
}
// We only care about key down actions
if (AKeyEvent_getAction(rawInputEvent) != AKEY_EVENT_ACTION_DOWN)
{
return;
}
const int keyCode = AKeyEvent_getKeyCode(rawInputEvent);
// always send the back event, but only if it's coming from the system
if (keyCode == AKEYCODE_BACK)
{
const int eventFlags = AKeyEvent_getFlags(rawInputEvent);
if ((eventFlags & AKEY_EVENT_FLAG_FROM_SYSTEM) == AKEY_EVENT_FLAG_FROM_SYSTEM)
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
m_threadAwareRawCommandEvents.push_back(AzFramework::InputDeviceVirtualKeyboard::Command::NavigationBack);
}
return;
}
// early out on the rest of the event processing if the keyboard isn't active
if (!HasTextEntryStarted())
{
return;
}
switch (keyCode)
{
case AKEYCODE_CLEAR:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
m_threadAwareRawCommandEvents.push_back(AzFramework::InputDeviceVirtualKeyboard::Command::EditClear);
}
break;
case AKEYCODE_ENTER:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
m_threadAwareRawCommandEvents.push_back(AzFramework::InputDeviceVirtualKeyboard::Command::EditEnter);
}
break;
case AKEYCODE_DEL:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
m_threadAwareRawTextEvents.push_back(AZStd::string("\b"));
}
break;
case AKEYCODE_SPACE:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
m_threadAwareRawTextEvents.push_back(AZStd::string(" "));
}
break;
default:
{
// Do nothing
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::OnRawInputTextEvent(const char* charsModifiedUTF8)
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
m_threadAwareRawTextEvents.push_back(AZStd::string(charsModifiedUTF8));
}
} // namespace AzFramework
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Platform/Common/Default/AzFramework/Input/User/LocalUserId_Default.h>
@@ -0,0 +1,62 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzCore/Android/Utils.h>
#include <android/native_window.h>
namespace AzFramework
{
class NativeWindowImpl_Android final
: public NativeWindow::Implementation
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Android, AZ::SystemAllocator, 0);
NativeWindowImpl_Android() = default;
~NativeWindowImpl_Android() override = default;
// NativeWindow::Implementation overrides...
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
private:
ANativeWindow* m_nativeWindow = nullptr;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
{
return aznew NativeWindowImpl_Android();
}
void NativeWindowImpl_Android::InitWindow([[maybe_unused]]const AZStd::string& title,
const WindowGeometry& geometry,
[[maybe_unused]]const WindowStyleMasks& styleMasks)
{
m_nativeWindow = AZ::Android::Utils::GetWindow();
m_width = geometry.m_width;
m_height = geometry.m_height;
if (m_nativeWindow)
{
ANativeWindow_setBuffersGeometry(m_nativeWindow, m_width, m_height, ANativeWindow_getFormat(m_nativeWindow));
}
}
NativeWindowHandle NativeWindowImpl_Android::GetWindowHandle() const
{
return reinterpret_cast<NativeWindowHandle>(m_nativeWindow);
}
} // namespace AzFramework
@@ -0,0 +1,11 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,36 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFramework/AzFramework_Traits_Platform.h
AzFramework/AzFramework_Traits_Android.h
AzFramework/API/ApplicationAPI_Platform.h
AzFramework/API/ApplicationAPI_Android.h
AzFramework/Application/Application_Android.cpp
../Common/Unimplemented/AzFramework/Asset/AssetSystemComponentHelper_Unimplemented.cpp
AzFramework/IO/LocalFileIO_Android.cpp
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
../Common/Default/AzFramework/TargetManagement/TargetManagementComponent_Default.cpp
AzFramework/Windowing/NativeWindow_Android.cpp
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Android.h
AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Android.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Android.cpp
AzFramework/Input/Devices/Motion/InputDeviceMotion_Android.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Android.cpp
AzFramework/Input/Devices/Touch/InputDeviceTouch_Android.cpp
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Android.cpp
AzFramework/Archive/ArchiveVars_Platform.h
AzFramework/Archive/ArchiveVars_Android.h
)