1 module dlangui.platforms.android.androidapp;
2 
3 version(Android):
4 
5 import core.stdc.stdlib : malloc;
6 import core.stdc.string : memset;
7 import dlangui.core.logger;
8 
9 import dlangui.widgets.styles;
10 import dlangui.widgets.widget;
11 import dlangui.graphics.drawbuf;
12 import dlangui.graphics.gldrawbuf;
13 import dlangui.graphics.glsupport;
14 import dlangui.platforms.common.platform;
15 
16 import android.input, android.looper : ALooper_pollAll;
17 import android.native_window : ANativeWindow_setBuffersGeometry;
18 import android.configuration;
19 import android.keycodes;
20 import android.log, android.android_native_app_glue;
21 
22 /**
23  * Window abstraction layer. Widgets can be shown only inside window.
24  *
25  */
26 class AndroidWindow : Window {
27     // Abstract methods : override in platform implementatino
28 
29     /// show window
30     override void show() {
31         // TODO
32         _visible = true;
33         _platform.drawWindow(this);
34     }
35     bool _visible;
36 
37     override @property Window parentWindow() {
38         return null;
39     }
40 
41     override protected void handleWindowActivityChange(bool isWindowActive) {
42         super.handleWindowActivityChange(isWindowActive);
43     }
44 
45     override @property bool isActive() {
46         //todo:
47         return true;
48     }
49 
50     protected dstring _caption;
51     /// returns window caption
52     override @property dstring windowCaption() const {
53         return _caption;
54     }
55     /// sets window caption
56     override @property void windowCaption(dstring caption) {
57         _caption = caption;
58     }
59     /// sets window icon
60     override @property void windowIcon(DrawBufRef icon) {
61         // not supported
62     }
63     uint _lastRedrawEventCode;
64     /// request window redraw
65     override void invalidate() {
66         _platform.sendRedrawEvent(this, ++_lastRedrawEventCode);
67     }
68 
69     void processRedrawEvent(uint code) {
70         //if (code == _lastRedrawEventCode)
71         //    redraw();
72     }
73     /// close window
74     override void close() {
75         _platform.closeWindow(this);
76     }
77 
78     protected AndroidPlatform _platform;
79     this(AndroidPlatform platform) {
80         super();
81         _platform = platform;
82     }
83 
84     ~this() {
85     }
86 
87     /// after drawing, call to schedule redraw if animation is active
88     override void scheduleAnimation() {
89         // override if necessary
90         // TODO
91     }
92 
93     ushort lastFlags;
94     short lastx;
95     short lasty;
96     protected ButtonDetails _lbutton;
97     protected ButtonDetails _mbutton;
98     protected ButtonDetails _rbutton;
99     void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) {
100         MouseEvent event = null;
101         lastFlags = 0; //convertMouseFlags(state);
102         if (_keyFlags & KeyFlag.Shift)
103             lastFlags |= MouseFlag.Shift;
104         if (_keyFlags & KeyFlag.Control)
105             lastFlags |= MouseFlag.Control;
106         if (_keyFlags & KeyFlag.Alt)
107             lastFlags |= MouseFlag.Alt;
108         lastx = cast(short)x;
109         lasty = cast(short)y;
110         MouseButton btn = MouseButton.Left; // convertMouseButton(button);
111         event = new MouseEvent(action, btn, lastFlags, lastx, lasty);
112         if (event) {
113             ButtonDetails * pbuttonDetails = null;
114             if (button == MouseButton.Left)
115                 pbuttonDetails = &_lbutton;
116             else if (button == MouseButton.Right)
117                 pbuttonDetails = &_rbutton;
118             else if (button == MouseButton.Middle)
119                 pbuttonDetails = &_mbutton;
120             if (pbuttonDetails) {
121                 if (action == MouseAction.ButtonDown) {
122                     pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags);
123                 } else if (action == MouseAction.ButtonUp) {
124                     pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags);
125                 }
126             }
127             event.lbutton = _lbutton;
128             event.rbutton = _rbutton;
129             event.mbutton = _mbutton;
130             bool res = dispatchMouseEvent(event);
131             if (res) {
132                 debug(mouse) Log.d("Calling update() after mouse event");
133                 invalidate();
134             }
135         }
136     }
137     uint _keyFlags;
138 
139     /**
140      * Process the next input event.
141      */
142     int handle_input(AInputEvent* event) {
143         import imm = dlangui.platforms.android.imm;
144         import std.conv : to;
145         Log.i("handle input, event=", AInputEvent_getType(event));
146         auto et = AInputEvent_getType(event);
147         if (et == AINPUT_EVENT_TYPE_MOTION) {
148             auto action = AMotionEvent_getAction(event);
149             int x = cast(int)AMotionEvent_getX(event, 0);
150             int y = cast(int)AMotionEvent_getY(event, 0);
151             switch(action) {
152                 case AMOTION_EVENT_ACTION_DOWN:
153                     processMouseEvent(MouseAction.ButtonDown, 0, 0, x, y);
154                     break;
155                 case AMOTION_EVENT_ACTION_UP:
156                     processMouseEvent(MouseAction.ButtonUp, 0, 0, x, y);
157                     break;
158                 case AMOTION_EVENT_ACTION_MOVE:
159                     processMouseEvent(MouseAction.Move, 0, 0, x, y);
160                     break;
161                 case AMOTION_EVENT_ACTION_CANCEL:
162                     processMouseEvent(MouseAction.Cancel, 0, 0, x, y);
163                     break;
164                 case AMOTION_EVENT_ACTION_OUTSIDE:
165                     //processMouseEvent(MouseAction.Down, 0, 0, x, y);
166                     break;
167                 case AMOTION_EVENT_ACTION_POINTER_DOWN:
168                     processMouseEvent(MouseAction.ButtonDown, 0, 0, x, y);
169                     break;
170                 case AMOTION_EVENT_ACTION_POINTER_UP:
171                     processMouseEvent(MouseAction.ButtonUp, 0, 0, x, y);
172                     break;
173                 default:
174                     break;
175             }
176             return 1;
177         } else if (et == AINPUT_EVENT_TYPE_KEY) {
178             Log.d("AINPUT_EVENT_TYPE_KEY");
179             KeyEvent evt;
180             auto app = (cast(AndroidPlatform)platform)._appstate;
181             int _keyFlags = AKeyEvent_getMetaState(event).toKeyFlag();
182             int sysKeyCode = AKeyEvent_getKeyCode(event);
183             int sysMeta = AKeyEvent_getMetaState(event);
184             int keyCode = androidKeyMap.get(sysKeyCode, KeyCode.init);
185             auto action = toKeyAction(AKeyEvent_getAction(event));
186             int char_ = imm.GetUnicodeChar(app, action, sysKeyCode, sysMeta);
187             dchar[] text;
188             if (!isTextEditControl(sysKeyCode)) {
189                 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) {
190                     // it's a string from IME
191                     if (sysKeyCode == AKEYCODE_UNKNOWN) {
192                         text = cast(dchar[]) to!dstring(imm.GetUnicodeString(app, event));
193                         action = KeyAction.Text;
194                     }
195                     // else  repeat character AKeyEvent_getRepeatCount() times
196                 }
197                 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN && (char_ || isASCIIChar(sysKeyCode))) {
198                     text ~= cast(dchar)(char_ == 0 ? sysKeyCode : char_);
199                     action = KeyAction.Text;
200                 }
201             }
202             Log.d("ACTION: ", action, " syskeyCode: ", sysKeyCode, " sysMeta: ", sysMeta, "meta: ", _keyFlags, " char '", cast(dchar)char_, "' str:", cast(dstring)text);
203             if (action == KeyAction.Text)
204                 evt = new KeyEvent(KeyAction.Text, 0, 0, cast(dstring)text);
205             else
206                 evt = new KeyEvent(action, keyCode, _keyFlags);
207             if (evt && dispatchKeyEvent(evt))
208                 update();
209             return 1;
210         }
211         return 0;
212     }
213 }
214 
215 /**
216  * Platform abstraction layer.
217  *
218  * Represents application.
219  *
220  *
221  *
222  */
223 class AndroidPlatform : Platform {
224 
225     protected AndroidWindow[] _windows;
226     protected AndroidWindow _activeWindow;
227     engine _engine;
228     protected android_app* _appstate;
229     protected EGLDisplay _display;
230     protected EGLSurface _surface;
231     protected EGLContext _context;
232     protected int _width;
233     protected int _height;
234 
235     this(android_app* state) {
236         Log.d("AndroidPlatform.this()");
237         _appstate = state;
238         memset(&_engine, 0, engine.sizeof);
239         Log.d("AndroidPlatform.this() - setting handlers");
240         state.userData = cast(void*)this;
241         state.onAppCmd = &engine_handle_cmd;
242         state.onInputEvent = &engine_handle_input;
243 
244         //Log.d("AndroidPlatform.this() - restoring saved state");
245         //if (state.savedState != null) {
246         //    // We are starting with a previous saved state; restore from it.
247         //    _engine.state = *cast(saved_state*)state.savedState;
248         //}
249         Log.d("AndroidPlatform.this() - done");
250     }
251 
252     ~this() {
253         foreach_reverse(w; _windows) {
254             destroy(w);
255         }
256         _windows.length = 0;
257         termDisplay();
258     }
259 
260 
261     void showSoftKeyboard(bool shouldShow) {
262         import imm = dlangui.platforms.android.imm;
263         imm.showSoftKeyboard(_appstate, shouldShow);
264     }
265 
266 
267     /**
268      * Initialize an EGL context for the current display.
269      */
270     int initDisplay() {
271         // initialize OpenGL ES and EGL
272         Log.i("initDisplay");
273 
274         /*
275          * Here specify the attributes of the desired configuration.
276          * Below, we select an EGLConfig with at least 8 bits per color
277          * component compatible with on-screen windows
278          */
279         const(EGLint)[9] attribs = [
280             EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
281             EGL_BLUE_SIZE, 8,
282             EGL_GREEN_SIZE, 8,
283             EGL_RED_SIZE, 8,
284             EGL_NONE
285         ];
286         EGLint w, h, dummy, format;
287         EGLint numConfigs;
288         EGLConfig config;
289         EGLSurface surface;
290         EGLContext context;
291 
292         EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
293 
294         eglInitialize(display, null, null);
295 
296         /* Here, the application chooses the configuration it desires. In this
297          * sample, we have a very simplified selection process, where we pick
298           * the first EGLConfig that matches our criteria */
299         eglChooseConfig(display, attribs.ptr, &config, 1, &numConfigs);
300 
301         /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
302          * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
303           * As soon as we picked a EGLConfig, we can safely reconfigure the
304          * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
305         eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
306 
307         ANativeWindow_setBuffersGeometry(_appstate.window, 0, 0, format);
308 
309         surface = eglCreateWindowSurface(display, config, _appstate.window, null);
310         EGLint[3] contextAttrs = [EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE];
311         context = eglCreateContext(display, config, null, contextAttrs.ptr);
312 
313         if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
314             LOGW("Unable to eglMakeCurrent");
315             return -1;
316         }
317 
318         eglQuerySurface(display, surface, EGL_WIDTH, &w);
319         eglQuerySurface(display, surface, EGL_HEIGHT, &h);
320 
321         Log.i("surface created: ", _width, "x", _height);
322 
323         _display = display;
324         _context = context;
325         _surface = surface;
326         _width = w;
327         _height = h;
328         _engine.state.angle = 0;
329 
330         // Initialize GL state.
331         //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
332         glEnable(GL_CULL_FACE);
333         //glShadeModel(GL_SMOOTH);
334         glDisable(GL_DEPTH_TEST);
335 
336         Log.i("calling initGLSupport");
337         initGLSupport(false);
338 
339         return 0;
340     }
341 
342 
343     /**
344      * Tear down the EGL context currently associated with the display.
345      */
346     void termDisplay() {
347         Log.i("termDisplay");
348         if (_display != EGL_NO_DISPLAY) {
349             eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
350             if (_context != EGL_NO_CONTEXT) {
351                 eglDestroyContext(_display, _context);
352             }
353             if (_surface != EGL_NO_SURFACE) {
354                 eglDestroySurface(_display, _surface);
355             }
356             eglTerminate(_display);
357         }
358         //_engine.animating = 0;
359         _display = EGL_NO_DISPLAY;
360         _context = EGL_NO_CONTEXT;
361         _surface = EGL_NO_SURFACE;
362     }
363 
364     /**
365      * Process the next input event.
366      */
367     int handle_input(AInputEvent* event) {
368         Log.i("handle input, event=", AInputEvent_getType(event));
369         auto w = activeWindow;
370         if (!w)
371             return 0;
372         return w.handle_input(event);
373     }
374 
375 
376     bool _appFocused;
377     /**
378      * Process the next main command.
379      */
380     void handle_cmd(int cmd) {
381         if (_appstate.destroyRequested != 0) {
382             Log.w("handle_cmd: destroyRequested is set!!!");
383         }
384         switch (cmd) {
385             case APP_CMD_SAVE_STATE:
386                 Log.d("APP_CMD_SAVE_STATE");
387                 // The system has asked us to save our current state.  Do so.
388                 _appstate.savedState = malloc(saved_state.sizeof);
389                 *(cast(saved_state*)_appstate.savedState) = _engine.state;
390                 _appstate.savedStateSize = saved_state.sizeof;
391                 break;
392             case APP_CMD_INIT_WINDOW:
393                 Log.d("APP_CMD_INIT_WINDOW");
394                 // The window is being shown, get it ready.
395                 if (_appstate.window != null) {
396                     initDisplay();
397                     drawWindow();
398                 }
399                 break;
400             case APP_CMD_TERM_WINDOW:
401                 Log.d("APP_CMD_TERM_WINDOW");
402                 // The window is being hidden or closed, clean it up.
403                 termDisplay();
404                 break;
405             case APP_CMD_GAINED_FOCUS:
406                 Log.d("APP_CMD_GAINED_FOCUS");
407                 // When our app gains focus
408                 _appFocused = true;
409                 break;
410             case APP_CMD_LOST_FOCUS:
411                 Log.d("APP_CMD_LOST_FOCUS");
412                 // When our app loses focus
413                 // This is to avoid consuming battery while not being used.
414                 // Also stop animating.
415                 //_engine.animating = 0;
416                 _appFocused = false;
417                 drawWindow();
418                 break;
419             case APP_CMD_INPUT_CHANGED:
420                 Log.d("APP_CMD_INPUT_CHANGED");
421                 break;
422             case APP_CMD_WINDOW_RESIZED:
423                 Log.d("APP_CMD_WINDOW_RESIZED");
424                 break;
425             case APP_CMD_WINDOW_REDRAW_NEEDED:
426                 Log.d("APP_CMD_WINDOW_REDRAW_NEEDED");
427                 drawWindow();
428                 break;
429             case APP_CMD_CONTENT_RECT_CHANGED:
430                 Log.d("APP_CMD_CONTENT_RECT_CHANGED");
431                 drawWindow();
432                 break;
433             case APP_CMD_CONFIG_CHANGED:
434                 Log.d("APP_CMD_CONFIG_CHANGED");
435                 break;
436             case APP_CMD_LOW_MEMORY:
437                 Log.d("APP_CMD_LOW_MEMORY");
438                 break;
439             case APP_CMD_START:
440                 Log.d("APP_CMD_START");
441                 break;
442             case APP_CMD_RESUME:
443                 Log.d("APP_CMD_RESUME");
444                 break;
445             case APP_CMD_PAUSE:
446                 Log.d("APP_CMD_PAUSE");
447                 break;
448             case APP_CMD_STOP:
449                 Log.d("APP_CMD_STOP");
450                 break;
451             case APP_CMD_DESTROY:
452                 Log.d("APP_CMD_DESTROY");
453                 break;
454             default:
455                 Log.i("unknown APP_CMD_XXX=", cmd);
456                 break;
457         }
458     }
459 
460     @property bool isAnimationActive() {
461         auto w = activeWindow;
462         return (w && w.isAnimationActive && _appFocused);
463     }
464 
465 
466 
467     void sendRedrawEvent(AndroidWindow w, uint redrawEventCode) {
468         import core.stdc.stdio;
469         import core.sys.posix.unistd;
470         if (w && w is activeWindow) {
471             // request update
472             _appstate.redrawNeeded = true;
473             Log.d("sending APP_CMD_WINDOW_REDRAW_NEEDED");
474             ubyte cmd = APP_CMD_WINDOW_REDRAW_NEEDED;
475             write(_appstate.msgwrite, &cmd, cmd.sizeof);
476         }
477     }
478 
479     /**
480      * create window
481      * Args:
482      *         windowCaption = window caption text
483      *         parent = parent Window, or null if no parent
484      *         flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen
485      *      width = window width
486      *      height = window height
487      *
488      * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget
489      */
490     override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) {
491         AndroidWindow w = new AndroidWindow(this);
492         _windows ~= w;
493         return w;
494     }
495 
496     /**
497      * close window
498      *
499      * Closes window earlier created with createWindow()
500      */
501     override  void closeWindow(Window w) {
502         import std.algorithm : remove;
503         for (int i = 0; i < _windows.length; i++) {
504             if (_windows[i] is w) {
505                 _windows = _windows.remove(i);
506                 break;
507             }
508         }
509         if (_windows.length == 0) {
510             _appstate.destroyRequested = true;
511         }
512     }
513 
514     @property AndroidWindow activeWindow() {
515         for (int i = cast(int)_windows.length - 1; i >= 0; i++)
516             if (_windows[i]._visible)
517                 return _windows[i];
518         return null;
519     }
520 
521     GLDrawBuf _drawbuf;
522     void drawWindow(AndroidWindow w = null) {
523         Log.i("drawWindow");
524         if (w is null)
525             w = activeWindow;
526         else if (!(activeWindow is w))
527             return;
528         if (_display == null) {
529             // No display.
530             return;
531         }
532 
533         // Just fill the screen with a color.
534         if (!w) {
535             glClearColor(0, 0, 0, 1);
536             glClear(GL_COLOR_BUFFER_BIT);
537         } else {
538             w.onResize(_width, _height);
539             glDisable(GL_DEPTH_TEST);
540             glViewport(0, 0, _width, _height);
541             float a = 1.0f;
542             float r = ((w.backgroundColor >> 16) & 255) / 255.0f;
543             float g = ((w.backgroundColor >> 8) & 255) / 255.0f;
544             float b = ((w.backgroundColor >> 0) & 255) / 255.0f;
545             glClearColor(r, g, b, a);
546             glClear(GL_COLOR_BUFFER_BIT);
547             if (!_drawbuf)
548                 _drawbuf = new GLDrawBuf(_width, _height);
549             _drawbuf.resize(_width, _height);
550             _drawbuf.beforeDrawing();
551             w.onDraw(_drawbuf);
552             _drawbuf.afterDrawing();
553         }
554 
555         eglSwapBuffers(_display, _surface);
556     }
557 
558     /**
559      * Starts application message loop.
560      *
561      * When returned from this method, application is shutting down.
562      */
563     override int enterMessageLoop() {
564         while (1) {
565             // Read all pending events.
566             int ident;
567             int events;
568             android_poll_source* source;
569 
570             // If not animating, we will block forever waiting for events.
571             // If animating, we loop until all events are read, then continue
572             // to draw the next frame of animation.
573             while ((ident=ALooper_pollAll(isAnimationActive ? 0 : -1, null, &events,
574                         cast(void**)&source)) >= 0) {
575 
576                 // Process this event.
577                 if (source != null) {
578                     source.process(_appstate, source);
579                 }
580 
581                 // If a sensor has data, process it now.
582                 if (ident == LOOPER_ID_USER) {
583                     /*
584                     if (_accelerometerSensor != null) {
585                         ASensorEvent event;
586                         while (ASensorEventQueue_getEvents(_sensorEventQueue,
587                                 &event, 1) > 0) {
588                             LOGI("accelerometer: x=%f y=%f z=%f",
589                                 event.acceleration.x, event.acceleration.y,
590                                 event.acceleration.z);
591                         }
592                     }
593                     */
594                 }
595 
596                 // Check if we are exiting.
597                 if (_appstate.destroyRequested != 0) {
598                     Log.w("destroyRequested is set: exiting message loop");
599                     return 0;
600                 }
601             }
602 
603             if (isAnimationActive) {
604                 // Done with events; draw next animation frame.
605                 _engine.state.angle += .01f;
606                 if (_engine.state.angle > 1) {
607                     _engine.state.angle = 0;
608                 }
609 
610                 // Drawing is throttled to the screen update rate, so there
611                 // is no need to do timing here.
612                 drawWindow();
613             }
614         }
615     }
616 
617     protected dstring _clipboardText;
618 
619     /// check has clipboard text
620     override bool hasClipboardText(bool mouseBuffer = false) {
621         return (_clipboardText.length > 0);
622     }
623 
624     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
625     override dstring getClipboardText(bool mouseBuffer = false) {
626         return _clipboardText;
627     }
628 
629     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
630     override void setClipboardText(dstring text, bool mouseBuffer = false) {
631         _clipboardText = text;
632     }
633 
634     /// calls request layout for all windows
635     override void requestLayout() {
636     }
637 
638     /// handle theme change: e.g. reload some themed resources
639     override void onThemeChanged() {
640         super.onThemeChanged();
641         // override and call dispatchThemeChange for all windows
642     }
643 
644 }
645 
646 
647 
648 /**
649  * Our saved state data.
650  */
651 struct saved_state {
652     float angle;
653     float x;
654     float y;
655 }
656 
657 /**
658  * Shared state for our app.
659  */
660 struct engine {
661     //int animating;
662     saved_state state;
663 }
664 
665 /**
666  * Process the next input event.
667  */
668 extern(C) int engine_handle_input(android_app* app, AInputEvent* event) {
669     AndroidPlatform p = cast(AndroidPlatform)app.userData;
670     return p.handle_input(event);
671 }
672 
673 /**
674  * Process the next main command.
675  */
676 extern(C) void engine_handle_cmd(android_app* app, int cmd) {
677     AndroidPlatform p = cast(AndroidPlatform)app.userData;
678     p.handle_cmd(cmd);
679 }
680 
681 void main(){}
682 
683 int getDensityDpi(android_app * app) {
684     AConfiguration * config = AConfiguration_new();
685     AConfiguration_fromAssetManager(config, app.activity.assetManager);
686     int res = AConfiguration_getDensity(config);
687     AConfiguration_delete(config);
688     return res;
689 }
690 
691 __gshared AndroidPlatform _platform;
692 
693 /**
694  * This is the main entry point of a native application that is using
695  * android_native_app_glue.  It runs in its own thread, with its own
696  * event loop for receiving input events and doing other things.
697  */
698 extern (C) void android_main(android_app* state) {
699     //import dlangui.platforms.common.startup : initLogs, initFontManager, initResourceManagers, ;
700     LOGI("Inside android_main");
701     initLogs();
702     Log.i("Testing logger - Log.i");
703     Log.fi("Testing logger - Log.fi %d %s", 12345, "asdfgh");
704 
705     if (!initFontManager()) {
706         Log.e("******************************************************************");
707         Log.e("No font files found!!!");
708         Log.e("Currently, only hardcoded font paths implemented.");
709         Log.e("******************************************************************");
710         assert(false);
711     }
712     initResourceManagers();
713     SCREEN_DPI = getDensityDpi(state);
714     TOUCH_MODE = true;
715     Log.i("SCREEN_DPI=", SCREEN_DPI);
716 
717     //currentTheme = createDefaultTheme();
718 
719 
720     _platform = new AndroidPlatform(state);
721     Platform.setInstance(_platform);
722 
723     _platform.uiTheme = "theme_default";
724 
725     // Make sure glue isn't stripped.
726     app_dummy();
727 
728     int res = 0;
729 
730     version (unittest) {
731     } else {
732         Log.i("Calling UIAppMain");
733         res = UIAppMain([]);
734         Log.i("UIAppMain returned with resultCode=", res);
735     }
736 
737     // loop waiting for stuff to do.
738     Log.d("Destroying Android platform");
739     Platform.setInstance(null);
740 
741     releaseResourcesOnAppExit();
742 
743     Log.d("Exiting main");
744 
745 
746 }
747 
748 
749 private KeyAction toKeyAction(int androidKeyAction) {
750     switch(androidKeyAction) {
751         case AKEY_EVENT_ACTION_DOWN: return KeyAction.KeyDown;
752         case AKEY_EVENT_ACTION_UP: return KeyAction.KeyUp;
753         case AKEY_EVENT_ACTION_MULTIPLE: return KeyAction.Repeat; // can also be text
754         default: 
755             assert(0, "should never reach this");
756     }
757 }
758 
759 
760 private bool isASCIIChar(int ch) {
761     return 31 < ch && ch < 127;
762 }
763 
764 // Text editor controls such as move caret or (de)select
765 private bool isTextEditControl(int keyCode) {
766     switch (keyCode){
767         case AKEYCODE_DEL: // backspace ("DEL" or "<x" arrow on soft keyboard)
768         case 112: // delete
769         case 59: // lshift
770         case 60: // rshift
771         case 113: // lcontrol
772         case 114: // rcontrol
773         case 57: // lalt
774         case 58: // ralt
775         case 19: // up arrow
776         case 20: // down arrow
777         case 21: // left arrow
778         case 22: // right arrow
779         case 92: // page up?
780         case 93: // page down?
781         case 122: // home
782         case 123: // end
783         case 124: // insert
784             return true;
785         static foreach(fn; 131..143) // f1-f12
786             case fn: return true;
787         default:
788             return false;
789     }
790 }
791 
792 private int toKeyFlag(int keyMeta) {
793     int state;
794     if (keyMeta & AMETA_ALT_ON) state |= KeyFlag.Alt;
795     if (keyMeta & AMETA_SHIFT_ON) state |= KeyFlag.Shift;
796     return state;
797 }
798 
799 
800     
801 /// Android to dlangui key mapping
802 private static immutable KeyCode[int] androidKeyMap; 
803 
804 static this() {
805     import std.conv : text;
806     androidKeyMap = [
807         AKEYCODE_DEL: KeyCode.BACK, // Delete(key code 67) on Android seems to work as backspace(key 112)
808         AKEYCODE_BACK: KeyCode.BACK,
809         AKEYCODE_SPACE: KeyCode.SPACE,
810         AKEYCODE_ENTER: KeyCode.RETURN,
811         AKEYCODE_TAB: KeyCode.TAB,
812         AKEYCODE_DPAD_LEFT: KeyCode.LEFT,
813         AKEYCODE_DPAD_RIGHT: KeyCode.RIGHT,
814         AKEYCODE_DPAD_UP: KeyCode.UP,
815         AKEYCODE_DPAD_DOWN: KeyCode.DOWN,
816         AKEYCODE_PAGE_UP: KeyCode.PAGEUP,
817         AKEYCODE_PAGE_DOWN: KeyCode.PAGEDOWN,
818         112: KeyCode.DEL,
819         122: KeyCode.HOME,
820         123: KeyCode.END,
821     ];
822     static foreach(n; 0..10) // keys 0-9
823         androidKeyMap[mixin("AKEYCODE_" ~ n.text)] = mixin("KeyCode.KEY_" ~ n.text);
824     static foreach(char n; 'A'..'Z'+1) // A-Z
825         androidKeyMap[mixin("AKEYCODE_" ~ n)] = mixin("KeyCode.KEY_" ~ n);
826 }