android: handle pause/unpause of application + recreate EGL window and OpenGL context...
[freeglut] / src / android / fg_main_android.c
1 /*
2  * fg_main_android.c
3  *
4  * The Android-specific windows message processing methods.
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Copied for Platform code by Evan Felix <karcaw at gmail.com>
9  * Copyright (C) 2012  Sylvain Beucler
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27  */
28
29 #include <GL/freeglut.h>
30 #include "fg_internal.h"
31 #include "fg_main.h"
32 #include "egl/fg_window_egl.h"
33
34 #include <android/log.h>
35 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FreeGLUT", __VA_ARGS__))
36 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "FreeGLUT", __VA_ARGS__))
37 #include <android/native_app_glue/android_native_app_glue.h>
38 #include <android/keycodes.h>
39
40 static struct touchscreen touchscreen;
41 static unsigned char key_a2fg[256];
42
43 /* Cf. http://developer.android.com/reference/android/view/KeyEvent.html */
44 /* These codes are missing in <android/keycodes.h> */
45 /* Don't convert to enum, since it may conflict with future version of
46    that <android/keycodes.h> */
47 #define AKEYCODE_FORWARD_DEL 112
48 #define AKEYCODE_CTRL_LEFT 113
49 #define AKEYCODE_CTRL_RIGHT 114
50 #define AKEYCODE_MOVE_HOME 122
51 #define AKEYCODE_MOVE_END 123
52 #define AKEYCODE_INSERT 124
53 #define AKEYCODE_ESCAPE 127
54 #define AKEYCODE_F1 131
55 #define AKEYCODE_F2 132
56 #define AKEYCODE_F3 133
57 #define AKEYCODE_F4 134
58 #define AKEYCODE_F5 135
59 #define AKEYCODE_F6 136
60 #define AKEYCODE_F7 137
61 #define AKEYCODE_F8 138
62 #define AKEYCODE_F9 139
63 #define AKEYCODE_F10 140
64 #define AKEYCODE_F11 141
65 #define AKEYCODE_F12 142
66
67 #define EVENT_HANDLED 1
68 #define EVENT_NOT_HANDLED 0
69
70 /**
71  * Initialize Android keycode to GLUT keycode mapping
72  */
73 static void key_init() {
74   memset(key_a2fg, 0, sizeof(key_a2fg));
75
76   key_a2fg[AKEYCODE_F1]  = GLUT_KEY_F1;
77   key_a2fg[AKEYCODE_F2]  = GLUT_KEY_F2;
78   key_a2fg[AKEYCODE_F3]  = GLUT_KEY_F3;
79   key_a2fg[AKEYCODE_F4]  = GLUT_KEY_F4;
80   key_a2fg[AKEYCODE_F5]  = GLUT_KEY_F5;
81   key_a2fg[AKEYCODE_F6]  = GLUT_KEY_F6;
82   key_a2fg[AKEYCODE_F7]  = GLUT_KEY_F7;
83   key_a2fg[AKEYCODE_F8]  = GLUT_KEY_F8;
84   key_a2fg[AKEYCODE_F9]  = GLUT_KEY_F9;
85   key_a2fg[AKEYCODE_F10] = GLUT_KEY_F10;
86   key_a2fg[AKEYCODE_F11] = GLUT_KEY_F11;
87   key_a2fg[AKEYCODE_F12] = GLUT_KEY_F12;
88
89   key_a2fg[AKEYCODE_PAGE_UP]   = GLUT_KEY_PAGE_UP;
90   key_a2fg[AKEYCODE_PAGE_DOWN] = GLUT_KEY_PAGE_DOWN;
91   key_a2fg[AKEYCODE_MOVE_HOME] = GLUT_KEY_HOME;
92   key_a2fg[AKEYCODE_MOVE_END]  = GLUT_KEY_END;
93   key_a2fg[AKEYCODE_INSERT]    = GLUT_KEY_INSERT;
94
95   key_a2fg[AKEYCODE_DPAD_UP]    = GLUT_KEY_UP;
96   key_a2fg[AKEYCODE_DPAD_DOWN]  = GLUT_KEY_DOWN;
97   key_a2fg[AKEYCODE_DPAD_LEFT]  = GLUT_KEY_LEFT;
98   key_a2fg[AKEYCODE_DPAD_RIGHT] = GLUT_KEY_RIGHT;
99
100   key_a2fg[AKEYCODE_ALT_LEFT]    = GLUT_KEY_ALT_L;
101   key_a2fg[AKEYCODE_ALT_RIGHT]   = GLUT_KEY_ALT_R;
102   key_a2fg[AKEYCODE_SHIFT_LEFT]  = GLUT_KEY_SHIFT_L;
103   key_a2fg[AKEYCODE_SHIFT_RIGHT] = GLUT_KEY_SHIFT_R;
104   key_a2fg[AKEYCODE_CTRL_LEFT]   = GLUT_KEY_CTRL_L;
105   key_a2fg[AKEYCODE_CTRL_RIGHT]  = GLUT_KEY_CTRL_R;
106 }
107
108 /**
109  * Convert an Android key event to ASCII.
110  */
111 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
112   int32_t code = AKeyEvent_getKeyCode(event);
113
114   /* Handle a few special cases: */
115   switch (code) {
116   case AKEYCODE_DEL:
117     return 8;
118   case AKEYCODE_FORWARD_DEL:
119     return 127;
120   case AKEYCODE_ESCAPE:
121     return 27;
122   }
123
124   /* Get usable JNI context */
125   JNIEnv* env = app->activity->env;
126   JavaVM* vm = app->activity->vm;
127   (*vm)->AttachCurrentThread(vm, &env, NULL);
128
129   jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent");
130   jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V");
131   jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor,
132                                        AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event));
133   jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I");
134   int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
135
136   /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
137   (*vm)->DetachCurrentThread(vm);
138
139   return ascii;
140 }
141
142 /*
143  * Request a window resize
144  */
145 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
146 {
147   fprintf(stderr, "fgPlatformReshapeWindow: STUB\n");
148 }
149
150 /*
151  * A static helper function to execute display callback for a window
152  */
153 void fgPlatformDisplayWindow ( SFG_Window *window )
154 {
155   fghRedrawWindow ( window ) ;
156 }
157
158 unsigned long fgPlatformSystemTime ( void )
159 {
160   struct timeval now;
161   gettimeofday( &now, NULL );
162   return now.tv_usec/1000 + now.tv_sec*1000;
163 }
164
165 /*
166  * Does the magic required to relinquish the CPU until something interesting
167  * happens.
168  */
169 void fgPlatformSleepForEvents( long msec )
170 {
171     /* Android's NativeActivity relies on a Looper/ALooper object to
172        notify about events.  The Looper object is plugged on two
173        internal pipe(2)s to detect system and input events.  Sadly you
174        can only ask the Looper for an event, not just ask whether
175        there is a pending event (and process it later).  Consequently,
176        short of redesigning NativeActivity, we cannot
177        SleepForEvents. */
178 }
179
180 /**
181  * Process the next input event.
182  */
183 int32_t handle_input(struct android_app* app, AInputEvent* event) {
184   SFG_Window* window = fgWindowByHandle(app->window);
185   if (window == NULL)
186     return EVENT_NOT_HANDLED;
187
188   /* FIXME: in Android, when key is repeated, down and up events
189      happen most often at the exact same time.  This makes it
190      impossible to animate based on key press time. */
191   /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
192   
193   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
194     /* LOGI("action: %d", AKeyEvent_getAction(event)); */
195     /* LOGI("keycode: %d", code); */
196     int32_t code = AKeyEvent_getKeyCode(event);
197
198     if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
199       int32_t keypress = 0;
200       unsigned char ascii = 0;
201       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
202         INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
203         return EVENT_HANDLED;
204       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
205         INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
206         return EVENT_HANDLED;
207       }
208     }
209     else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
210       int32_t keypress = 0;
211       unsigned char ascii = 0;
212       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
213         INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
214         return EVENT_HANDLED;
215       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
216         INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
217         return EVENT_HANDLED;
218       }
219     }
220   }
221
222   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
223     int32_t action = AMotionEvent_getAction(event);
224     float x = AMotionEvent_getX(event, 0);
225     float y = AMotionEvent_getY(event, 0);
226     LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
227     
228     /* Virtual arrows PAD */
229     /* Don't interfere with existing mouse move event */
230     if (!touchscreen.in_mmotion) {
231       struct vpad_state prev_vpad = touchscreen.vpad;
232       touchscreen.vpad.left = touchscreen.vpad.right
233         = touchscreen.vpad.up = touchscreen.vpad.down = false;
234
235       /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
236       int32_t height = ANativeWindow_getHeight(window->Window.Handle);
237       if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
238         if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
239           touchscreen.vpad.left = true;
240         if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
241           touchscreen.vpad.right = true;
242         if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
243           touchscreen.vpad.down = true;
244         if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
245           touchscreen.vpad.up = true;
246       }
247       if (action == AMOTION_EVENT_ACTION_DOWN && 
248           (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
249         touchscreen.vpad.on = true;
250       if (action == AMOTION_EVENT_ACTION_UP)
251         touchscreen.vpad.on = false;
252       if (prev_vpad.left != touchscreen.vpad.left
253           || prev_vpad.right != touchscreen.vpad.right
254           || prev_vpad.up != touchscreen.vpad.up
255           || prev_vpad.down != touchscreen.vpad.down
256           || prev_vpad.on != touchscreen.vpad.on) {
257         if (FETCH_WCB(*window, Special)) {
258           if (prev_vpad.left == false && touchscreen.vpad.left == true)
259             INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
260           else if (prev_vpad.right == false && touchscreen.vpad.right == true)
261             INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
262           else if (prev_vpad.up == false && touchscreen.vpad.up == true)
263             INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
264           else if (prev_vpad.down == false && touchscreen.vpad.down == true)
265             INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
266         }
267         if (FETCH_WCB(*window, SpecialUp)) {
268           if (prev_vpad.left == true && touchscreen.vpad.left == false)
269             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
270           if (prev_vpad.right == true && touchscreen.vpad.right == false)
271             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
272           if (prev_vpad.up == true && touchscreen.vpad.up == false)
273             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
274           if (prev_vpad.down == true && touchscreen.vpad.down == false)
275             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
276         }
277         return EVENT_HANDLED;
278       }
279     }
280     
281     /* Normal mouse events */
282     if (!touchscreen.vpad.on) {
283       window->State.MouseX = x;
284       window->State.MouseY = y;
285       LOGI("Changed mouse position: %f,%f", x, y);
286       if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
287         touchscreen.in_mmotion = true;
288         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
289       } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
290         touchscreen.in_mmotion = false;
291         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
292       } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
293         INVOKE_WCB(*window, Motion, (x, y));
294       }
295     }
296     
297     return EVENT_HANDLED;
298   }
299
300   /* Let Android handle other events (e.g. Back and Menu buttons) */
301   return EVENT_NOT_HANDLED;
302 }
303
304 /**
305  * Process the next main command.
306  */
307 void handle_cmd(struct android_app* app, int32_t cmd) {
308   SFG_Window* window = fgWindowByHandle(app->window);  /* may be NULL */
309   switch (cmd) {
310   /* App life cycle, in that order: */
311   case APP_CMD_START:
312     LOGI("handle_cmd: APP_CMD_START");
313     break;
314   case APP_CMD_RESUME:
315     LOGI("handle_cmd: APP_CMD_RESUME");
316     /* Cf. fgPlatformProcessSingleEvent */
317     break;
318   case APP_CMD_INIT_WINDOW: /* surfaceCreated */
319     /* The window is being shown, get it ready. */
320     LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
321     fgDisplay.pDisplay.single_native_window = app->window;
322     /* glPlatformOpenWindow was waiting for Handle to be defined and
323        will now return from fgPlatformProcessSingleEvent() */
324     break;
325   case APP_CMD_GAINED_FOCUS:
326     LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
327     break;
328   case APP_CMD_WINDOW_RESIZED:
329     LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
330     if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
331       /* Make ProcessSingleEvent detect the new size, only available
332          after the next SwapBuffer */
333       glutPostRedisplay();
334     break;
335
336   case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
337     /* The system has asked us to save our current state, when it
338        pauses the application without destroying it right after. */
339     app->savedState = strdup("Detect me as non-NULL on next android_main");
340     app->savedStateSize = strlen(app->savedState) + 1;
341     LOGI("handle_cmd: APP_CMD_SAVE_STATE");
342     break;
343   case APP_CMD_PAUSE:
344     LOGI("handle_cmd: APP_CMD_PAUSE");
345     /* Cf. fgPlatformProcessSingleEvent */
346     break;
347   case APP_CMD_LOST_FOCUS:
348     LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
349     break;
350   case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
351     /* The application is being hidden, but may be restored */
352     LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
353     fghPlatformCloseWindowEGL(window);
354     fgDisplay.pDisplay.single_native_window = NULL;
355     break;
356   case APP_CMD_STOP:
357     LOGI("handle_cmd: APP_CMD_STOP");
358     break;
359   case APP_CMD_DESTROY: /* Activity.onDestroy */
360     LOGI("handle_cmd: APP_CMD_DESTROY");
361     /* User closed the application for good, let's kill the window */
362     {
363       /* Can't use fgWindowByHandle as app->window is NULL */
364       SFG_Window* window = fgStructure.CurrentWindow;
365       if (window != NULL) {
366         fgDestroyWindow(window);
367       } else {
368         LOGI("APP_CMD_DESTROY: No current window");
369       }
370     }
371     /* glue has already set android_app->destroyRequested=1 */
372     break;
373
374   case APP_CMD_CONFIG_CHANGED:
375     /* Handle rotation / orientation change */
376     LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
377     break;
378   case APP_CMD_LOW_MEMORY:
379     LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
380     break;
381   default:
382     LOGI("handle_cmd: unhandled cmd=%d", cmd);
383   }
384 }
385
386 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
387                            GLboolean positionUse, int x, int y,
388                            GLboolean sizeUse, int w, int h,
389                            GLboolean gameMode, GLboolean isSubWindow );
390
391 void fgPlatformProcessSingleEvent ( void )
392 {
393   /* When the screen is resized, the window handle still points to the
394      old window until the next SwapBuffer, while it's crucial to set
395      the size (onShape) correctly before the next onDisplay callback.
396      Plus we don't know if the next SwapBuffer already occurred at the
397      time we process the event (e.g. during onDisplay). */
398   /* So we do the check each time rather than on event. */
399   /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
400      2.3, that next SwapBuffer is fake (but still necessary to get the
401      new size). */
402   SFG_Window* window = fgStructure.CurrentWindow;
403   if (window != NULL && window->Window.Handle != NULL) {
404     int32_t width = ANativeWindow_getWidth(window->Window.Handle);
405     int32_t height = ANativeWindow_getHeight(window->Window.Handle);
406     if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
407       window->State.pWState.LastWidth = width;
408       window->State.pWState.LastHeight = height;
409       LOGI("width=%d, height=%d", width, height);
410       if( FETCH_WCB( *window, Reshape ) )
411         INVOKE_WCB( *window, Reshape, ( width, height ) );
412       else
413         glViewport( 0, 0, width, height );
414       glutPostRedisplay();
415     }
416   }
417
418   /* Read pending event. */
419   int ident;
420   int events;
421   struct android_poll_source* source;
422   /* This is called "ProcessSingleEvent" but this means we'd only
423      process ~60 (screen Hz) mouse events per second, plus other ports
424      are processing all events already.  So let's process all pending
425      events. */
426   /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
427   while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
428     /* Process this event. */
429     if (source != NULL) {
430       source->process(source->app, source);
431     }
432   }
433
434   /* If we're not in RESUME state, Android paused us, so wait */
435   struct android_app* app = fgDisplay.pDisplay.app;
436   if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
437     int FOREVER = -1;
438     while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
439       if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
440         /* Process this event. */
441         if (source != NULL) {
442           source->process(source->app, source);
443         }
444       }
445     }
446     /* If coming back from a pause: */
447     /* - Recreate window context and surface */
448     /* - Call user-defined hook to restore resources (textures...) */
449     /* - Exit pause looop */
450     if (app->destroyRequested != 1) {
451       /* Android is full-screen only, simplified call.. */
452       /* Ideally we'd have a fgPlatformReopenWindow() */
453       fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
454       /* TODO: INVOKE_WCB(*window, Pause?); */
455       /* TODO: INVOKE_WCB(*window, LoadResources/ContextLost/...?); */
456       /* TODO: INVOKE_WCB(*window, Resume?); */
457     }
458   }
459 }
460
461 void fgPlatformMainLoopPreliminaryWork ( void )
462 {
463   LOGI("fgPlatformMainLoopPreliminaryWork\n");
464
465   key_init();
466
467   /* Make sure glue isn't stripped. */
468   /* JNI entry points need to be bundled even when linking statically */
469   app_dummy();
470 }