Files
UnrealEngineUWP/Engine/Source/Runtime/Launch/Private/Android/LaunchAndroid.cpp
JJ Hoesing 5dfb74209b - Make four finger touch more reliable. Fixes TTP:331266
[CL 2044741 by JJ Hoesing in Main branch]
2014-04-23 18:34:35 -04:00

657 lines
20 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#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"
// 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,
};
// map of all supported keycodes
static TSet<uint16> MappedKeyCodes;
// -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;
}
extern void AndroidThunkCpp_ShowConsoleWindow();
/** The global EngineLoop instance */
FEngineLoop GEngineLoop;
bool GShowConsoleWindowNextTick = false;
static void AndroidProcessEvents(struct android_app* state);
//Event thread stuff
static void* AndroidEventThreadWorker(void* param);
//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
bool GHasInterruptionRequest = false;
bool GIsInterrupted = false;
void UpdateGameInterruptions()
{
// Check for game suspension.
if(GHasInterruptionRequest)
{
// Suspend the renderer.
if(GUseThreadedRendering)
{
FlushRenderingCommands();
StopRenderingThread();
}
else
{
RHIReleaseThreadOwnership();
}
// Flag the suspended state.
GIsInterrupted = true;
// Wait for resume.
while(GHasInterruptionRequest)
{
FPlatformProcess::Sleep(0.1f);
}
// Flag the resume state.
GIsInterrupted = false;
// Reset the window surface.
RHIAcquireThreadOwnership();
RHIReleaseThreadOwnership();
// Resume the renderer.
if(GUseThreadedRendering)
{
StartRenderingThread();
}
else
{
RHIAcquireThreadOwnership();
}
}
}
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
FString CommandLineFilePath = FString("/mnt/sdcard/") + (GGameName[0] ? GGameName : TEXT("UE4Game")) + FString("/UE4CommandLine.txt");
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
GetJavaEnv();
// 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"));
}
}
// 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]);
}
// read the command line file
InitCommandLine();
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Final commandline: %s\n"), FCommandLine::Get());
// initialize the engine
GEngineLoop.PreInit(0, NULL, FCommandLine::Get());
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;
uint64 GameThreadAffinity = AffinityManagerGetAffinity( TEXT("MainGame"));
FPlatformProcess::SetThreadAffinityMask( GameThreadAffinity );
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");
//continue to process events until the engine is shutting down
while (!GIsRequestingExit)
{
static int count = 1;
//LOGD("Event process loop #%d", count);
count++;
AndroidProcessEvents(state);
float timeToSleep = 0.05f; //in seconds
sleep(timeToSleep);
}
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;
while((ident = ALooper_pollAll(0, &fdesc, &events, (void**)&source)) >= 0)
{
// process this event
if (source)
source->process(state, source);
}
}
pthread_t G_AndroidEventThread;
void android_main(struct android_app* state)
{
FPlatformMisc::LowLevelOutputDebugString(L"Entering native app glue main function");
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);
}
//Called from the event process thread
static int32_t HandleInputCB(struct android_app* app, AInputEvent* event)
{
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)
{
return 0;
}
int32_t Width = 0 ;
int32_t Height = 0 ;
if(Window)
{
FAndroidWindow::CalculateSurfaceSize(Window, Width, Height);
}
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
if ((pointerCount >= 4) && (type == TouchBegan))
{
GShowConsoleWindowNextTick = true;
}
#endif
}
return 1;
}
else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY)
{
int keyCode = AKeyEvent_getKeyCode(event);
//Trap Joystick events first, with fallthrough if there is no joystick support
if (((AInputEvent_getSource(event) & AINPUT_SOURCE_CLASS_JOYSTICK) != 0) && (GetAxes != NULL))
{
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);
// only handle mapped key codes
if (!MappedKeyCodes.Contains(keyCode))
{
return 0;
}
FDeferredAndroidMessage Message;
Message.messageType = AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP ? MessageType_KeyUp : MessageType_KeyDown;
Message.KeyEventData.unichar = keyCode;
Message.KeyEventData.keyId = keyCode;
Message.KeyEventData.isRepeat = AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE;
FAndroidInputInterface::DeferMessage(Message);
}
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);
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);
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;
}
}
//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);
}