Files
UnrealEngineUWP/Engine/Source/Runtime/Launch/Private/Android/LaunchAndroid.cpp
Jaroslaw Palczynski 724ea452a5 Refactoring thread affinity settings.
There was a bug in setting affinity of a thread that assumed affinity from lookup table with key being a thread name. When names was appended with consecutive numbers (e.g. "RenderingThread 1") the mechanism failed. Refactored this to use special static consts describing affinity override'able by different platforms for different affinity types + possibility of setting affinity per thread.
#codereview Jaroslaw.Surowiec

[CL 2070197 by Jaroslaw Palczynski in Main branch]
2014-05-12 08:40:54 -04:00

876 lines
27 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"
#include <android/sensor.h>
#include "Core.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;
// List of gamepad keycodes to ignore
static const uint16 IgnoredGamepadKeyCodesList[] =
{
AKEYCODE_VOLUME_UP,
AKEYCODE_VOLUME_DOWN
};
// map of gamepad keycodes that should be ignored
static TSet<uint16> IgnoredGamepadKeyCodes;
// -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();
// Base path for file accesses
extern FString GFilePathBase;
/** The global EngineLoop instance */
FEngineLoop GEngineLoop;
bool GShowConsoleWindowNextTick = false;
static void AndroidProcessEvents(struct android_app* state);
//Event thread stuff
static void* AndroidEventThreadWorker(void* param);
// How often to process (read & dispatch) events, in seconds.
static const float EventRefreshRate = 1.0f / 20.0f;
//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;
// 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;
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 = GFilePathBase + FString("/") + (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]);
}
const int IgnoredGamepadKeyCodeCount = sizeof(IgnoredGamepadKeyCodesList)/sizeof(uint16);
for (int i = 0; i < IgnoredGamepadKeyCodeCount; ++i)
{
IgnoredGamepadKeyCodes.Add(IgnoredGamepadKeyCodesList[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()"));
// Hack to initialize Google Play if enabled until we get the full subsystem online.
// GConfig should be valid here.
if (JNIEnv* Env = GetJavaEnv())
{
Env->CallVoidMethod(GJavaGlobalThis, JDef_GameActivity::AndroidThunkJava_GooglePlayConnect);
}
// 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;
FPlatformProcess::SetThreadAffinityMask(FPlatformAffinity::GetMainGameMask());
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");
// 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(
SensorManager, state->looper, LOOPER_ID_USER, NULL, NULL);
}
FPlatformMisc::LowLevelOutputDebugString(L"Passed sensor 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);
sleep(EventRefreshRate);
}
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;
// 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);
while((ident = ALooper_pollAll(0, &fdesc, &events, (void**)&source)) >= 0)
{
// process this event
if (source)
source->process(state, source);
// process sensor events
if (ident == LOOPER_ID_USER)
{
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.
FVector accelerometer_dir = -current_accelerometer.SafeNormal();
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());
}
}
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)
{
// 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));
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);
FPlatformMisc::LowLevelOutputDebugStringf(L"Received keycode: %d", keyCode);
//Trap Joystick events first, with fallthrough if there is no joystick support
if (((AInputEvent_getSource(event) & (AINPUT_SOURCE_GAMEPAD | AINPUT_SOURCE_DPAD)) != 0) && (GetAxes != NULL))
{
if (IgnoredGamepadKeyCodes.Contains(keyCode))
{
return 0;
}
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);
if (NULL != SensorQueue)
{
if (NULL != SensorAccelerometer)
ASensorEventQueue_disableSensor(SensorQueue, SensorAccelerometer);
if (NULL != SensorGyroscope)
ASensorEventQueue_disableSensor(SensorQueue, SensorGyroscope);
}
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);
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);
}
}
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);
}
//This function is declared in the Java-defined class, GameActivity.java: "public native void nativeIsGooglePlayEnabled(String commandString);"
extern "C" jboolean Java_com_epicgames_ue4_GameActivity_nativeIsGooglePlayEnabled(JNIEnv* jenv, jobject thiz)
{
bool bEnabled = true;
GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bEnableGooglePlaySupport"), bEnabled, GEngineIni);
UE_LOG(LogOnline, Log, TEXT("Checking whether Google Play is enabled. bEnableGooglePlaySupport = %d"), bEnabled);
return bEnabled;
}