304ff49ef2b91c2942cebd1c3c1f283571939ffd
[sdk] / ecere / src / gui / drivers / AndroidInterface.ec
1 namespace gui::drivers;
2
3 import "Window"
4 import "Interface"
5 import "Condition"
6
7 #define uint _uint
8 #define set _set
9 #include <errno.h>
10 #include <locale.h>
11 #include <pthread.h>
12 #include <unistd.h>
13
14 #include <android/configuration.h>
15 #include <android/looper.h>
16 #include <android/native_activity.h>
17 #include <android/sensor.h>
18 #include <android/log.h>
19 #include <android/window.h>
20
21 #include <jni.h>
22 #undef set
23 #undef uint
24
25 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "ecere-app", __VA_ARGS__))
26 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "ecere-app", __VA_ARGS__))
27 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "ecere-app", __VA_ARGS__))
28 #ifndef _DEBUG
29 #define LOGV(...)  ((void)0)
30 #else
31 #define LOGV(...)  ((void)__android_log_print(ANDROID_LOG_VERBOSE, "ecere-app", __VA_ARGS__))
32 #endif
33
34 // *** NATIVE APP GLUE ********
35 enum LooperID { main = 1, input = 2, user = 3 };
36 enum AppCommand : byte
37 {
38    error = 0, inputChanged, initWindow, termWindow, windowResized, windowRedrawNeeded,
39    contentRectChanged, gainedFocus, lostFocus,
40    configChanged, lowMemory, start, resume, saveState, pause, stop, destroy
41 };
42
43 class AndroidPollSource
44 {
45 public:
46    void * userData;
47    LooperID id;
48    virtual void any_object::process();
49 };
50
51 class AndroidAppGlue : Thread
52 {
53    void* userData;
54    virtual void onAppCmd(AppCommand cmd);
55    virtual int onInputEvent(AInputEvent* event);
56    virtual void main();
57
58    ANativeActivity* activity;
59    AConfiguration* config;
60    void* savedState;
61    uint savedStateSize;
62
63    ALooper* looper;
64    AInputQueue* inputQueue;
65    ANativeWindow* window;
66    ARect contentRect;
67    AppCommand activityState;
68    bool destroyRequested;
69    char * moduleName;
70
71 private:
72    Mutex mutex { };
73    Condition cond { };
74
75    int msgread, msgwrite;
76
77    unsigned int Main()
78    {
79       config = AConfiguration_new();
80       AConfiguration_fromAssetManager(config, activity->assetManager);
81
82       print_cur_config();
83
84       looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
85       ALooper_addFd(looper, msgread, LooperID::main, ALOOPER_EVENT_INPUT, null, cmdPollSource);
86
87       mutex.Wait();
88       running = true;
89       cond.Signal();
90       mutex.Release();
91
92       main();
93
94       destroy();
95       return 0;
96    }
97
98    void destroy()
99    {
100       free_saved_state();
101       mutex.Wait();
102       if(inputQueue)
103          AInputQueue_detachLooper(inputQueue);
104       AConfiguration_delete(config);
105       destroyed = true;
106       cond.Signal();
107       mutex.Release();
108    }
109
110    AndroidPollSource cmdPollSource
111    {
112       this, main;
113
114       void process()
115       {
116          AppCommand cmd = read_cmd();
117          pre_exec_cmd(cmd);
118          onAppCmd(cmd);
119          post_exec_cmd(cmd);
120       }
121    };
122    AndroidPollSource inputPollSource
123    {
124       this, input;
125
126       void process()
127       {
128          AInputEvent* event = null;
129          if(AInputQueue_getEvent(inputQueue, &event) >= 0)
130          {
131             //int handled = 0;
132             LOGV("New input event: type=%d\n", AInputEvent_getType(event));
133             if(AInputQueue_preDispatchEvent(inputQueue, event))
134                return;
135             /*handled = */onInputEvent(event);
136             //AInputQueue_finishEvent(inputQueue, event, handled);
137          }
138          else
139             LOGE("Failure reading next input event: %s\n", strerror(errno));
140       }
141    };
142
143    bool running;
144    bool stateSaved;
145    bool destroyed;
146    AInputQueue* pendingInputQueue;
147    ANativeWindow* pendingWindow;
148    ARect pendingContentRect;
149
150    void free_saved_state()
151    {
152       mutex.Wait();
153       if(savedState)
154          free(savedState);
155       savedState = 0;
156       savedStateSize = 0;
157       mutex.Release();
158    }
159
160    AppCommand read_cmd()
161    {
162       AppCommand cmd;
163       if(read(msgread, &cmd, sizeof(cmd)) == sizeof(cmd))
164       {
165          if(cmd == saveState)
166             free_saved_state();
167          return cmd;
168       }
169       else
170          LOGE("No data on command pipe!");
171       return error;
172    }
173
174    void print_cur_config()
175    {
176       char lang[2], country[2];
177       AConfiguration_getLanguage(config, lang);
178       AConfiguration_getCountry(config, country);
179
180       LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
181               "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
182               "modetype=%d modenight=%d",
183               AConfiguration_getMcc(config),
184               AConfiguration_getMnc(config),
185               lang[0], lang[1], country[0], country[1],
186               AConfiguration_getOrientation(config),
187               AConfiguration_getTouchscreen(config),
188               AConfiguration_getDensity(config),
189               AConfiguration_getKeyboard(config),
190               AConfiguration_getNavigation(config),
191               AConfiguration_getKeysHidden(config),
192               AConfiguration_getNavHidden(config),
193               AConfiguration_getSdkVersion(config),
194               AConfiguration_getScreenSize(config),
195               AConfiguration_getScreenLong(config),
196               AConfiguration_getUiModeType(config),
197               AConfiguration_getUiModeNight(config));
198    }
199
200    void pre_exec_cmd(AppCommand cmd)
201    {
202       PrintLn("pre_exec_cmd: ", cmd);
203       switch(cmd)
204       {
205          case inputChanged:
206             mutex.Wait();
207             if(inputQueue)
208                AInputQueue_detachLooper(inputQueue);
209             inputQueue = pendingInputQueue;
210             if(inputQueue)
211                AInputQueue_attachLooper(inputQueue, looper, LooperID::input, null, inputPollSource);
212             cond.Signal();
213             mutex.Release();
214             break;
215          case initWindow:
216             mutex.Wait();
217             window = pendingWindow;
218             cond.Signal();
219             mutex.Release();
220             break;
221          case termWindow:
222             cond.Signal();
223             break;
224          case resume:
225          case start:
226          case pause:
227          case stop:
228             mutex.Wait();
229             activityState = cmd;
230             cond.Signal();
231             mutex.Release();
232             break;
233          case configChanged:
234             AConfiguration_fromAssetManager(config, activity->assetManager);
235             print_cur_config();
236             break;
237          case destroy:
238             destroyRequested = true;
239             break;
240       }
241    }
242
243    void post_exec_cmd(AppCommand cmd)
244    {
245       PrintLn("post_exec_cmd: ", cmd);
246       switch(cmd)
247       {
248          case termWindow:
249             mutex.Wait();
250             window = null;
251             cond.Signal();
252             mutex.Release();
253             break;
254          case saveState:
255             mutex.Wait();
256             stateSaved = true;
257             cond.Signal();
258             mutex.Release();
259             break;
260          case resume:
261             free_saved_state();
262             break;
263       }
264    }
265
266    void write_cmd(AppCommand cmd)
267    {
268       if(write(msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd))
269          LOGE("Failure writing android_app cmd: %s\n", strerror(errno));
270    }
271
272    void set_input(AInputQueue* inputQueue)
273    {
274       mutex.Wait();
275       pendingInputQueue = inputQueue;
276       write_cmd(inputChanged);
277       while(inputQueue != pendingInputQueue)
278          cond.Wait(mutex);
279       mutex.Release();
280    }
281
282    void set_window(ANativeWindow* window)
283    {
284       mutex.Wait();
285       if(pendingWindow)
286          write_cmd(termWindow);
287       pendingWindow = window;
288       if(window)
289          write_cmd(initWindow);
290       while(window != pendingWindow)
291          cond.Wait(mutex);
292       mutex.Release();
293    }
294
295    void set_activity_state(AppCommand cmd)
296    {
297       mutex.Wait();
298       write_cmd(cmd);
299       while(activityState != cmd)
300          cond.Wait(mutex);
301       mutex.Release();
302    }
303
304    void cleanup()
305    {
306       mutex.Wait();
307       write_cmd(destroy);
308       while(!destroyed)
309          cond.Wait(mutex);
310       mutex.Release();
311       close(msgread);
312       close(msgwrite);
313    }
314
315    void setSavedState(void * state, uint size)
316    {
317       if(savedState)
318          free(savedState);
319       savedState = null;
320       if(state)
321       {
322          savedState = malloc(size);
323          savedStateSize = size;
324          memcpy(savedState, state, size);
325       }
326       else
327          savedStateSize = 0;
328    }
329
330    public void Create()
331    {
332       int msgpipe[2];
333       if(pipe(msgpipe))
334          LOGE("could not create pipe: %s", strerror(errno));
335       msgread = msgpipe[0];
336       msgwrite = msgpipe[1];
337
338       Thread::Create();
339
340       // Wait for thread to start.
341       mutex.Wait();
342       while(!running) cond.Wait(mutex);
343       mutex.Release();
344    }
345 }
346
347 // Callbacks
348 static void onDestroy(ANativeActivity* activity)
349 {
350    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
351    LOGI("Destroy: %p\n", activity);
352    app.cleanup();
353    app.Wait();
354    delete androidActivity;
355    delete __androidCurrentModule;
356    LOGI("THE END.");
357 }
358
359 static void onStart(ANativeActivity* activity)
360 {
361    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
362    LOGI("Start: %p\n", activity);
363    app.set_activity_state(start);
364 }
365
366 static void onResume(ANativeActivity* activity)
367 {
368    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
369    LOGI("Resume: %p\n", activity);
370    app.set_activity_state(resume);
371 }
372
373 static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen)
374 {
375    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
376    void* savedState = null;
377    LOGI("SaveInstanceState: %p\n", activity);
378    app.mutex.Wait();
379    app.stateSaved = false;
380    app.write_cmd(saveState);
381    while(!app.stateSaved)
382       app.cond.Wait(app.mutex);
383    if(app.savedState)
384    {
385       savedState = app.savedState;
386       *outLen = app.savedStateSize;
387       app.savedState = null;
388       app.savedStateSize = 0;
389    }
390    app.mutex.Release();
391    return savedState;
392 }
393
394 static void onPause(ANativeActivity* activity)
395 {
396    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
397    LOGI("Pause: %p\n", activity);
398    app.set_activity_state(pause);
399 }
400
401 static void onStop(ANativeActivity* activity)
402 {
403    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
404    LOGI("Stop: %p\n", activity);
405    app.set_activity_state(stop);
406 }
407
408 static void onConfigurationChanged(ANativeActivity* activity)
409 {
410    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
411    LOGI("ConfigurationChanged: %p\n", activity);
412    app.write_cmd(configChanged);
413 }
414
415 static void onLowMemory(ANativeActivity* activity)
416 {
417    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
418    LOGI("LowMemory: %p\n", activity);
419    app.write_cmd(lowMemory);
420 }
421
422 static void onWindowFocusChanged(ANativeActivity* activity, int focused)
423 {
424    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
425    LOGI("WindowFocusChanged: %p -- %d\n", activity, focused);
426    app.write_cmd(focused ? gainedFocus : lostFocus);
427 }
428
429 static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window)
430 {
431    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
432    LOGI("NativeWindowCreated: %p -- %p\n", activity, window);
433    app.set_window(window);
434 }
435
436 static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window)
437 {
438    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
439    LOGI("NativeWindowDestroyed: %p -- %p\n", activity, window);
440    app.window = null;
441    app.set_window(null);
442 }
443
444 static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue)
445 {
446    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
447    LOGI("InputQueueCreated: %p -- %p\n", activity, queue);
448    app.set_input(queue);
449 }
450
451 static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue)
452 {
453    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
454    LOGI("InputQueueDestroyed: %p -- %p\n", activity, queue);
455    app.inputQueue = null;
456    app.set_input(null);
457 }
458
459 default dllexport void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize)
460 {
461    AndroidAppGlue app;
462    char * moduleName;
463
464    // Determine our package name
465    JNIEnv* env=activity->env;
466    jclass clazz;
467    const char* str;
468    jboolean isCopy;
469    jmethodID methodID;
470    jobject result;
471
472    // *** Reinitialize static global variables ***
473    gotInit = false;
474    guiApplicationInitialized = false;
475    guiApp = null;
476    desktopW = 0; desktopH = 0;
477    clipBoardData = null;
478    __thisModule = null;
479    __androidCurrentModule = null;
480
481    LOGI("Creating: %p\n", activity);
482
483    //(*activity->vm)->AttachCurrentThread(activity->vm, &env, 0);
484    clazz = (*env)->GetObjectClass(env, activity->clazz);
485    methodID = (*env)->GetMethodID(env, clazz, "getPackageName", "()Ljava/lang/String;");
486    result = (*env)->CallObjectMethod(env, activity->clazz, methodID);
487    str = (*env)->GetStringUTFChars(env, (jstring)result, &isCopy);
488    // (*activity->vm)->DetachCurrentThread(activity->vm);
489    moduleName = strstr(str, "com.ecere.");
490    if(moduleName) moduleName += 10;
491    androidArgv[0] = moduleName;
492
493    // Create a base Application class
494    __androidCurrentModule = __ecere_COM_Initialize(true, 1, androidArgv);
495    // Load up Ecere
496    eModule_Load(__androidCurrentModule, "ecere", publicAccess);
497
498
499    if(activity->internalDataPath) PrintLn("internalDataPath is ", activity->internalDataPath);
500    if(activity->externalDataPath) PrintLn("externalDataPath is ", activity->externalDataPath);
501    {
502       char tmp[256];
503       PrintLn("cwd is ", GetWorkingDir(tmp, sizeof(tmp)));
504    }
505
506    ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_FULLSCREEN|AWINDOW_FLAG_KEEP_SCREEN_ON, 0 );
507    app = AndroidActivity { activity = activity, moduleName = moduleName };
508    incref app;
509    app.setSavedState(savedState, (uint)savedStateSize);
510    activity->callbacks->onDestroy = onDestroy;
511    activity->callbacks->onStart = onStart;
512    activity->callbacks->onResume = onResume;
513    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
514    activity->callbacks->onPause = onPause;
515    activity->callbacks->onStop = onStop;
516    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
517    activity->callbacks->onLowMemory = onLowMemory;
518    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
519    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
520    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
521    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
522    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
523    activity->instance = app;
524    app.Create();
525 }
526
527 // *** END OF NATIVE APP GLUE ******
528
529 default:
530 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
531 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp;
532 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown;
533 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
534 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove;
535 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick;
536 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown;
537 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp;
538 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick;
539 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown;
540 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonUp;
541 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick;
542 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown;
543 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonUp;
544 private:
545
546 static Module __androidCurrentModule;
547 static char * androidArgv[1];
548
549 static int desktopW, desktopH;
550 static char * clipBoardData;
551 static int mouseX, mouseY;
552
553 class AndroidInterface : Interface
554 {
555    class_property(name) = "Android";
556
557    // --- User Interface System ---
558    bool Initialize()
559    {
560       setlocale(LC_ALL, "en_US.UTF-8");
561       return true;
562    }
563
564    void Terminate()
565    {
566
567    }
568
569    #define DBLCLICK_DELAY  300   // 0.3 second
570    #define DBLCLICK_DELTA  1
571
572    bool ProcessInput(bool processAll)
573    {
574       bool eventAvailable = false;
575
576       if(androidActivity.ident < 0)
577          androidActivity.ident = (LooperID)ALooper_pollAll(0, null, &androidActivity.events, (void**)&androidActivity.source);
578
579       if(gotInit && androidActivity.window)
580       {
581          int w = ANativeWindow_getWidth(androidActivity.window);
582          int h = ANativeWindow_getHeight(androidActivity.window);
583          if(desktopW != w || desktopH != h)
584          {
585             guiApp.SetDesktopPosition(0, 0, w, h, true);
586             desktopW = w;
587             desktopH = h;
588             guiApp.desktop.Update(null);
589          }
590       }
591
592       while(androidActivity.ident >= 0)
593       {
594          AndroidPollSource source = androidActivity.source;
595
596          androidActivity.source = null;
597          if(source)
598             source.process(source.userData);
599
600          // If a sensor has data, process it now.
601          /*
602          if(androidActivity.ident == user)
603          {
604             if(androidActivity.accelerometerSensor)
605             {
606                ASensorEvent event;
607                while (ASensorEventQueue_getEvents(androidActivity.sensorEventQueue, &event, 1) > 0)
608                   LOGI("accelerometer: x=%f y=%f z=%f", event.acceleration.x, event.acceleration.y, event.acceleration.z);
609             }
610          }
611          */
612
613          eventAvailable = true;
614          if(androidActivity.destroyRequested)
615          {
616             guiApp.desktop.Destroy(0);
617             eventAvailable = true;
618             androidActivity.ident = (LooperID)-1;
619          }
620          else if(processAll)
621             androidActivity.ident = (LooperID)ALooper_pollAll(0, null, &androidActivity.events, (void**)&androidActivity.source);
622          else
623             androidActivity.ident = (LooperID)-1;
624       }
625       return eventAvailable;
626    }
627
628    void Wait()
629    {
630       androidActivity.ident = (LooperID)ALooper_pollAll((int)(1000/18.2f), null, &androidActivity.events, (void**)&androidActivity.source);
631       // guiApp.WaitEvent();
632    }
633
634    void Lock(Window window)
635    {
636
637    }
638
639    void Unlock(Window window)
640    {
641
642    }
643
644    const char ** GraphicsDrivers(int * numDrivers)
645    {
646       static const char *graphicsDrivers[] = { "OpenGL" };
647       *numDrivers = sizeof(graphicsDrivers) / sizeof(char *);
648       return (const char **)graphicsDrivers;
649    }
650
651    void GetCurrentMode(bool * fullScreen, int * resolution, int * colorDepth, int * refreshRate)
652    {
653       *fullScreen = true;
654    }
655
656    void EnsureFullScreen(bool *fullScreen)
657    {
658       *fullScreen = true;
659    }
660
661    bool ScreenMode(bool fullScreen, int resolution, int colorDepth, int refreshRate, bool * textMode)
662    {
663       bool result = true;
664
665       return result;
666    }
667
668    // --- Window Creation ---
669    void * CreateRootWindow(Window window)
670    {
671       return androidActivity.window;
672    }
673
674    void DestroyRootWindow(Window window)
675    {
676
677    }
678
679    // -- Window manipulation ---
680
681    void SetRootWindowCaption(Window window, const char * name)
682    {
683
684    }
685
686    void PositionRootWindow(Window window, int x, int y, int w, int h, bool move, bool resize)
687    {
688
689    }
690
691    void OrderRootWindow(Window window, bool topMost)
692    {
693
694    }
695
696    void SetRootWindowColor(Window window)
697    {
698
699    }
700
701    void OffsetWindow(Window window, int * x, int * y)
702    {
703
704    }
705
706    void UpdateRootWindow(Window window)
707    {
708       if(!window.parent || !window.parent.display)
709       {
710          if(window.visible)
711          {
712             Box box = window.box;
713             box.left -= window.clientStart.x;
714             box.top -= window.clientStart.y;
715             box.right -= window.clientStart.x;
716             box.bottom -= window.clientStart.y;
717             // Logf("Update root window %s\n", window.name);
718             window.Update(null);
719             box.left   += window.clientStart.x;
720             box.top    += window.clientStart.y;
721             box.right  += window.clientStart.x;
722             box.bottom += window.clientStart.y;
723             window.UpdateDirty(box);
724          }
725       }
726    }
727
728
729    void SetRootWindowState(Window window, WindowState state, bool visible)
730    {
731    }
732
733    void FlashRootWindow(Window window)
734    {
735
736    }
737
738    void ActivateRootWindow(Window window)
739    {
740
741    }
742
743    // --- Mouse-based window movement ---
744
745    void StartMoving(Window window, int x, int y, bool fromKeyBoard)
746    {
747
748    }
749
750    void StopMoving(Window window)
751    {
752
753    }
754
755    // -- Mouse manipulation ---
756
757    void GetMousePosition(int *x, int *y)
758    {
759       *x = mouseX;
760       *y = mouseY;
761    }
762
763    void SetMousePosition(int x, int y)
764    {
765       mouseX = x;
766       mouseY = y;
767    }
768
769    void SetMouseRange(Window window, Box box)
770    {
771    }
772
773    void SetMouseCapture(Window window)
774    {
775    }
776
777    // -- Mouse cursor ---
778
779    void SetMouseCursor(Window window, int cursor)
780    {
781       if(cursor == -1)
782       {
783
784       }
785    }
786
787    // --- Caret ---
788
789    void SetCaret(int x, int y, int size)
790    {
791       Window caretOwner = guiApp.caretOwner;
792       Window window = caretOwner ? caretOwner.rootWindow : null;
793       if(window && window.windowData)
794       {
795       }
796    }
797
798    void ClearClipboard()
799    {
800       if(clipBoardData)
801       {
802          delete clipBoardData;
803       }
804    }
805
806    bool AllocateClipboard(ClipBoard clipBoard, uint size)
807    {
808       bool result = false;
809       if((clipBoard.text = new0 byte[size]))
810          result = true;
811       return result;
812    }
813
814    bool SaveClipboard(ClipBoard clipBoard)
815    {
816       bool result = false;
817       if(clipBoard.text)
818       {
819          if(clipBoardData)
820             delete clipBoardData;
821
822          clipBoardData = clipBoard.text;
823          clipBoard.text = null;
824          result = true;
825       }
826       return result;
827    }
828
829    bool LoadClipboard(ClipBoard clipBoard)
830    {
831       bool result = false;
832
833       // The data is inside this client...
834       if(clipBoardData)
835       {
836          clipBoard.text = new char[strlen(clipBoardData)+1];
837          strcpy(clipBoard.text, clipBoardData);
838          result = true;
839       }
840       // The data is with another client...
841       else
842       {
843       }
844       return result;
845    }
846
847    void UnloadClipboard(ClipBoard clipBoard)
848    {
849       delete clipBoard.text;
850    }
851
852    // --- State based input ---
853
854    bool AcquireInput(Window window, bool state)
855    {
856       return false;
857    }
858
859    bool GetMouseState(MouseButtons * buttons, int * x, int * y)
860    {
861       bool result = false;
862       if(x) *x = 0;
863       if(y) *y = 0;
864       return result;
865    }
866
867    bool GetJoystickState(int device, Joystick joystick)
868    {
869       bool result = false;
870       return result;
871    }
872
873    bool GetKeyState(Key key)
874    {
875       bool keyState = false;
876       return keyState;
877    }
878
879    void SetTimerResolution(uint hertz)
880    {
881       // timerDelay = hertz ? (1000000 / hertz) : MAXINT;
882    }
883
884    bool SetIcon(Window window, BitmapResource resource)
885    {
886       if(resource)
887       {
888          /*Bitmap bitmap { };
889          if(bitmap.Load(resource.fileName, null, null))
890          {
891          }
892          delete bitmap;*/
893       }
894       return true;
895    }
896 }
897
898 struct SavedState
899 {
900     float angle;
901     int x;
902     int y;
903 };
904
905 static AndroidActivity androidActivity;
906
907 default const char * AndroidInterface_GetLibLocation()
908 {
909    if(androidActivity)
910    {
911       static char loc[MAX_LOCATION];
912       sprintf(loc, "/data/data/com.ecere.%s/lib/lib", androidActivity.moduleName);
913       return loc;
914    }
915    return null;
916 }
917
918 static bool gotInit;
919
920 default float AMotionEvent_getAxisValue(const AInputEvent* motion_event,
921         int32_t axis, size_t pointer_index);
922
923
924 static define AMETA_META_ON       = 0x00010000;
925 static define AMETA_META_LEFT_ON  = 0x00020000;
926 static define AMETA_META_RIGHT_ON = 0x00040000;
927
928 static Key keyCodeTable[] =
929 {
930     0, //AKEYCODE_UNKNOWN         = 0,
931     0, //AKEYCODE_SOFT_LEFT       = 1,
932     0, //AKEYCODE_SOFT_RIGHT      = 2,
933     0, //AKEYCODE_HOME            = 3,
934     0, //AKEYCODE_BACK            = 4,
935     0, //AKEYCODE_CALL            = 5,
936     0, //AKEYCODE_ENDCALL         = 6,
937     k0, //AKEYCODE_0               = 7,
938     k1, //AKEYCODE_1               = 8,
939     k2, //AKEYCODE_2               = 9,
940     k3, //AKEYCODE_3               = 10,
941     k4, //AKEYCODE_4               = 11,
942     k5, //AKEYCODE_5               = 12,
943     k6, //AKEYCODE_6               = 13,
944     k7, //AKEYCODE_7               = 14,
945     k8, //AKEYCODE_8               = 15,
946     k9, //AKEYCODE_9               = 16,
947     keyPadStar, //AKEYCODE_STAR            = 17,
948     Key { k3, shift = true }, //AKEYCODE_POUND           = 18,
949     up, //AKEYCODE_DPAD_UP         = 19,
950     down, //AKEYCODE_DPAD_DOWN       = 20,
951     left, //AKEYCODE_DPAD_LEFT       = 21,
952     right, //AKEYCODE_DPAD_RIGHT      = 22,
953     keyPad5, //AKEYCODE_DPAD_CENTER     = 23,
954     0, //AKEYCODE_VOLUME_UP       = 24,
955     0, //AKEYCODE_VOLUME_DOWN     = 25,
956     0, //AKEYCODE_POWER           = 26,
957     0, //AKEYCODE_CAMERA          = 27,
958     0, //AKEYCODE_CLEAR           = 28,
959     a, //AKEYCODE_A               = 29,
960     b, //AKEYCODE_B               = 30,
961     c, //AKEYCODE_C               = 31,
962     d, //AKEYCODE_D               = 32,
963     e, //AKEYCODE_E               = 33,
964     f, //AKEYCODE_F               = 34,
965     g, //AKEYCODE_G               = 35,
966     h, //AKEYCODE_H               = 36,
967     i, //AKEYCODE_I               = 37,
968     j, //AKEYCODE_J               = 38,
969     k, //AKEYCODE_K               = 39,
970     l, //AKEYCODE_L               = 40,
971     m, //AKEYCODE_M               = 41,
972     n, //AKEYCODE_N               = 42,
973     o, //AKEYCODE_O               = 43,
974     p, //AKEYCODE_P               = 44,
975     q, //AKEYCODE_Q               = 45,
976     r, //AKEYCODE_R               = 46,
977     s, //AKEYCODE_S               = 47,
978     t, //AKEYCODE_T               = 48,
979     u, //AKEYCODE_U               = 49,
980     v, //AKEYCODE_V               = 50,
981     w, //AKEYCODE_W               = 51,
982     x, //AKEYCODE_X               = 52,
983     y, //AKEYCODE_Y               = 53,
984     z, //AKEYCODE_Z               = 54,
985     comma, //AKEYCODE_COMMA           = 55,
986     period, //AKEYCODE_PERIOD          = 56,
987     leftAlt, //AKEYCODE_ALT_LEFT        = 57,
988     rightAlt, //AKEYCODE_ALT_RIGHT       = 58,
989     leftShift, //AKEYCODE_SHIFT_LEFT      = 59,
990     rightShift, //AKEYCODE_SHIFT_RIGHT     = 60,
991     tab, //AKEYCODE_TAB             = 61,
992     space, //AKEYCODE_SPACE           = 62,
993     0, //AKEYCODE_SYM             = 63,
994     0, //AKEYCODE_EXPLORER        = 64,
995     0, //AKEYCODE_ENVELOPE        = 65,
996     enter, //AKEYCODE_ENTER           = 66,
997     backSpace, //AKEYCODE_DEL             = 67,
998     backQuote, //AKEYCODE_GRAVE           = 68,
999     minus, //AKEYCODE_MINUS           = 69,
1000     plus, //AKEYCODE_EQUALS          = 70,
1001     leftBracket, //AKEYCODE_LEFT_BRACKET    = 71,
1002     rightBracket, //AKEYCODE_RIGHT_BRACKET   = 72,
1003     backSlash, //AKEYCODE_BACKSLASH       = 73,
1004     semicolon, //AKEYCODE_SEMICOLON       = 74,
1005     quote, //AKEYCODE_APOSTROPHE      = 75,
1006     slash, //AKEYCODE_SLASH           = 76,
1007     Key { k2, shift = true }, //AKEYCODE_AT              = 77,
1008     0, //AKEYCODE_NUM             = 78,      // Interpreted as an Alt
1009     0, //AKEYCODE_HEADSETHOOK     = 79,
1010     0, //AKEYCODE_FOCUS           = 80,   // *Camera* focus
1011     keyPadPlus, //AKEYCODE_PLUS            = 81,
1012     0, //AKEYCODE_MENU            = 82,
1013     0, //AKEYCODE_NOTIFICATION    = 83,
1014     0, //AKEYCODE_SEARCH          = 84,
1015     0, //AKEYCODE_MEDIA_PLAY_PAUSE= 85,
1016     0, //AKEYCODE_MEDIA_STOP      = 86,
1017     0, //AKEYCODE_MEDIA_NEXT      = 87,
1018     0, //AKEYCODE_MEDIA_PREVIOUS  = 88,
1019     0, //AKEYCODE_MEDIA_REWIND    = 89,
1020     0, //AKEYCODE_MEDIA_FAST_FORWARD = 90,
1021     0, //AKEYCODE_MUTE            = 91,
1022     0, //AKEYCODE_PAGE_UP         = 92,
1023     0, //AKEYCODE_PAGE_DOWN       = 93,
1024     0, //AKEYCODE_PICTSYMBOLS     = 94,
1025     0, //AKEYCODE_SWITCH_CHARSET  = 95,
1026     0, //AKEYCODE_BUTTON_A        = 96,
1027     0, //AKEYCODE_BUTTON_B        = 97,
1028     0, //AKEYCODE_BUTTON_C        = 98,
1029     0, //AKEYCODE_BUTTON_X        = 99,
1030     0, //AKEYCODE_BUTTON_Y        = 100,
1031     0, //AKEYCODE_BUTTON_Z        = 101,
1032     0, //AKEYCODE_BUTTON_L1       = 102,
1033     0, //AKEYCODE_BUTTON_R1       = 103,
1034     0, //AKEYCODE_BUTTON_L2       = 104,
1035     0, //AKEYCODE_BUTTON_R2       = 105,
1036     0, //AKEYCODE_BUTTON_THUMBL   = 106,
1037     0, //AKEYCODE_BUTTON_THUMBR   = 107,
1038     0, //AKEYCODE_BUTTON_START    = 108,
1039     0, //AKEYCODE_BUTTON_SELECT   = 109,
1040     0, //AKEYCODE_BUTTON_MODE     = 110,
1041     escape, //AKEYCODE_BUTTON_ESCAPE = 111,
1042     del, //AKEYCODE_BUTTON_ESCAPE    = 112,
1043     leftControl, // = 113
1044     rightControl, // = 114
1045     capsLock, // = 115
1046     scrollLock, // = 116
1047     0, // = 117      KEYCODE_META_LEFT
1048     0, // = 118      KEYCODE_META_RIGHT
1049     0, // = 119      KEYCODE_FUNCTION
1050     printScreen, // = 120      KEYCODE_SYSRQ
1051     pauseBreak, // = 121
1052     home, // = 122
1053     end, // = 123
1054     insert // = 124
1055 };
1056
1057 // Why don't we have this in the NDK :(
1058 // default int32_t AKeyEvent_getUnichar(const AInputEvent* key_event);
1059
1060 static Array<TouchPointerInfo> buildPointerInfo(AInputEvent * event)
1061 {
1062    uint count = (uint)AMotionEvent_getPointerCount(event);
1063    Array<TouchPointerInfo> infos { size = count };
1064    int i;
1065    for(i = 0; i < count; i++)
1066    {
1067       infos[i].x = (int)AMotionEvent_getX(event, i);
1068       infos[i].y = (int)AMotionEvent_getY(event, i);
1069       infos[i].id = (int)AMotionEvent_getPointerId(event, i);
1070       infos[i].pressure = AMotionEvent_getPressure(event, i);
1071       infos[i].size = AMotionEvent_getSize(event, i);
1072    }
1073    return infos;
1074 }
1075
1076 class AndroidActivity : AndroidAppGlue
1077 {
1078    AndroidPollSource source;
1079    int events;
1080    LooperID ident;
1081    /*
1082    ASensorManager* sensorManager;
1083    const ASensor* accelerometerSensor;
1084    ASensorEventQueue* sensorEventQueue;
1085    */
1086    SavedState state;
1087
1088    int onInputEvent(AInputEvent* event)
1089    {
1090       static Time lastTime = 0;
1091       Window window = guiApp.desktop;
1092       uint type = AInputEvent_getType(event);
1093       if(type == AINPUT_EVENT_TYPE_MOTION)
1094       {
1095          uint actionAndIndex = AMotionEvent_getAction(event);
1096          //uint source = AInputEvent_getSource(event);
1097          uint action = actionAndIndex & AMOTION_EVENT_ACTION_MASK;
1098          //uint index  = (actionAndIndex & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
1099          //uint flags = AMotionEvent_getFlags(event);
1100          uint meta = AMotionEvent_getMetaState(event);
1101          //uint edge = AMotionEvent_getEdgeFlags(event);
1102          //int64 downTime = AMotionEvent_getDownTime(event);     // nanotime
1103          //int64 eventTime = AMotionEvent_getDownTime(event);
1104          //float axis;
1105          Modifiers keyFlags = 0;
1106          int x = (int)AMotionEvent_getX(event, 0);
1107          int y = (int)AMotionEvent_getY(event, 0);
1108          bool shift = (meta & AMETA_SHIFT_ON) ? true : false;
1109          bool alt = (meta & AMETA_ALT_ON) ? true : false;
1110          //bool sym = (meta & AMETA_SYM_ON) ? true : false;
1111
1112          keyFlags.shift = shift;
1113          keyFlags.alt = alt;
1114
1115          //PrintLn("Got a motion input event: ", action);
1116          /*
1117          if(action == 8) //AMOTION_EVENT_ACTION_SCROLL)
1118             axis = AMotionEvent_getAxisValue(event, 9, index); //AMOTION_EVENT_AXIS_VSCROLL);
1119          */
1120
1121          AInputQueue_finishEvent(inputQueue, event, 1);
1122          switch(action)
1123          {
1124             /*
1125             case 8: //AMOTION_EVENT_ACTION_SCROLL:
1126                window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit, (axis < 0) ? wheelUp : wheelDown, 0);
1127                break;
1128                */
1129             case AMOTION_EVENT_ACTION_DOWN:
1130             {
1131                Time time = GetTime();
1132                bool result = true;
1133                if(Abs(x - mouseX) < 40 && Abs(y - mouseY) < 40 && time - lastTime < 0.3)
1134                   if(!window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick, x, y, &keyFlags, false, true))
1135                      result = false;
1136                lastTime = time;
1137                mouseX = x, mouseY = y;
1138                if(result)
1139                   window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown, x, y, &keyFlags, false, true);
1140                if(result)
1141                {
1142                   Array<TouchPointerInfo> infos = buildPointerInfo(event);
1143                   window.MultiTouchMessage(down, infos, &keyFlags, false, true);
1144                   delete infos;
1145                }
1146                break;
1147             }
1148             case AMOTION_EVENT_ACTION_UP:
1149                mouseX = x, mouseY = y;
1150                if(window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp, x, y, &keyFlags, false, true))
1151                {
1152                   Array<TouchPointerInfo> infos = buildPointerInfo(event);
1153                   window.MultiTouchMessage(up, infos, &keyFlags, false, true);
1154                   delete infos;
1155                }
1156                break;
1157             case AMOTION_EVENT_ACTION_MOVE:
1158                mouseX = x, mouseY = y;
1159                if(window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove, x, y, &keyFlags, false, true))
1160                {
1161                   Array<TouchPointerInfo> infos = buildPointerInfo(event);
1162                   window.MultiTouchMessage(move, infos, &keyFlags, false, true);
1163                   delete infos;
1164                }
1165                break;
1166             case AMOTION_EVENT_ACTION_CANCEL: break;
1167             case AMOTION_EVENT_ACTION_OUTSIDE: break;
1168             case AMOTION_EVENT_ACTION_POINTER_DOWN:
1169             {
1170                Array<TouchPointerInfo> infos = buildPointerInfo(event);
1171                window.MultiTouchMessage(pointerDown, infos, &keyFlags, false, true);
1172                delete infos;
1173                break;
1174             }
1175             case AMOTION_EVENT_ACTION_POINTER_UP:
1176             {
1177                Array<TouchPointerInfo> infos = buildPointerInfo(event);
1178                window.MultiTouchMessage(pointerUp, infos, &keyFlags, false, true);
1179                delete infos;
1180                break;
1181             }
1182          }
1183          return 1;
1184       }
1185       else if(type == AINPUT_EVENT_TYPE_KEY)
1186       {
1187          uint action = AKeyEvent_getAction(event);
1188          //uint flags = AKeyEvent_getFlags(event);
1189          uint keyCode = AKeyEvent_getKeyCode(event);
1190          uint meta = AKeyEvent_getMetaState(event);
1191          Key key = keyCodeTable[keyCode];
1192          bool shift = (meta & AMETA_SHIFT_ON) ? true : false;
1193          bool alt = (meta & AMETA_ALT_ON || meta & AMETA_ALT_LEFT_ON || meta & AMETA_ALT_RIGHT_ON) ? true : false;
1194          //bool metaMeta = (meta & AMETA_META_ON || meta & AMETA_META_LEFT_ON || meta & AMETA_META_RIGHT_ON) ? true : false;
1195          //bool sym = (meta & AMETA_SYM_ON) ? true : false;
1196          //unichar ch = AKeyEvent_getUnichar(event);
1197          unichar ch = 0;
1198
1199          key.shift = shift;
1200          key.alt = alt;
1201
1202          AInputQueue_finishEvent(inputQueue, event, 1);
1203
1204          // PrintLn("Got a key: action = ", action, ", flags = ", flags, ", keyCode = ", keyCode, ", meta = ", meta, ": key = ", (int)key);
1205
1206          if(key)
1207          {
1208             if(action == AKEY_EVENT_ACTION_DOWN || action == AKEY_EVENT_ACTION_MULTIPLE)
1209             {
1210                /*if(key == wheelDown || key == wheelUp)
1211                   window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit, key, ch);
1212                else*/
1213                {
1214                   char c = Interface::TranslateKey(key.code, shift);
1215                   if(c > 0) ch = c;
1216                   window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown, key, ch);
1217                }
1218             }
1219             else if(action == AKEY_EVENT_ACTION_UP)
1220                window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp, key, ch);
1221          }
1222          return 1;
1223       }
1224       else
1225          AInputQueue_finishEvent(inputQueue, event, 0);
1226       return 0;
1227    }
1228
1229    void onAppCmd(AppCommand cmd)
1230    {
1231       switch(cmd)
1232       {
1233          case saveState:
1234             setSavedState(&state, sizeof(state));
1235             break;
1236          case initWindow:
1237             if(window)
1238             {
1239                int w, h;
1240                gotInit = true;
1241                ANativeWindow_setBuffersGeometry(window, 0, 0, 0); //format);
1242                w = ANativeWindow_getWidth(window);
1243                h = ANativeWindow_getHeight(window);
1244                guiApp.Initialize(false);
1245                guiApp.desktop.windowHandle = window;
1246                guiApp.interfaceDriver = null;
1247                guiApp.SwitchMode(true, null, 0, 0, 0, null, false);
1248
1249                if(desktopW != w || desktopH != h)
1250                {
1251                   guiApp.SetDesktopPosition(0, 0, w, h, true);
1252                   desktopW = w;
1253                   desktopH = h;
1254                }
1255                guiApp.desktop.Update(null);
1256             }
1257             break;
1258          case termWindow:
1259             guiApp.desktop.UnloadGraphics(false);
1260             break;
1261          case gainedFocus:
1262             guiApp.desktop.Update(null);
1263             guiApp.SetAppFocus(true);
1264             /*
1265             if(accelerometerSensor)
1266             {
1267                ASensorEventQueue_enableSensor(sensorEventQueue, accelerometerSensor);
1268                ASensorEventQueue_setEventRate(sensorEventQueue, accelerometerSensor, (1000L/60)*1000);
1269             }
1270             */
1271             break;
1272          case lostFocus:
1273             /*
1274             if(accelerometerSensor)
1275                ASensorEventQueue_disableSensor(sensorEventQueue, accelerometerSensor);
1276             */
1277             guiApp.SetAppFocus(false);
1278             guiApp.desktop.Update(null);
1279             break;
1280          case configChanged:
1281             if(window)
1282                guiApp.desktop.UpdateDisplay();
1283             break;
1284       }
1285    }
1286
1287    void main()
1288    {
1289       androidActivity = this;
1290       /* Let's have fun with sensors when we have an actual device to play with
1291       sensorManager = ASensorManager_getInstance();
1292       accelerometerSensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_ACCELEROMETER);
1293       sensorEventQueue = ASensorManager_createEventQueue(sensorManager, looper, LooperID::user, null, null);
1294       */
1295
1296       if(savedState)
1297          state = *(SavedState*)savedState;
1298
1299       {
1300          Module app;
1301
1302          // Evolve the Application class into a GuiApplication
1303          eInstance_Evolve((Instance *)&__androidCurrentModule, class(GuiApplication));
1304
1305          // Wait for the initWindow command:
1306          guiApp.interfaceDriver = class(AndroidInterface);
1307          while(!gotInit)
1308          {
1309             // Can't call the GuiApplication here, because GuiApplication::Initialize() has not been called yet
1310             guiApp.interfaceDriver.Wait();
1311             guiApp.interfaceDriver.ProcessInput(true);
1312          }
1313
1314          // Invoke __ecereDll_Load() in lib[our package name].so
1315          app = eModule_Load(__androidCurrentModule, moduleName, publicAccess);
1316          if(app)
1317          {
1318             Class c;
1319             // Find out if any GuiApplication class was defined in our module
1320             for(c = app.classes.first; c && !eClass_IsDerived(c, class(GuiApplication)); c = c.next);
1321             if(!c) c = class(GuiApplication);
1322
1323             guiApp.lockMutex.Release();   // TOCHECK: Seems the evolve is losing our mutex lock here ?
1324
1325             // Evolve the Application into it
1326             eInstance_Evolve((Instance *)&__androidCurrentModule, c);
1327             guiApp = (GuiApplication)__androidCurrentModule;
1328
1329             {
1330                const String skin = guiApp.skin;
1331                *&guiApp.currentSkin = null;
1332                guiApp.SelectSkin(skin);
1333             }
1334
1335             guiApp.lockMutex.Wait();
1336
1337             // Call Main()
1338             ((void (*)(void *))(void *)__androidCurrentModule._vTbl[12])(__androidCurrentModule);
1339          }
1340
1341          if(!destroyRequested)
1342             ANativeActivity_finish(activity);
1343          while(!destroyRequested)
1344          {
1345             guiApp.interfaceDriver.Wait();
1346             guiApp.interfaceDriver.ProcessInput(true);
1347          }
1348       }
1349    }
1350 }