Document android app lifecycle; kill app when window is closed, until pausing/restori...
[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
33 #include <android/log.h>
34 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FreeGLUT", __VA_ARGS__))
35 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "FreeGLUT", __VA_ARGS__))
36 #include <android/native_app_glue/android_native_app_glue.h>
37 #include <android/keycodes.h>
38
39 static struct touchscreen touchscreen;
40 static unsigned char key_a2fg[256];
41
42 /* Cf. http://developer.android.com/reference/android/view/KeyEvent.html */
43 /* These codes are missing in <android/keycodes.h> */
44 /* Don't convert to enum, since it may conflict with future version of
45    that <android/keycodes.h> */
46 #define AKEYCODE_FORWARD_DEL 112
47 #define AKEYCODE_CTRL_LEFT 113
48 #define AKEYCODE_CTRL_RIGHT 114
49 #define AKEYCODE_MOVE_HOME 122
50 #define AKEYCODE_MOVE_END 123
51 #define AKEYCODE_INSERT 124
52 #define AKEYCODE_ESCAPE 127
53 #define AKEYCODE_F1 131
54 #define AKEYCODE_F2 132
55 #define AKEYCODE_F3 133
56 #define AKEYCODE_F4 134
57 #define AKEYCODE_F5 135
58 #define AKEYCODE_F6 136
59 #define AKEYCODE_F7 137
60 #define AKEYCODE_F8 138
61 #define AKEYCODE_F9 139
62 #define AKEYCODE_F10 140
63 #define AKEYCODE_F11 141
64 #define AKEYCODE_F12 142
65
66 #define EVENT_HANDLED 1
67 #define EVENT_NOT_HANDLED 0
68
69 /**
70  * Initialize Android keycode to GLUT keycode mapping
71  */
72 static void key_init() {
73   memset(key_a2fg, 0, sizeof(key_a2fg));
74
75   key_a2fg[AKEYCODE_F1]  = GLUT_KEY_F1;
76   key_a2fg[AKEYCODE_F2]  = GLUT_KEY_F2;
77   key_a2fg[AKEYCODE_F3]  = GLUT_KEY_F3;
78   key_a2fg[AKEYCODE_F4]  = GLUT_KEY_F4;
79   key_a2fg[AKEYCODE_F5]  = GLUT_KEY_F5;
80   key_a2fg[AKEYCODE_F6]  = GLUT_KEY_F6;
81   key_a2fg[AKEYCODE_F7]  = GLUT_KEY_F7;
82   key_a2fg[AKEYCODE_F8]  = GLUT_KEY_F8;
83   key_a2fg[AKEYCODE_F9]  = GLUT_KEY_F9;
84   key_a2fg[AKEYCODE_F10] = GLUT_KEY_F10;
85   key_a2fg[AKEYCODE_F11] = GLUT_KEY_F11;
86   key_a2fg[AKEYCODE_F12] = GLUT_KEY_F12;
87
88   key_a2fg[AKEYCODE_PAGE_UP]   = GLUT_KEY_PAGE_UP;
89   key_a2fg[AKEYCODE_PAGE_DOWN] = GLUT_KEY_PAGE_DOWN;
90   key_a2fg[AKEYCODE_MOVE_HOME] = GLUT_KEY_HOME;
91   key_a2fg[AKEYCODE_MOVE_END]  = GLUT_KEY_END;
92   key_a2fg[AKEYCODE_INSERT]    = GLUT_KEY_INSERT;
93
94   key_a2fg[AKEYCODE_DPAD_UP]    = GLUT_KEY_UP;
95   key_a2fg[AKEYCODE_DPAD_DOWN]  = GLUT_KEY_DOWN;
96   key_a2fg[AKEYCODE_DPAD_LEFT]  = GLUT_KEY_LEFT;
97   key_a2fg[AKEYCODE_DPAD_RIGHT] = GLUT_KEY_RIGHT;
98
99   key_a2fg[AKEYCODE_ALT_LEFT]    = GLUT_KEY_ALT_L;
100   key_a2fg[AKEYCODE_ALT_RIGHT]   = GLUT_KEY_ALT_R;
101   key_a2fg[AKEYCODE_SHIFT_LEFT]  = GLUT_KEY_SHIFT_L;
102   key_a2fg[AKEYCODE_SHIFT_RIGHT] = GLUT_KEY_SHIFT_R;
103   key_a2fg[AKEYCODE_CTRL_LEFT]   = GLUT_KEY_CTRL_L;
104   key_a2fg[AKEYCODE_CTRL_RIGHT]  = GLUT_KEY_CTRL_R;
105 }
106
107 /**
108  * Convert an Android key event to ASCII.
109  */
110 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
111   int32_t code = AKeyEvent_getKeyCode(event);
112
113   /* Handle a few special cases: */
114   switch (code) {
115   case AKEYCODE_DEL:
116     return 8;
117   case AKEYCODE_FORWARD_DEL:
118     return 127;
119   case AKEYCODE_ESCAPE:
120     return 27;
121   }
122
123   /* Get usable JNI context */
124   JNIEnv* env = app->activity->env;
125   JavaVM* vm = app->activity->vm;
126   (*vm)->AttachCurrentThread(vm, &env, NULL);
127
128   jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent");
129   jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V");
130   jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor,
131                                        AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event));
132   jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I");
133   int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
134
135   /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
136   (*vm)->DetachCurrentThread(vm);
137
138   return ascii;
139 }
140
141 /*
142  * Request a window resize
143  */
144 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
145 {
146   fprintf(stderr, "fgPlatformReshapeWindow: STUB\n");
147 }
148
149 /*
150  * A static helper function to execute display callback for a window
151  */
152 void fgPlatformDisplayWindow ( SFG_Window *window )
153 {
154   fghRedrawWindow ( window ) ;
155 }
156
157 unsigned long fgPlatformSystemTime ( void )
158 {
159   struct timeval now;
160   gettimeofday( &now, NULL );
161   return now.tv_usec/1000 + now.tv_sec*1000;
162 }
163
164 /*
165  * Does the magic required to relinquish the CPU until something interesting
166  * happens.
167  */
168 void fgPlatformSleepForEvents( long msec )
169 {
170     /* Android's NativeActivity relies on a Looper/ALooper object to
171        notify about events.  The Looper object is plugged on two
172        internal pipe(2)s to detect system and input events.  Sadly you
173        can only ask the Looper for an event, not just ask whether
174        there is a pending event (and process it later).  Consequently,
175        short of redesigning NativeActivity, we cannot
176        SleepForEvents. */
177 }
178
179 /**
180  * Process the next input event.
181  */
182 int32_t handle_input(struct android_app* app, AInputEvent* event) {
183   SFG_Window* window = fgStructure.CurrentWindow;
184
185   /* FIXME: in Android, when key is repeated, down and up events
186      happen most often at the exact same time.  This makes it
187      impossible to animate based on key press time. */
188   /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
189   
190   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
191     /* LOGI("action: %d", AKeyEvent_getAction(event)); */
192     /* LOGI("keycode: %d", code); */
193     int32_t code = AKeyEvent_getKeyCode(event);
194
195     if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
196       int32_t keypress = 0;
197       unsigned char ascii = 0;
198       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
199         INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
200         return EVENT_HANDLED;
201       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
202         INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
203         return EVENT_HANDLED;
204       }
205     }
206     else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
207       int32_t keypress = 0;
208       unsigned char ascii = 0;
209       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
210         INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
211         return EVENT_HANDLED;
212       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
213         INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
214         return EVENT_HANDLED;
215       }
216     }
217   }
218
219   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
220     int32_t action = AMotionEvent_getAction(event);
221     float x = AMotionEvent_getX(event, 0);
222     float y = AMotionEvent_getY(event, 0);
223     LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
224     
225     /* Virtual arrows PAD */
226     /* Don't interfere with existing mouse move event */
227     if (!touchscreen.in_mmotion) {
228       struct vpad_state prev_vpad = touchscreen.vpad;
229       touchscreen.vpad.left = touchscreen.vpad.right
230         = touchscreen.vpad.up = touchscreen.vpad.down = false;
231
232       /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
233       int32_t height = ANativeWindow_getHeight(window->Window.Handle);
234       if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
235         if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
236           touchscreen.vpad.left = true;
237         if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
238           touchscreen.vpad.right = true;
239         if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
240           touchscreen.vpad.down = true;
241         if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
242           touchscreen.vpad.up = true;
243       }
244       if (action == AMOTION_EVENT_ACTION_DOWN && 
245           (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
246         touchscreen.vpad.on = true;
247       if (action == AMOTION_EVENT_ACTION_UP)
248         touchscreen.vpad.on = false;
249       if (prev_vpad.left != touchscreen.vpad.left
250           || prev_vpad.right != touchscreen.vpad.right
251           || prev_vpad.up != touchscreen.vpad.up
252           || prev_vpad.down != touchscreen.vpad.down
253           || prev_vpad.on != touchscreen.vpad.on) {
254         if (FETCH_WCB(*window, Special)) {
255           if (prev_vpad.left == false && touchscreen.vpad.left == true)
256             INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
257           else if (prev_vpad.right == false && touchscreen.vpad.right == true)
258             INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
259           else if (prev_vpad.up == false && touchscreen.vpad.up == true)
260             INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
261           else if (prev_vpad.down == false && touchscreen.vpad.down == true)
262             INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
263         }
264         if (FETCH_WCB(*window, SpecialUp)) {
265           if (prev_vpad.left == true && touchscreen.vpad.left == false)
266             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
267           if (prev_vpad.right == true && touchscreen.vpad.right == false)
268             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
269           if (prev_vpad.up == true && touchscreen.vpad.up == false)
270             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
271           if (prev_vpad.down == true && touchscreen.vpad.down == false)
272             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
273         }
274         return EVENT_HANDLED;
275       }
276     }
277     
278     /* Normal mouse events */
279     if (!touchscreen.vpad.on) {
280       window->State.MouseX = x;
281       window->State.MouseY = y;
282       LOGI("Changed mouse position: %f,%f", x, y);
283       if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
284         touchscreen.in_mmotion = true;
285         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
286       } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
287         touchscreen.in_mmotion = false;
288         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
289       } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
290         INVOKE_WCB(*window, Motion, (x, y));
291       }
292     }
293     
294     return EVENT_HANDLED;
295   }
296
297   /* Let Android handle other events (e.g. Back and Menu buttons) */
298   return EVENT_NOT_HANDLED;
299 }
300
301 /**
302  * Process the next main command.
303  */
304 void handle_cmd(struct android_app* app, int32_t cmd) {
305   switch (cmd) {
306   /* App life cycle, in that order: */
307   case APP_CMD_START:
308     LOGI("handle_cmd: APP_CMD_START");
309     break;
310   case APP_CMD_RESUME:
311     LOGI("handle_cmd: APP_CMD_RESUME");
312     /* If coming back from a pause: */
313     /* - Recreate window context and surface */
314     /* - Call user-defined hook to restore resources (textures...) */
315     /* - Unpause GLUT callbacks */
316     break;
317   case APP_CMD_INIT_WINDOW: /* surfaceCreated */
318     /* The window is being shown, get it ready. */
319     LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
320     fgDisplay.pDisplay.single_window->Window.Handle = app->window;
321     /* glPlatformOpenWindow was waiting for Handle to be defined and
322        will now return from fgPlatformProcessSingleEvent() */
323     break;
324   case APP_CMD_GAINED_FOCUS:
325     LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
326     break;
327   case APP_CMD_WINDOW_RESIZED:
328     LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
329     if (fgDisplay.pDisplay.single_window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
330       /* Make ProcessSingleEvent detect the new size, only available
331          after the next SwapBuffer */
332       glutPostRedisplay();
333     break;
334
335   case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
336     /* The system has asked us to save our current state, when it
337        pauses the application without destroying it right after. */
338     /* app->savedState = ... */
339     /* app->savedStateSize = ... */
340     LOGI("handle_cmd: APP_CMD_SAVE_STATE");
341     break;
342   case APP_CMD_PAUSE:
343     LOGI("handle_cmd: APP_CMD_PAUSE");
344     /* - Pause GLUT callbacks */
345     break;
346   case APP_CMD_LOST_FOCUS:
347     LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
348     break;
349   case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
350     /* The application is being hidden, but may be restored */
351     /* TODO: Pausing/resuming windows not ready yet, so killing it now */
352     fgDestroyWindow(fgDisplay.pDisplay.single_window);
353     fgDisplay.pDisplay.single_window = NULL;
354     LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
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     if (fgDisplay.pDisplay.single_window != NULL) {
363       fgDestroyWindow(fgDisplay.pDisplay.single_window);
364       fgDisplay.pDisplay.single_window = NULL;
365     }
366     /* glue has already set android_app->destroyRequested=1 */
367     break;
368
369   case APP_CMD_CONFIG_CHANGED:
370     /* Handle rotation / orientation change */
371     LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
372     break;
373   case APP_CMD_LOW_MEMORY:
374     LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
375     break;
376   default:
377     LOGI("handle_cmd: unhandled cmd=%d", cmd);
378   }
379 }
380
381 void fgPlatformProcessSingleEvent ( void )
382 {
383   /* When the screen is resized, the window handle still points to the
384      old window until the next SwapBuffer, while it's crucial to set
385      the size (onShape) correctly before the next onDisplay callback.
386      Plus we don't know if the next SwapBuffer already occurred at the
387      time we process the event (e.g. during onDisplay). */
388   /* So we do the check each time rather than on event. */
389   /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
390      2.3, that next SwapBuffer is fake (but still necessary to get the
391      new size). */
392   SFG_Window* window = fgDisplay.pDisplay.single_window;
393   if (window != NULL && window->Window.Handle != NULL) {
394     int32_t width = ANativeWindow_getWidth(window->Window.Handle);
395     int32_t height = ANativeWindow_getHeight(window->Window.Handle);
396     if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
397       window->State.pWState.LastWidth = width;
398       window->State.pWState.LastHeight = height;
399       LOGI("width=%d, height=%d", width, height);
400       if( FETCH_WCB( *window, Reshape ) )
401         INVOKE_WCB( *window, Reshape, ( width, height ) );
402       else
403         glViewport( 0, 0, width, height );
404       glutPostRedisplay();
405     }
406   }
407
408   /* Read pending event. */
409   int ident;
410   int events;
411   struct android_poll_source* source;
412   /* This is called "ProcessSingleEvent" but this means we'd only
413      process ~60 (screen Hz) mouse events per second, plus other ports
414      are processing all events already.  So let's process all pending
415      events. */
416   /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
417   while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
418     /* Process this event. */
419     if (source != NULL) {
420       source->process(source->app, source);
421     }
422   }
423 }
424
425 void fgPlatformMainLoopPreliminaryWork ( void )
426 {
427   LOGI("fgPlatformMainLoopPreliminaryWork\n");
428
429   key_init();
430
431   /* Make sure glue isn't stripped. */
432   /* JNI entry points need to be bundled even when linking statically */
433   app_dummy();
434 }