2012-09-06 16:14:12 -07:00
|
|
|
// Copyright (C) 2002-2007 Nikolaus Gebhardt
|
|
|
|
// Copyright (C) 2007-2011 Christian Stehno
|
|
|
|
// This file is part of the "Irrlicht Engine".
|
|
|
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
|
|
|
|
|
|
#include "CIrrDeviceAndroid.h"
|
|
|
|
|
|
|
|
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
|
|
|
|
|
|
|
#include "os.h"
|
|
|
|
#include "CFileSystem.h"
|
2013-10-17 16:11:56 -07:00
|
|
|
#include "CAndroidAssetReader.h"
|
2012-09-06 16:14:12 -07:00
|
|
|
#include "CAndroidAssetFileArchive.h"
|
2013-10-16 23:50:30 -07:00
|
|
|
#include "CEGLManager.h"
|
2013-10-17 16:11:56 -07:00
|
|
|
#include "ISceneManager.h"
|
|
|
|
#include "IGUIEnvironment.h"
|
2013-10-25 17:02:19 -07:00
|
|
|
#include "CEGLManager.h"
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-11-01 05:35:58 -07:00
|
|
|
namespace irr
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
namespace video
|
|
|
|
{
|
2013-11-01 05:35:58 -07:00
|
|
|
IVideoDriver* createOGLES1Driver(const SIrrlichtCreationParameters& params,
|
2013-11-13 15:20:30 -08:00
|
|
|
io::IFileSystem* io, video::IContextManager* contextManager);
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-11-01 05:35:58 -07:00
|
|
|
IVideoDriver* createOGLES2Driver(const SIrrlichtCreationParameters& params,
|
2013-10-29 16:42:00 -07:00
|
|
|
video::SExposedVideoData& data, io::IFileSystem* io, video::IContextManager* contextManager);
|
2013-11-01 05:35:58 -07:00
|
|
|
}
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace irr
|
|
|
|
{
|
|
|
|
|
|
|
|
CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters& param)
|
2013-10-17 16:11:56 -07:00
|
|
|
: CIrrDeviceStub(param), Focused(false), Initialized(false), Paused(true)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
#ifdef _DEBUG
|
2012-09-06 16:14:12 -07:00
|
|
|
setDebugName("CIrrDeviceAndroid");
|
2013-10-17 16:11:56 -07:00
|
|
|
#endif
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2012-09-06 16:14:12 -07:00
|
|
|
// Get the interface to the native Android activity.
|
2013-10-17 16:11:56 -07:00
|
|
|
Android = (android_app*)(param.PrivateData);
|
|
|
|
|
|
|
|
io::CAndroidAssetReader::Activity = Android->activity;
|
|
|
|
io::CAndroidAssetFileArchive::Activity = Android->activity;
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2012-09-06 16:14:12 -07:00
|
|
|
// Set the private data so we can use it in any static callbacks.
|
|
|
|
Android->userData = this;
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
// Set the default command handler. This is a callback function that the Android
|
|
|
|
// OS invokes to send the native activity messages.
|
2012-09-06 16:14:12 -07:00
|
|
|
Android->onAppCmd = handleAndroidCommand;
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
// Create a sensor manager to recieve touch screen events from the java acivity.
|
2012-09-06 16:14:12 -07:00
|
|
|
SensorManager = ASensorManager_getInstance();
|
2013-10-17 16:11:56 -07:00
|
|
|
SensorEventQueue = ASensorManager_createEventQueue(SensorManager, Android->looper, LOOPER_ID_USER, 0, 0);
|
2012-09-06 16:14:12 -07:00
|
|
|
Android->onInputEvent = handleInput;
|
2013-10-17 16:11:56 -07:00
|
|
|
|
|
|
|
// Create EGL manager.
|
2013-11-13 15:20:30 -08:00
|
|
|
ContextManager = new video::CEGLManager();
|
2013-10-17 16:11:56 -07:00
|
|
|
|
2012-09-06 16:14:12 -07:00
|
|
|
os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG);
|
2013-10-17 16:11:56 -07:00
|
|
|
|
|
|
|
do
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
s32 Events = 0;
|
|
|
|
android_poll_source* Source = 0;
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
while ((ALooper_pollAll(((Focused && !Paused) || !Initialized) ? 0 : -1, 0, &Events, (void**)&Source)) >= 0)
|
2013-11-01 05:35:58 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
if(Source)
|
|
|
|
Source->process(Android, Source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while(!Initialized);
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
CIrrDeviceAndroid::~CIrrDeviceAndroid()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
if (GUIEnvironment)
|
|
|
|
{
|
|
|
|
GUIEnvironment->drop();
|
|
|
|
GUIEnvironment = 0;
|
|
|
|
}
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
if (SceneManager)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
SceneManager->drop();
|
|
|
|
SceneManager = 0;
|
|
|
|
}
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
if (VideoDriver)
|
|
|
|
{
|
|
|
|
VideoDriver->drop();
|
|
|
|
VideoDriver = 0;
|
|
|
|
}
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
bool CIrrDeviceAndroid::run()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
if (!Initialized)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
os::Timer::tick();
|
|
|
|
|
|
|
|
s32 Events = 0;
|
|
|
|
android_poll_source* Source = 0;
|
|
|
|
|
|
|
|
while ((ALooper_pollAll(((Focused && !Paused) || !Initialized) ? 0 : -1, 0, &Events, (void**)&Source)) >= 0)
|
2013-11-01 05:35:58 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
if(Source)
|
|
|
|
Source->process(Android, Source);
|
|
|
|
|
|
|
|
if(!Initialized)
|
|
|
|
break;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
2013-10-17 16:11:56 -07:00
|
|
|
|
|
|
|
return Initialized;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::yield()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIrrDeviceAndroid::setWindowCaption(const wchar_t* text)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CIrrDeviceAndroid::present(video::IImage* surface, void* windowId, core::rect<s32>* srcClip)
|
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
return true;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
bool CIrrDeviceAndroid::isWindowActive() const
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
return (Focused && !Paused);
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
bool CIrrDeviceAndroid::isWindowFocused() const
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
return Focused;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
bool CIrrDeviceAndroid::isWindowMinimized() const
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
return !Focused;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::closeDevice()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-29 16:42:00 -07:00
|
|
|
ANativeActivity_finish(Android->activity);
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::setResizable(bool resize)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::minimizeWindow()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::maximizeWindow()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::restoreWindow()
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-04-17 22:10:42 -07:00
|
|
|
core::position2di CIrrDeviceAndroid::getWindowPosition()
|
|
|
|
{
|
|
|
|
return core::position2di(0, 0);
|
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
E_DEVICE_TYPE CIrrDeviceAndroid::getType() const
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
return EIDT_ANDROID;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
void CIrrDeviceAndroid::handleAndroidCommand(android_app* app, int32_t cmd)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-11-01 05:35:58 -07:00
|
|
|
CIrrDeviceAndroid* Device = (CIrrDeviceAndroid*)app->userData;
|
|
|
|
|
|
|
|
switch (cmd)
|
|
|
|
{
|
|
|
|
case APP_CMD_SAVE_STATE:
|
|
|
|
os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG);
|
|
|
|
break;
|
|
|
|
case APP_CMD_INIT_WINDOW:
|
2012-09-06 16:14:12 -07:00
|
|
|
os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG);
|
2013-11-01 05:35:58 -07:00
|
|
|
Device->getExposedVideoData().OGLESAndroid.window = app->window;
|
2013-10-23 13:47:36 -07:00
|
|
|
|
|
|
|
if (Device->CreationParams.WindowSize.Width == 0 || Device->CreationParams.WindowSize.Height == 0)
|
|
|
|
{
|
|
|
|
Device->CreationParams.WindowSize.Width = ANativeWindow_getWidth(app->window);
|
|
|
|
Device->CreationParams.WindowSize.Height = ANativeWindow_getHeight(app->window);
|
|
|
|
}
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2013-11-13 15:20:30 -08:00
|
|
|
Device->getContextManager()->initialize(CreationParams, ExposedVideoData);
|
|
|
|
Device->getContextManager()->generateSurface();
|
|
|
|
Device->getContextManager()->generateContext();
|
|
|
|
Device->getContextManager()->activateContext(Device->getContextManager()->getContext());
|
2013-10-29 16:42:00 -07:00
|
|
|
|
|
|
|
if (!Device->Initialized)
|
|
|
|
{
|
|
|
|
io::CAndroidAssetFileArchive* Assets = new io::CAndroidAssetFileArchive(false, false);
|
|
|
|
Assets->addDirectory("media");
|
|
|
|
Device->FileSystem->addFileArchive(Assets);
|
|
|
|
|
|
|
|
Device->createDriver();
|
2013-11-01 05:35:58 -07:00
|
|
|
|
|
|
|
if (Device->VideoDriver)
|
2013-10-29 16:42:00 -07:00
|
|
|
Device->createGUIAndScene();
|
|
|
|
}
|
2013-11-01 05:35:58 -07:00
|
|
|
Device->Initialized = true;
|
|
|
|
break;
|
|
|
|
case APP_CMD_TERM_WINDOW:
|
2013-10-16 23:50:30 -07:00
|
|
|
os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG);
|
2013-11-01 05:35:58 -07:00
|
|
|
Device->getContextManager()->destroySurface();
|
|
|
|
break;
|
|
|
|
case APP_CMD_GAINED_FOCUS:
|
|
|
|
os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG);
|
2013-10-17 16:11:56 -07:00
|
|
|
Device->Focused = true;
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
|
|
|
case APP_CMD_LOST_FOCUS:
|
2012-09-06 16:14:12 -07:00
|
|
|
os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG);
|
2013-11-01 05:35:58 -07:00
|
|
|
Device->Focused = false;
|
|
|
|
break;
|
2012-09-06 16:14:12 -07:00
|
|
|
case APP_CMD_DESTROY:
|
|
|
|
os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG);
|
2013-10-17 16:11:56 -07:00
|
|
|
Device->Initialized = false;
|
2012-09-06 16:14:12 -07:00
|
|
|
break;
|
|
|
|
case APP_CMD_PAUSE:
|
|
|
|
os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG);
|
2013-10-17 16:11:56 -07:00
|
|
|
Device->Paused = true;
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
2012-09-06 16:14:12 -07:00
|
|
|
case APP_CMD_STOP:
|
|
|
|
os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG);
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
2012-09-06 16:14:12 -07:00
|
|
|
case APP_CMD_RESUME:
|
|
|
|
os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG);
|
2013-10-17 16:11:56 -07:00
|
|
|
Device->Paused = false;
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
2012-09-06 16:14:12 -07:00
|
|
|
default:
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
|
|
|
}
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
s32 CIrrDeviceAndroid::handleInput(android_app* app, AInputEvent* androidEvent)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
CIrrDeviceAndroid* Device = (CIrrDeviceAndroid*)app->userData;
|
|
|
|
s32 Status = 0;
|
|
|
|
|
|
|
|
if (AInputEvent_getType(androidEvent) == AINPUT_EVENT_TYPE_MOTION)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
SEvent Event;
|
|
|
|
s32 PointerCount = AMotionEvent_getPointerCount(androidEvent);
|
|
|
|
s32 EventAction = AMotionEvent_getAction(androidEvent);
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
bool MultiTouchEvent = true;
|
2013-10-23 09:30:40 -07:00
|
|
|
bool Touched = false;
|
2012-09-06 16:14:12 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
switch (EventAction)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
case AMOTION_EVENT_ACTION_DOWN:
|
|
|
|
Event.MultiTouchInput.Event = EMTIE_PRESSED_DOWN;
|
2013-10-23 09:30:40 -07:00
|
|
|
Touched = true;
|
2013-10-17 16:11:56 -07:00
|
|
|
break;
|
|
|
|
case AMOTION_EVENT_ACTION_MOVE:
|
|
|
|
Event.MultiTouchInput.Event = EMTIE_MOVED;
|
2013-10-23 09:30:40 -07:00
|
|
|
Touched = true;
|
2013-10-17 16:11:56 -07:00
|
|
|
break;
|
|
|
|
case AMOTION_EVENT_ACTION_UP:
|
2013-11-01 05:35:58 -07:00
|
|
|
Event.MultiTouchInput.Event = EMTIE_LEFT_UP;
|
2013-10-17 16:11:56 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
MultiTouchEvent = false;
|
|
|
|
break;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
2013-10-17 16:11:56 -07:00
|
|
|
|
|
|
|
if (MultiTouchEvent)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
Event.EventType = EET_MULTI_TOUCH_EVENT;
|
|
|
|
Event.MultiTouchInput.clear();
|
|
|
|
|
|
|
|
for (s32 i = 0; i < PointerCount; ++i)
|
2012-09-06 16:14:12 -07:00
|
|
|
{
|
2013-10-17 16:11:56 -07:00
|
|
|
if (i >= NUMBER_OF_MULTI_TOUCHES)
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
2013-10-17 16:11:56 -07:00
|
|
|
|
2013-11-01 05:35:58 -07:00
|
|
|
Event.MultiTouchInput.PrevX[i] = 0; // TODO
|
|
|
|
Event.MultiTouchInput.PrevY[i] = 0; // TODO
|
|
|
|
Event.MultiTouchInput.X[i] = AMotionEvent_getX(androidEvent, i);
|
|
|
|
Event.MultiTouchInput.Y[i] = AMotionEvent_getY(androidEvent, i);
|
2013-10-23 09:30:40 -07:00
|
|
|
Event.MultiTouchInput.Touched[i] = Touched;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
2013-11-01 05:35:58 -07:00
|
|
|
|
2013-10-17 16:11:56 -07:00
|
|
|
Device->postEventFromUser(Event);
|
|
|
|
|
|
|
|
Status = 1;
|
2012-09-06 16:14:12 -07:00
|
|
|
}
|
|
|
|
}
|
2013-10-17 16:11:56 -07:00
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIrrDeviceAndroid::createDriver()
|
|
|
|
{
|
|
|
|
switch(CreationParams.DriverType)
|
|
|
|
{
|
|
|
|
case video::EDT_OGLES1:
|
2013-11-01 05:35:58 -07:00
|
|
|
#ifdef _IRR_COMPILE_WITH_OGLES1_
|
2013-11-13 15:20:30 -08:00
|
|
|
VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager);
|
2013-10-17 16:11:56 -07:00
|
|
|
#else
|
|
|
|
os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR);
|
|
|
|
#endif
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
2013-10-17 16:11:56 -07:00
|
|
|
case video::EDT_OGLES2:
|
|
|
|
#ifdef _IRR_COMPILE_WITH_OGLES2_
|
2013-10-29 16:42:00 -07:00
|
|
|
VideoDriver = video::createOGLES2Driver(CreationParams, ExposedVideoData, FileSystem, ContextManager);
|
2013-10-17 16:11:56 -07:00
|
|
|
#else
|
|
|
|
os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR);
|
|
|
|
#endif
|
2013-11-01 05:35:58 -07:00
|
|
|
break;
|
2013-10-17 16:11:56 -07:00
|
|
|
case video::EDT_NULL:
|
|
|
|
VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
|
|
|
|
break;
|
|
|
|
case video::EDT_SOFTWARE:
|
|
|
|
case video::EDT_BURNINGSVIDEO:
|
|
|
|
case video::EDT_OPENGL:
|
|
|
|
case video::EDT_DIRECT3D8:
|
|
|
|
case video::EDT_DIRECT3D9:
|
2013-11-01 05:35:58 -07:00
|
|
|
os::Printer::log("This driver is not available in Android. Try OpenGL ES 1.0 or ES 2.0.", ELL_ERROR);
|
2013-10-17 16:11:56 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
|
|
|
|
break;
|
2013-11-01 05:35:58 -07:00
|
|
|
}
|
2013-10-17 16:11:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
video::SExposedVideoData& CIrrDeviceAndroid::getExposedVideoData()
|
|
|
|
{
|
|
|
|
return ExposedVideoData;
|
|
|
|
}
|
|
|
|
|
2012-09-06 16:14:12 -07:00
|
|
|
} // end namespace irr
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|