2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
# include "LaunchPrivatePCH.h"
# include <string.h>
# include <jni.h>
# include <pthread.h>
# include "AndroidJNI.h"
# include "AndroidEventManager.h"
# include "AndroidInputInterface.h"
# include <android/log.h>
# include <android_native_app_glue.h>
# include <cstdio>
# include <sys/resource.h>
# include <dlfcn.h>
# include "AndroidWindow.h"
2014-05-08 15:57:50 -04:00
# include <android/sensor.h>
# include "Core.h"
2014-03-14 14:13:41 -04:00
// Function pointer for retrieving joystick events
// Function has been part of the OS since Honeycomb, but only appeared in the
// NDK in r19. Querying via dlsym allows its use without tying to the newest
// NDK.
typedef float ( * GetAxesType ) ( const AInputEvent * , int32_t axis , size_t pointer_index ) ;
static GetAxesType GetAxes = NULL ;
// List of default axes to query for each controller
// Ideal solution is to call out to Java and enumerate the list of axes.
static const int32_t AxisList [ ] =
{
AMOTION_EVENT_AXIS_X ,
AMOTION_EVENT_AXIS_Y ,
AMOTION_EVENT_AXIS_Z ,
AMOTION_EVENT_AXIS_RZ ,
AMOTION_EVENT_AXIS_LTRIGGER ,
AMOTION_EVENT_AXIS_RTRIGGER ,
AMOTION_EVENT_AXIS_GAS ,
AMOTION_EVENT_AXIS_BRAKE ,
//These are DPAD analogs
//AMOTION_EVENT_AXIS_HAT_X,
//AMOTION_EVENT_AXIS_HAT_Y,
} ;
2014-04-23 17:39:33 -04:00
// map of all supported keycodes
static TSet < uint16 > MappedKeyCodes ;
2014-04-23 22:06:21 -04:00
// List of gamepad keycodes to ignore
static const uint16 IgnoredGamepadKeyCodesList [ ] =
{
AKEYCODE_VOLUME_UP ,
AKEYCODE_VOLUME_DOWN
} ;
2014-09-18 17:49:40 -04:00
// List of desired gamepad keycodes
static const uint16 ValidGamepadKeyCodesList [ ] =
{
AKEYCODE_BUTTON_A ,
AKEYCODE_DPAD_CENTER ,
AKEYCODE_BUTTON_B ,
AKEYCODE_BUTTON_X ,
AKEYCODE_BUTTON_Y ,
AKEYCODE_BUTTON_L1 ,
AKEYCODE_BUTTON_R1 ,
AKEYCODE_BUTTON_START ,
AKEYCODE_MENU ,
AKEYCODE_BUTTON_SELECT ,
AKEYCODE_BACK ,
AKEYCODE_BUTTON_THUMBL ,
AKEYCODE_BUTTON_THUMBR ,
AKEYCODE_BUTTON_L2 ,
AKEYCODE_BUTTON_R2 ,
AKEYCODE_DPAD_UP ,
AKEYCODE_DPAD_DOWN ,
AKEYCODE_DPAD_LEFT ,
AKEYCODE_DPAD_RIGHT
} ;
2014-04-23 22:06:21 -04:00
// map of gamepad keycodes that should be ignored
static TSet < uint16 > IgnoredGamepadKeyCodes ;
2014-09-18 17:49:40 -04:00
// map of gamepad keycodes that should be passed forward
static TSet < uint16 > ValidGamepadKeyCodes ;
2014-03-14 14:13:41 -04:00
// -nostdlib means no crtbegin_so.o, so we have to provide our own __dso_handle and atexit()
extern " C "
{
int atexit ( void ( * func ) ( void ) ) { return 0 ; }
extern void * __dso_handle __attribute__ ( ( __visibility__ ( " hidden " ) ) ) ;
void * __dso_handle ;
}
2014-10-20 19:15:50 -04:00
extern void AndroidThunkCpp_InitHMDs ( ) ;
2014-03-14 14:13:41 -04:00
extern void AndroidThunkCpp_ShowConsoleWindow ( ) ;
2014-04-23 18:59:23 -04:00
// Base path for file accesses
extern FString GFilePathBase ;
2014-03-14 14:13:41 -04:00
/** The global EngineLoop instance */
FEngineLoop GEngineLoop ;
bool GShowConsoleWindowNextTick = false ;
static void AndroidProcessEvents ( struct android_app * state ) ;
//Event thread stuff
static void * AndroidEventThreadWorker ( void * param ) ;
2014-05-08 15:57:50 -04:00
// How often to process (read & dispatch) events, in seconds.
static const float EventRefreshRate = 1.0f / 20.0f ;
2014-03-14 14:13:41 -04:00
//Android event callback functions
static int32_t HandleInputCB ( struct android_app * app , AInputEvent * event ) ; //Touch and key input events
static void OnAppCommandCB ( struct android_app * app , int32_t cmd ) ; //Lifetime events
2014-07-29 18:07:48 -04:00
static int HandleSensorEvents ( int fd , int events , void * data ) ; // Sensor events
2014-03-14 14:13:41 -04:00
bool GHasInterruptionRequest = false ;
bool GIsInterrupted = false ;
2014-05-08 15:57:50 -04:00
// Android sensor data management
static ASensorManager * SensorManager = NULL ;
// Accelerometer (includes gravity), i.e. FMotionEvent::GetAcceleration.
static const ASensor * SensorAccelerometer = NULL ;
// Gyroscope, i.e. FMotionEvent::GetRotationRate.
static const ASensor * SensorGyroscope = NULL ;
static ASensorEventQueue * SensorQueue = NULL ;
// android.hardware.SensorManager.SENSOR_DELAY_GAME
static const int32_t SensorDelayGame = 1 ;
// Time decay sampling rate.
static const float SampleDecayRate = 0.85f ;
2014-06-27 15:48:23 -04:00
// Event for coordinating pausing of the main and event handling threads to prevent background spinning
static FEvent * EventHandlerEvent = NULL ;
2014-07-10 14:41:00 -04:00
// Wait for Java onCreate to complete before resume main init
static volatile bool GResumeMainInit = false ;
static volatile bool GEventHandlerInitialized = false ;
extern " C " void Java_com_epicgames_ue4_GameActivity_nativeResumeMainInit ( JNIEnv * jenv , jobject thiz )
{
GResumeMainInit = true ;
// now wait for event handler to be set up before returning
while ( ! GEventHandlerInitialized )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
FPlatformMisc : : MemoryBarrier ( ) ;
}
}
2014-06-27 15:48:23 -04:00
2014-10-20 19:15:50 -04:00
static volatile bool GHMDsInitialized = false ;
static TArray < IHeadMountedDisplayModule * > GHMDImplementations ;
void InitHMDs ( )
{
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " nohmd " ) ) | | FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " emulatestereo " ) ) )
{
return ;
}
// Get a list of plugins that implement this feature
GHMDImplementations = IModularFeatures : : Get ( ) . GetModularFeatureImplementations < IHeadMountedDisplayModule > ( IHeadMountedDisplayModule : : GetModularFeatureName ( ) ) ;
AndroidThunkCpp_InitHMDs ( ) ;
while ( ! GHMDsInitialized )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
FPlatformMisc : : MemoryBarrier ( ) ;
}
}
2014-03-14 14:13:41 -04:00
static void InitCommandLine ( )
{
static const uint32 CMD_LINE_MAX = 16384u ;
// initialize the command line to an empty string
FCommandLine : : Set ( TEXT ( " " ) ) ;
// read in the command line text file from the sdcard if it exists
2015-02-27 18:11:18 -05:00
FString CommandLineFilePath = GFilePathBase + FString ( " /UE4Game/ " ) + ( ! FApp : : IsGameNameEmpty ( ) ? FApp : : GetGameName ( ) : FPlatformProcess : : ExecutableName ( ) ) + FString ( " /UE4CommandLine.txt " ) ;
2014-03-14 14:13:41 -04:00
FILE * CommandLineFile = fopen ( TCHAR_TO_UTF8 ( * CommandLineFilePath ) , " r " ) ;
if ( CommandLineFile = = NULL )
{
// if that failed, try the lowercase version
CommandLineFilePath = CommandLineFilePath . Replace ( TEXT ( " UE4CommandLine.txt " ) , TEXT ( " ue4commandline.txt " ) ) ;
CommandLineFile = fopen ( TCHAR_TO_UTF8 ( * CommandLineFilePath ) , " r " ) ;
}
if ( CommandLineFile )
{
char CommandLine [ CMD_LINE_MAX ] ;
fgets ( CommandLine , ARRAY_COUNT ( CommandLine ) - 1 , CommandLineFile ) ;
fclose ( CommandLineFile ) ;
// chop off trailing spaces
while ( * CommandLine & & isspace ( CommandLine [ strlen ( CommandLine ) - 1 ] ) )
{
CommandLine [ strlen ( CommandLine ) - 1 ] = 0 ;
}
FCommandLine : : Append ( UTF8_TO_TCHAR ( CommandLine ) ) ;
}
}
//Main function called from the android entry point
int32 AndroidMain ( struct android_app * state )
{
FPlatformMisc : : LowLevelOutputDebugString ( L " Entered AndroidMain() " ) ;
// Force the first call to GetJavaEnv() to happen on the game thread, allowing subsequent calls to occur on any thread
2014-11-17 11:31:49 -05:00
FAndroidApplication : : GetJavaEnv ( ) ;
2014-03-14 14:13:41 -04:00
// adjust the file descriptor limits to allow as many open files as possible
rlimit cur_fd_limit ;
{
int result = getrlimit ( RLIMIT_NOFILE , & cur_fd_limit ) ;
//FPlatformMisc::LowLevelOutputDebugStringf(TEXT("(%d) Current fd limits: soft = %lld, hard = %lld"), result, cur_fd_limit.rlim_cur, cur_fd_limit.rlim_max);
}
{
rlimit new_limit = cur_fd_limit ;
new_limit . rlim_cur = cur_fd_limit . rlim_max ;
new_limit . rlim_max = cur_fd_limit . rlim_max ;
int result = setrlimit ( RLIMIT_NOFILE , & new_limit ) ;
//FPlatformMisc::LowLevelOutputDebugStringf(TEXT("(%d) Setting fd limits: soft = %lld, hard = %lld"), result, new_limit.rlim_cur, new_limit.rlim_max);
}
{
int result = getrlimit ( RLIMIT_NOFILE , & cur_fd_limit ) ;
//FPlatformMisc::LowLevelOutputDebugStringf(TEXT("(%d) Current fd limits: soft = %lld, hard = %lld"), result, cur_fd_limit.rlim_cur, cur_fd_limit.rlim_max);
}
// setup joystick support
// r19 is the first NDK to include AMotionEvenet_getAxisValue in the headers
// However, it has existed in the so since Honeycomb, query for the symbol
// to determine whether to try controller support
{
void * Lib = dlopen ( " libandroid.so " , 0 ) ;
if ( Lib ! = NULL )
{
GetAxes = ( GetAxesType ) dlsym ( Lib , " AMotionEvent_getAxisValue " ) ;
}
if ( GetAxes ! = NULL )
{
FPlatformMisc : : LowLevelOutputDebugStringf ( TEXT ( " Controller interface supported \n " ) ) ;
}
else
{
FPlatformMisc : : LowLevelOutputDebugStringf ( TEXT ( " Controller interface UNsupported \n " ) ) ;
}
}
2014-04-23 17:39:33 -04:00
// setup key filtering
static const uint32 MAX_KEY_MAPPINGS ( 256 ) ;
uint16 KeyCodes [ MAX_KEY_MAPPINGS ] ;
uint32 NumKeyCodes = FPlatformMisc : : GetKeyMap ( KeyCodes , nullptr , MAX_KEY_MAPPINGS ) ;
for ( int i = 0 ; i < NumKeyCodes ; + + i )
{
MappedKeyCodes . Add ( KeyCodes [ i ] ) ;
}
2014-04-23 22:06:21 -04:00
const int IgnoredGamepadKeyCodeCount = sizeof ( IgnoredGamepadKeyCodesList ) / sizeof ( uint16 ) ;
for ( int i = 0 ; i < IgnoredGamepadKeyCodeCount ; + + i )
{
IgnoredGamepadKeyCodes . Add ( IgnoredGamepadKeyCodesList [ i ] ) ;
}
2014-09-18 17:49:40 -04:00
const int ValidGamepadKeyCodeCount = sizeof ( ValidGamepadKeyCodesList ) / sizeof ( uint16 ) ;
for ( int i = 0 ; i < ValidGamepadKeyCodeCount ; + + i )
{
ValidGamepadKeyCodes . Add ( ValidGamepadKeyCodesList [ i ] ) ;
}
2014-07-10 14:41:00 -04:00
// wait for java activity onCreate to finish
while ( ! GResumeMainInit )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
FPlatformMisc : : MemoryBarrier ( ) ;
}
2014-05-22 09:13:12 -04:00
2014-03-14 14:13:41 -04:00
// read the command line file
InitCommandLine ( ) ;
FPlatformMisc : : LowLevelOutputDebugStringf ( TEXT ( " Final commandline: %s \n " ) , FCommandLine : : Get ( ) ) ;
2015-03-24 17:06:02 -04:00
EventHandlerEvent = FPlatformProcess : : GetSynchEventFromPool ( false ) ;
2014-06-27 15:48:23 -04:00
FPlatformMisc : : LowLevelOutputDebugString ( L " Created sync event " ) ;
FAppEventManager : : GetInstance ( ) - > SetEventHandlerEvent ( EventHandlerEvent ) ;
2014-07-10 14:41:00 -04:00
// ready for onCreate to complete
GEventHandlerInitialized = true ;
2014-11-17 11:31:49 -05:00
// Initialize file system access (i.e. mount OBBs, etc.).
// We need to do this really early for Android so that files in the
// OBBs and APK are found.
IPlatformFile : : GetPlatformPhysical ( ) . Initialize ( nullptr , FCommandLine : : Get ( ) ) ;
2015-03-24 17:06:02 -04:00
#if 0
for ( int32 i = 0 ; i < 10 ; i + + )
{
sleep ( 1 ) ;
FPlatformMisc : : LowLevelOutputDebugStringf ( TEXT ( " [Patch %d] " ) , i ) ;
}
FPlatformMisc : : LowLevelOutputDebugStringf ( TEXT ( " [Patch] : Dont Patch \n " ) ) ;
# endif
2014-03-14 14:13:41 -04:00
// initialize the engine
GEngineLoop . PreInit ( 0 , NULL , FCommandLine : : Get ( ) ) ;
2014-10-20 19:15:50 -04:00
// initialize HMDs
InitHMDs ( ) ;
2014-03-14 14:13:41 -04:00
UE_LOG ( LogAndroid , Display , TEXT ( " Passed PreInit() " ) ) ;
GLog - > SetCurrentThreadAsMasterThread ( ) ;
GEngineLoop . Init ( ) ;
UE_LOG ( LogAndroid , Log , TEXT ( " Passed GEngineLoop.Init() " ) ) ;
// tick until done
while ( ! GIsRequestingExit )
{
FAppEventManager : : GetInstance ( ) - > Tick ( ) ;
if ( ! FAppEventManager : : GetInstance ( ) - > IsGamePaused ( ) )
{
GEngineLoop . Tick ( ) ;
float timeToSleep = 0.05f ; //in seconds
sleep ( timeToSleep ) ;
}
# if !UE_BUILD_SHIPPING
// show console window on next game tick
if ( GShowConsoleWindowNextTick )
{
GShowConsoleWindowNextTick = false ;
AndroidThunkCpp_ShowConsoleWindow ( ) ;
}
# endif
}
UE_LOG ( LogAndroid , Log , TEXT ( " Exiting " ) ) ;
// exit out!
GEngineLoop . Exit ( ) ;
return 0 ;
}
static void * AndroidEventThreadWorker ( void * param )
{
struct android_app * state = ( struct android_app * ) param ;
2014-05-12 08:40:54 -04:00
FPlatformProcess : : SetThreadAffinityMask ( FPlatformAffinity : : GetMainGameMask ( ) ) ;
2014-04-23 16:36:00 -04:00
2014-03-14 14:13:41 -04:00
FPlatformMisc : : LowLevelOutputDebugString ( L " Entering event processing thread engine entry point " ) ;
ALooper * looper = ALooper_prepare ( ALOOPER_PREPARE_ALLOW_NON_CALLBACKS ) ;
ALooper_addFd ( looper , state - > msgread , LOOPER_ID_MAIN , ALOOPER_EVENT_INPUT , NULL ,
& state - > cmdPollSource ) ;
state - > looper = looper ;
FPlatformMisc : : LowLevelOutputDebugString ( L " Prepared looper for event thread " ) ;
//Assign the callbacks
state - > onAppCmd = OnAppCommandCB ;
state - > onInputEvent = HandleInputCB ;
FPlatformMisc : : LowLevelOutputDebugString ( L " Passed callback initialization " ) ;
2014-05-08 15:57:50 -04:00
// Acquire sensors
SensorManager = ASensorManager_getInstance ( ) ;
if ( NULL ! = SensorManager )
{
// Register for the various sensor events we want. Some
// may return NULL indicating that the sensor data is not
// available in the device. For those empty data will eventually
// get fed into the motion events.
SensorAccelerometer = ASensorManager_getDefaultSensor (
SensorManager , ASENSOR_TYPE_ACCELEROMETER ) ;
SensorGyroscope = ASensorManager_getDefaultSensor (
SensorManager , ASENSOR_TYPE_GYROSCOPE ) ;
// Create the queue for events to arrive.
SensorQueue = ASensorManager_createEventQueue (
2014-07-29 18:07:48 -04:00
SensorManager , state - > looper , LOOPER_ID_USER , HandleSensorEvents , NULL ) ;
2014-05-08 15:57:50 -04:00
}
FPlatformMisc : : LowLevelOutputDebugString ( L " Passed sensor initialization " ) ;
2014-03-14 14:13:41 -04:00
//continue to process events until the engine is shutting down
while ( ! GIsRequestingExit )
{
2014-09-18 17:49:40 -04:00
// FPlatformMisc::LowLevelOutputDebugString(L"AndroidEventThreadWorker");
2014-03-14 14:13:41 -04:00
AndroidProcessEvents ( state ) ;
2014-06-27 15:48:23 -04:00
sleep ( EventRefreshRate ) ; // this is really 0 since it takes int seconds.
2014-05-08 15:57:50 -04:00
}
2014-03-14 14:13:41 -04:00
UE_LOG ( LogAndroid , Log , TEXT ( " Exiting " ) ) ;
return NULL ;
}
//Called from the separate event processing thread
static void AndroidProcessEvents ( struct android_app * state )
{
int ident ;
int fdesc ;
int events ;
struct android_poll_source * source ;
2014-06-27 15:48:23 -04:00
while ( ( ident = ALooper_pollAll ( - 1 , & fdesc , & events , ( void * * ) & source ) ) > = 0 )
2014-03-14 14:13:41 -04:00
{
// process this event
if ( source )
2014-06-27 15:48:23 -04:00
{
2014-03-14 14:13:41 -04:00
source - > process ( state , source ) ;
2014-06-27 15:48:23 -04:00
}
2014-05-08 15:57:50 -04:00
}
}
2014-03-14 14:13:41 -04:00
pthread_t G_AndroidEventThread ;
2014-09-18 16:41:28 -04:00
struct android_app * GNativeAndroidApp = NULL ;
2014-03-14 14:13:41 -04:00
void android_main ( struct android_app * state )
{
FPlatformMisc : : LowLevelOutputDebugString ( L " Entering native app glue main function " ) ;
2014-09-18 16:41:28 -04:00
GNativeAndroidApp = state ;
check ( GNativeAndroidApp ) ;
2014-03-14 14:13:41 -04:00
pthread_attr_t otherAttr ;
pthread_attr_init ( & otherAttr ) ;
pthread_attr_setdetachstate ( & otherAttr , PTHREAD_CREATE_DETACHED ) ;
pthread_create ( & G_AndroidEventThread , & otherAttr , AndroidEventThreadWorker , state ) ;
FPlatformMisc : : LowLevelOutputDebugString ( L " Created event thread " ) ;
// Make sure glue isn't stripped.
app_dummy ( ) ;
//@todo android: replace with native activity, main loop off of UI thread, etc.
AndroidMain ( state ) ;
}
2014-11-19 11:04:52 -05:00
extern bool GAndroidGPUInfoReady ;
2014-03-14 14:13:41 -04:00
//Called from the event process thread
static int32_t HandleInputCB ( struct android_app * app , AInputEvent * event )
{
2014-04-23 18:45:11 -04:00
// FPlatformMisc::LowLevelOutputDebugStringf(L"INPUT - type: %x, action: %x, source: %x, keycode: %x, buttons: %x", AInputEvent_getType(event),
// AMotionEvent_getAction(event), AInputEvent_getSource(event), AKeyEvent_getKeyCode(event), AMotionEvent_getButtonState(event));
2014-03-14 14:13:41 -04:00
if ( AInputEvent_getType ( event ) = = AINPUT_EVENT_TYPE_MOTION )
{
int action = AMotionEvent_getAction ( event ) ;
int actionType = action & AMOTION_EVENT_ACTION_MASK ;
size_t actionPointer = ( size_t ) ( ( action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK ) > > AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT ) ;
bool isActionTargeted = ( actionType = = AMOTION_EVENT_ACTION_POINTER_DOWN | | actionType = = AMOTION_EVENT_ACTION_POINTER_UP ) ;
// trap Joystick events first, with fallthrough if there is no joystick support
if ( ( ( AInputEvent_getSource ( event ) & AINPUT_SOURCE_CLASS_JOYSTICK ) ! = 0 ) & &
( GetAxes ! = NULL ) & &
( actionType = = AMOTION_EVENT_ACTION_MOVE ) )
{
const int axisCount = sizeof ( AxisList ) / sizeof ( int32_t ) ;
int device = AInputEvent_getDeviceId ( event ) ;
for ( int axis = 0 ; axis < axisCount ; axis + + )
{
float val = GetAxes ( event , AxisList [ axis ] , 0 ) ;
FAndroidInputInterface : : JoystickAxisEvent ( device , AxisList [ axis ] , val ) ;
}
}
else
{
TArray < TouchInput > TouchesArray ;
TouchType type = TouchEnded ;
switch ( actionType )
{
case AMOTION_EVENT_ACTION_DOWN :
case AMOTION_EVENT_ACTION_POINTER_DOWN :
type = TouchBegan ;
break ;
case AMOTION_EVENT_ACTION_MOVE :
type = TouchMoved ;
break ;
case AMOTION_EVENT_ACTION_UP :
case AMOTION_EVENT_ACTION_POINTER_UP :
case AMOTION_EVENT_ACTION_CANCEL :
case AMOTION_EVENT_ACTION_OUTSIDE :
type = TouchEnded ;
break ;
}
size_t pointerCount = AMotionEvent_getPointerCount ( event ) ;
if ( pointerCount = = 0 )
{
return 1 ;
}
ANativeWindow * Window = ( ANativeWindow * ) FPlatformMisc : : GetHardwareWindow ( ) ;
if ( ! Window )
{
2014-04-23 17:39:33 -04:00
return 0 ;
2014-03-14 14:13:41 -04:00
}
int32_t Width = 0 ;
int32_t Height = 0 ;
if ( Window )
{
FAndroidWindow : : CalculateSurfaceSize ( Window , Width , Height ) ;
}
2014-11-19 11:04:52 -05:00
// make sure OpenGL context created before accepting touch events.. FAndroidWindow::GetScreenRect() may try to create it early from wrong thread if this is the first call
if ( ! GAndroidGPUInfoReady )
{
2015-01-19 18:26:22 -05:00
return 1 ;
2014-11-19 11:04:52 -05:00
}
2014-03-14 14:13:41 -04:00
FPlatformRect ScreenRect = FAndroidWindow : : GetScreenRect ( ) ;
if ( isActionTargeted )
{
if ( actionPointer < 0 | | pointerCount < ( int ) actionPointer )
{
return 1 ;
}
int pointerId = AMotionEvent_getPointerId ( event , actionPointer ) ;
float x = FMath : : Min < float > ( AMotionEvent_getX ( event , actionPointer ) / Width , 1.f ) ;
x * = ( ScreenRect . Right - 1 ) ;
float y = FMath : : Min < float > ( AMotionEvent_getY ( event , actionPointer ) / Height , 1.f ) ;
y * = ( ScreenRect . Bottom - 1 ) ;
UE_LOG ( LogAndroid , Verbose , TEXT ( " Received targeted motion event from pointer %u (id %d) action %d: (%.2f, %.2f) " ) , actionPointer , pointerId , action , x , y ) ;
TouchInput TouchMessage ;
TouchMessage . Handle = pointerId ;
TouchMessage . Type = type ;
TouchMessage . Position = FVector2D ( x , y ) ;
TouchMessage . LastPosition = FVector2D ( x , y ) ; //@todo android: AMotionEvent_getHistoricalRawX
TouchesArray . Add ( TouchMessage ) ;
}
else
{
for ( size_t i = 0 ; i < pointerCount ; + + i )
{
int pointerId = AMotionEvent_getPointerId ( event , i ) ;
float x = FMath : : Min < float > ( AMotionEvent_getX ( event , i ) / Width , 1.f ) ;
x * = ( ScreenRect . Right - 1 ) ;
float y = FMath : : Min < float > ( AMotionEvent_getY ( event , i ) / Height , 1.f ) ;
y * = ( ScreenRect . Bottom - 1 ) ;
UE_LOG ( LogAndroid , Verbose , TEXT ( " Received motion event from pointer %u (id %d) action %d: (%.2f, %.2f) " ) , i , action , AMotionEvent_getPointerId ( event , i ) , x , y ) ;
TouchInput TouchMessage ;
TouchMessage . Handle = AMotionEvent_getPointerId ( event , i ) ;
TouchMessage . Type = type ;
TouchMessage . Position = FVector2D ( x , y ) ;
TouchMessage . LastPosition = FVector2D ( x , y ) ; //@todo android: AMotionEvent_getHistoricalRawX
TouchesArray . Add ( TouchMessage ) ;
}
}
FAndroidInputInterface : : QueueTouchInput ( TouchesArray ) ;
# if !UE_BUILD_SHIPPING
2014-04-23 18:34:35 -04:00
if ( ( pointerCount > = 4 ) & & ( type = = TouchBegan ) )
2014-03-14 14:13:41 -04:00
{
2014-06-20 18:54:06 -04:00
bool bShowConsole = true ;
GConfig - > GetBool ( TEXT ( " /Script/Engine.InputSettings " ) , TEXT ( " bShowConsoleOnFourFingerTap " ) , bShowConsole , GInputIni ) ;
if ( bShowConsole )
{
GShowConsoleWindowNextTick = true ;
}
2014-03-14 14:13:41 -04:00
}
# endif
}
return 1 ;
}
else if ( AInputEvent_getType ( event ) = = AINPUT_EVENT_TYPE_KEY )
{
int keyCode = AKeyEvent_getKeyCode ( event ) ;
2014-04-23 22:06:21 -04:00
FPlatformMisc : : LowLevelOutputDebugStringf ( L " Received keycode: %d " , keyCode ) ;
2014-03-14 14:13:41 -04:00
//Trap Joystick events first, with fallthrough if there is no joystick support
2014-09-18 17:49:40 -04:00
if ( ( ( AInputEvent_getSource ( event ) & ( AINPUT_SOURCE_GAMEPAD | AINPUT_SOURCE_DPAD ) ) ! = 0 ) & & ( GetAxes ! = NULL ) & & ValidGamepadKeyCodes . Contains ( keyCode ) )
2014-03-14 14:13:41 -04:00
{
2014-04-23 22:06:21 -04:00
if ( IgnoredGamepadKeyCodes . Contains ( keyCode ) )
{
return 0 ;
}
2014-03-14 14:13:41 -04:00
int device = AInputEvent_getDeviceId ( event ) ;
bool down = AKeyEvent_getAction ( event ) ! = AKEY_EVENT_ACTION_UP ;
FAndroidInputInterface : : JoystickButtonEvent ( device , keyCode , down ) ;
FPlatformMisc : : LowLevelOutputDebugStringf ( L " Received gamepad button: %d " , keyCode ) ;
}
else
{
FPlatformMisc : : LowLevelOutputDebugStringf ( L " Received key event: %d " , keyCode ) ;
2014-04-23 17:39:33 -04:00
// only handle mapped key codes
if ( ! MappedKeyCodes . Contains ( keyCode ) )
{
return 0 ;
}
2014-03-14 14:13:41 -04:00
FDeferredAndroidMessage Message ;
Message . messageType = AKeyEvent_getAction ( event ) = = AKEY_EVENT_ACTION_UP ? MessageType_KeyUp : MessageType_KeyDown ;
Message . KeyEventData . unichar = keyCode ;
Message . KeyEventData . keyId = keyCode ;
2014-09-18 17:49:40 -04:00
Message . KeyEventData . modifier = AKeyEvent_getMetaState ( event ) ;
2014-03-14 14:13:41 -04:00
Message . KeyEventData . isRepeat = AKeyEvent_getAction ( event ) = = AKEY_EVENT_ACTION_MULTIPLE ;
FAndroidInputInterface : : DeferMessage ( Message ) ;
2015-04-22 17:01:01 -04:00
// allow event to be generated for volume up and down, but conditionally allow system to handle it, too
if ( keyCode = = AKEYCODE_VOLUME_UP | | keyCode = = AKEYCODE_VOLUME_DOWN )
{
if ( FPlatformMisc : : GetVolumeButtonsHandledBySystem ( ) )
{
return 0 ;
}
}
2014-03-14 14:13:41 -04:00
}
return 1 ;
}
return 0 ;
}
static void onNativeWindowResized ( ANativeActivity * activity , ANativeWindow * window )
{
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_WINDOW_CHANGED , window ) ;
}
//Called from the event process thread
static void OnAppCommandCB ( struct android_app * app , int32_t cmd )
{
FPlatformMisc : : LowLevelOutputDebugStringf ( L " OnAppCommandCB cmd: %u " , cmd ) ;
switch ( cmd )
{
case APP_CMD_SAVE_STATE :
/**
* Command from main thread : the app should generate a new saved state
* for itself , to restore from later if needed . If you have saved state ,
* allocate it with malloc and place it in android_app . savedState with
* the size in android_app . savedStateSize . The will be freed for you
* later .
*/
// the OS asked us to save the state of the app
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_SAVE_STATE " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_SAVE_STATE ) ;
break ;
case APP_CMD_INIT_WINDOW :
/**
* Command from main thread : a new ANativeWindow is ready for use . Upon
* receiving this command , android_app - > window will contain the new window
* surface .
*/
// get the window ready for showing
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_INIT_WINDOW " ) ) ;
FAppEventManager : : GetInstance ( ) - > HandleWindowCreated ( app - > pendingWindow ) ;
break ;
case APP_CMD_TERM_WINDOW :
/**
* Command from main thread : the existing ANativeWindow needs to be
* terminated . Upon receiving this command , android_app - > window still
* contains the existing window ; after calling android_app_exec_cmd
* it will be set to NULL .
*/
// clean up the window because it is being hidden/closed
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_TERM_WINDOW " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_WINDOW_DESTROYED , NULL ) ;
break ;
case APP_CMD_LOST_FOCUS :
/**
* Command from main thread : the app ' s activity window has lost
* input focus .
*/
// if the app lost focus, avoid unnecessary processing (like monitoring the accelerometer)
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_LOST_FOCUS " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_WINDOW_LOST_FOCUS , NULL ) ;
2014-05-08 15:57:50 -04:00
if ( NULL ! = SensorQueue )
{
if ( NULL ! = SensorAccelerometer )
ASensorEventQueue_disableSensor ( SensorQueue , SensorAccelerometer ) ;
if ( NULL ! = SensorGyroscope )
ASensorEventQueue_disableSensor ( SensorQueue , SensorGyroscope ) ;
}
2014-03-14 14:13:41 -04:00
break ;
case APP_CMD_GAINED_FOCUS :
/**
* Command from main thread : the app ' s activity window has gained
* input focus .
*/
// bring back a certain functionality, like monitoring the accelerometer
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_GAINED_FOCUS " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_WINDOW_GAINED_FOCUS , NULL ) ;
2014-05-08 15:57:50 -04:00
if ( NULL ! = SensorQueue )
{
if ( NULL ! = SensorAccelerometer )
{
ASensorEventQueue_enableSensor ( SensorQueue , SensorAccelerometer ) ;
ASensorEventQueue_setEventRate ( SensorQueue , SensorAccelerometer , SensorDelayGame ) ;
}
if ( NULL ! = SensorGyroscope )
{
ASensorEventQueue_enableSensor ( SensorQueue , SensorGyroscope ) ;
ASensorEventQueue_setEventRate ( SensorQueue , SensorGyroscope , SensorDelayGame ) ;
}
}
2014-03-14 14:13:41 -04:00
break ;
case APP_CMD_INPUT_CHANGED :
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_INPUT_CHANGED " ) ) ;
break ;
case APP_CMD_WINDOW_RESIZED :
/**
* Command from main thread : the current ANativeWindow has been resized .
* Please redraw with its new size .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_WINDOW_RESIZED " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_WINDOW_RESIZED ) ;
break ;
case APP_CMD_WINDOW_REDRAW_NEEDED :
/**
* Command from main thread : the system needs that the current ANativeWindow
* be redrawn . You should redraw the window before handing this to
* android_app_exec_cmd ( ) in order to avoid transient drawing glitches .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_WINDOW_REDRAW_NEEDED " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_WINDOW_REDRAW_NEEDED ) ;
break ;
case APP_CMD_CONTENT_RECT_CHANGED :
/**
* Command from main thread : the content area of the window has changed ,
* such as from the soft input window being shown or hidden . You can
* find the new content rect in android_app : : contentRect .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_CONTENT_RECT_CHANGED " ) ) ;
break ;
case APP_CMD_CONFIG_CHANGED :
/**
* Command from main thread : the current device configuration has changed .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_CONFIG_CHANGED " ) ) ;
break ;
case APP_CMD_LOW_MEMORY :
/**
* Command from main thread : the system is running low on memory .
* Try to reduce your memory use .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_LOW_MEMORY " ) ) ;
break ;
case APP_CMD_START :
/**
* Command from main thread : the app ' s activity has been started .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_START " ) ) ;
app - > activity - > callbacks - > onNativeWindowResized = onNativeWindowResized ; //currently not handled in glue code.
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_ON_START ) ;
break ;
case APP_CMD_RESUME :
/**
* Command from main thread : the app ' s activity has been resumed .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_RESUME " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_ON_RESUME ) ;
break ;
case APP_CMD_PAUSE :
/**
* Command from main thread : the app ' s activity has been paused .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_PAUSE " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_ON_PAUSE ) ;
break ;
case APP_CMD_STOP :
/**
* Command from main thread : the app ' s activity has been stopped .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_STOP " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_ON_STOP ) ;
break ;
case APP_CMD_DESTROY :
/**
* Command from main thread : the app ' s activity is being destroyed ,
* and waiting for the app thread to clean up and exit before proceeding .
*/
UE_LOG ( LogAndroid , Log , TEXT ( " Case APP_CMD_DESTROY " ) ) ;
FAppEventManager : : GetInstance ( ) - > EnqueueAppEvent ( APP_EVENT_STATE_ON_PAUSE ) ;
break ;
}
2014-06-27 15:48:23 -04:00
if ( EventHandlerEvent )
EventHandlerEvent - > Trigger ( ) ;
2014-03-14 14:13:41 -04:00
}
2014-07-29 18:07:48 -04:00
static int HandleSensorEvents ( int fd , int events , void * data )
{
// It's not possible to discern sequencing across sensors in
// Android. So we average out all the sensor events on one cycle
// and post a single motion sensor data point. We also need
// to synthesize additional information.
FVector current_accelerometer ( 0 , 0 , 0 ) ;
FVector current_gyroscope ( 0 , 0 , 0 ) ;
int32 current_accelerometer_sample_count = 0 ;
int32 current_gyroscope_sample_count = 0 ;
static FVector last_accelerometer ( 0 , 0 , 0 ) ;
if ( NULL ! = SensorAccelerometer | | NULL ! = SensorGyroscope )
{
ASensorEvent sensor_event ;
while ( ASensorEventQueue_getEvents ( SensorQueue , & sensor_event , 1 ) > 0 )
{
if ( ASENSOR_TYPE_ACCELEROMETER = = sensor_event . type )
{
current_accelerometer . X + = sensor_event . acceleration . x ;
current_accelerometer . Y + = sensor_event . acceleration . y ;
current_accelerometer . Z + = sensor_event . acceleration . z ;
current_accelerometer_sample_count + = 1 ;
}
else if ( ASENSOR_TYPE_GYROSCOPE = = sensor_event . type )
{
current_gyroscope . X + = sensor_event . vector . pitch ;
current_gyroscope . Y + = sensor_event . vector . azimuth ;
current_gyroscope . Z + = sensor_event . vector . roll ;
current_gyroscope_sample_count + = 1 ;
}
}
}
if ( current_accelerometer_sample_count > 0 )
{
// Do simple average of the samples we just got.
current_accelerometer / = float ( current_accelerometer_sample_count ) ;
last_accelerometer = current_accelerometer ;
}
else
{
current_accelerometer = last_accelerometer ;
}
if ( current_gyroscope_sample_count > 0 )
{
// Do simple average of the samples we just got.
current_gyroscope / = float ( current_gyroscope_sample_count ) ;
}
// If we have motion samples we generate the single event.
if ( current_accelerometer_sample_count > 0 | |
current_gyroscope_sample_count > 0 )
{
// The data we compose the motion event from.
FVector current_tilt ( 0 , 0 , 0 ) ;
FVector current_rotation_rate ( 0 , 0 , 0 ) ;
FVector current_gravity ( 0 , 0 , 0 ) ;
FVector current_acceleration ( 0 , 0 , 0 ) ;
// Buffered, historical, motion data.
static FVector last_tilt ( 0 , 0 , 0 ) ;
static FVector last_gravity ( 0 , 0 , 0 ) ;
// We use a low-pass filter to synthesize the gravity
// vector.
static bool first_acceleration_sample = true ;
if ( ! first_acceleration_sample )
{
current_gravity
= last_gravity * SampleDecayRate
+ current_accelerometer * ( 1.0f - SampleDecayRate ) ;
}
first_acceleration_sample = false ;
// Calc the tilt from the accelerometer as it's not
// available directly.
2014-11-26 10:01:12 -05:00
FVector accelerometer_dir = - current_accelerometer . GetSafeNormal ( ) ;
2014-07-29 18:07:48 -04:00
float current_pitch
= FMath : : Atan2 ( accelerometer_dir . Y , accelerometer_dir . Z ) ;
float current_roll
= - FMath : : Atan2 ( accelerometer_dir . X , accelerometer_dir . Z ) ;
current_tilt . X = current_pitch ;
current_tilt . Y = 0 ;
current_tilt . Z = current_roll ;
// And take out the gravity from the accel to get
// the linear acceleration.
current_acceleration = current_accelerometer - current_gravity ;
if ( current_gyroscope_sample_count > 0 )
{
// The rotation rate is the what the gyroscope gives us.
current_rotation_rate = current_gyroscope ;
}
else if ( NULL = = SensorGyroscope )
{
// If we don't have a gyroscope at all we need to calc a rotation
// rate from our calculated tilt and a delta.
current_rotation_rate = current_tilt - last_tilt ;
}
// Finally record the motion event with all the data.
FAndroidInputInterface : : QueueMotionData ( current_tilt ,
current_rotation_rate , current_gravity , current_acceleration ) ;
// Update history values.
last_tilt = current_tilt ;
last_gravity = current_gravity ;
// UE_LOG(LogTemp, Log, TEXT("MOTION: tilt = %s, rotation-rate = %s, gravity = %s, acceleration = %s"),
// *current_tilt.ToCompactString(),
// *current_rotation_rate.ToCompactString(),
// *current_gravity.ToCompactString(),
// *current_acceleration.ToCompactString());
}
// Indicate we want to keep getting events.
return 1 ;
}
2014-03-14 14:13:41 -04:00
//Native-defined functions
//This function is declared in the Java-defined class, GameActivity.java: "public native void nativeConsoleCommand(String commandString);"
extern " C " void Java_com_epicgames_ue4_GameActivity_nativeConsoleCommand ( JNIEnv * jenv , jobject thiz , jstring commandString )
{
const char * javaChars = jenv - > GetStringUTFChars ( commandString , 0 ) ;
new ( GEngine - > DeferredCommands ) FString ( UTF8_TO_TCHAR ( javaChars ) ) ;
//Release the string
jenv - > ReleaseStringUTFChars ( commandString , javaChars ) ;
}
2014-04-23 19:40:10 -04:00
2014-10-20 19:15:50 -04:00
// This is called from the Java UI thread for initializing VR HMDs
extern " C " void Java_com_epicgames_ue4_GameActivity_nativeInitHMDs ( JNIEnv * jenv , jobject thiz )
{
for ( auto HMDModuleIt = GHMDImplementations . CreateIterator ( ) ; HMDModuleIt ; + + HMDModuleIt )
{
( * HMDModuleIt ) - > PreInit ( ) ;
}
GHMDsInitialized = true ;
}
2014-05-22 09:13:12 -04:00
2014-08-22 10:24:13 -04:00
extern " C " void Java_com_epicgames_ue4_GameActivity_nativeSetAndroidVersionInformation ( JNIEnv * jenv , jobject thiz , jstring androidVersion , jstring phoneMake , jstring phoneModel , jstring osLanguage )
2014-05-22 09:13:12 -04:00
{
const char * javaAndroidVersion = jenv - > GetStringUTFChars ( androidVersion , 0 ) ;
FString UEAndroidVersion = FString ( UTF8_TO_TCHAR ( javaAndroidVersion ) ) ;
const char * javaPhoneMake = jenv - > GetStringUTFChars ( phoneMake , 0 ) ;
FString UEPhoneMake = FString ( UTF8_TO_TCHAR ( javaPhoneMake ) ) ;
const char * javaPhoneModel = jenv - > GetStringUTFChars ( phoneModel , 0 ) ;
FString UEPhoneModel = FString ( UTF8_TO_TCHAR ( javaPhoneModel ) ) ;
2014-08-22 10:24:13 -04:00
const char * javaOSLanguage = jenv - > GetStringUTFChars ( osLanguage , 0 ) ;
FString UEOSLanguage = FString ( UTF8_TO_TCHAR ( javaOSLanguage ) ) ;
FAndroidMisc : : SetVersionInfo ( UEAndroidVersion , UEPhoneMake , UEPhoneModel , UEOSLanguage ) ;
2014-09-18 17:49:40 -04:00
}
2015-01-20 12:23:03 -05:00
2015-01-27 18:30:20 -05:00
bool WaitForAndroidLoseFocusEvent ( double TimeoutSeconds )
2015-01-20 12:23:03 -05:00
{
2015-01-27 18:30:20 -05:00
return FAppEventManager : : GetInstance ( ) - > WaitForEventInQueue ( EAppEventState : : APP_EVENT_STATE_WINDOW_LOST_FOCUS , TimeoutSeconds ) ;
2015-01-20 12:23:03 -05:00
}