typo
[freeglut] / src / android / fg_main_android.c
index 3f973f2..67bdead 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * freeglut_main_android.c
+ * fg_main_android.c
  *
  * The Android-specific windows message processing methods.
  *
@@ -28,6 +28,8 @@
 
 #include <GL/freeglut.h>
 #include "fg_internal.h"
+#include "fg_main.h"
+#include "egl/fg_window_egl.h"
 
 #include <android/log.h>
 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FreeGLUT", __VA_ARGS__))
@@ -132,14 +134,13 @@ static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
   int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
 
   /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
+  (*vm)->DetachCurrentThread(vm);
 
   return ascii;
 }
 
 /*
- * Handle a window configuration change. When no reshape
- * callback is hooked, the viewport size is updated to
- * match the new window size.
+ * Request a window resize
  */
 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
 {
@@ -167,19 +168,30 @@ unsigned long fgPlatformSystemTime ( void )
  */
 void fgPlatformSleepForEvents( long msec )
 {
-  /* fprintf(stderr, "fgPlatformSleepForEvents: STUB\n"); */
+    /* Android's NativeActivity relies on a Looper/ALooper object to
+       notify about events.  The Looper object is plugged on two
+       internal pipe(2)s to detect system and input events.  Sadly you
+       can only ask the Looper for an event, not just ask whether
+       there is a pending event (and process it later).  Consequently,
+       short of redesigning NativeActivity, we cannot
+       SleepForEvents. */
 }
 
 /**
  * Process the next input event.
  */
 int32_t handle_input(struct android_app* app, AInputEvent* event) {
-  SFG_Window* window = fgStructure.CurrentWindow;
-
-  /* FIXME: in Android, when key is repeated, down and up events
-     happen most often at the exact same time.  This makes it
-     impossible to animate based on key press time. */
-  /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
+  SFG_Window* window = fgWindowByHandle(app->window);
+  if (window == NULL)
+    return EVENT_NOT_HANDLED;
+
+  /* FIXME: in Android, when a key is repeated, down
+     and up events happen most often at the exact same time.  This
+     makes it impossible to animate based on key press time. */
+  /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */ 
+  /* This looks like a bug in the Android virtual keyboard system :/
+     Real buttons such as the Back button appear to work correctly
+     (series of down events with proper getRepeatCount value). */
   
   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
     /* LOGI("action: %d", AKeyEvent_getAction(event)); */
@@ -217,13 +229,13 @@ int32_t handle_input(struct android_app* app, AInputEvent* event) {
     LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
     
     /* Virtual arrows PAD */
-    // Don't interfere with existing mouse move event
+    /* Don't interfere with existing mouse move event */
     if (!touchscreen.in_mmotion) {
       struct vpad_state prev_vpad = touchscreen.vpad;
       touchscreen.vpad.left = touchscreen.vpad.right
        = touchscreen.vpad.up = touchscreen.vpad.down = false;
 
-      int32_t width = ANativeWindow_getWidth(window->Window.Handle);
+      /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
       int32_t height = ANativeWindow_getHeight(window->Window.Handle);
       if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
        if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
@@ -273,7 +285,7 @@ int32_t handle_input(struct android_app* app, AInputEvent* event) {
     if (!touchscreen.vpad.on) {
       window->State.MouseX = x;
       window->State.MouseY = y;
-      LOGI("Changed mouse position: %d,%d", x, y);
+      LOGI("Changed mouse position: %f,%f", x, y);
       if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
        touchscreen.in_mmotion = true;
        INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
@@ -296,54 +308,91 @@ int32_t handle_input(struct android_app* app, AInputEvent* event) {
  * Process the next main command.
  */
 void handle_cmd(struct android_app* app, int32_t cmd) {
+  SFG_Window* window = fgWindowByHandle(app->window);  /* may be NULL */
   switch (cmd) {
-  case APP_CMD_SAVE_STATE:
-    /* The system has asked us to save our current state.  Do so. */
-    LOGI("handle_cmd: APP_CMD_SAVE_STATE");
+  /* App life cycle, in that order: */
+  case APP_CMD_START:
+    LOGI("handle_cmd: APP_CMD_START");
     break;
-  case APP_CMD_INIT_WINDOW:
-    /* The window is being shown, get it ready. */
-    LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
-    fgDisplay.pDisplay.single_window->Window.Handle = app->window;
-    /* glPlatformOpenWindow was waiting for Handle to be defined and
-       will now return from fgPlatformProcessSingleEvent() */
-    break;
-  case APP_CMD_TERM_WINDOW:
-    /* The window is being hidden or closed, clean it up. */
-    LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
-    fgDestroyWindow(fgDisplay.pDisplay.single_window);
+  case APP_CMD_RESUME:
+    LOGI("handle_cmd: APP_CMD_RESUME");
+    /* Cf. fgPlatformProcessSingleEvent */
     break;
-  case APP_CMD_DESTROY:
-    /* Not reached because GLUT exit()s when last window is closed */
-    LOGI("handle_cmd: APP_CMD_DESTROY");
+  case APP_CMD_INIT_WINDOW: /* surfaceCreated */
+    /* The window is being shown, get it ready. */
+    LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
+    fgDisplay.pDisplay.single_native_window = app->window;
+    /* start|resume: glPlatformOpenWindow was waiting for Handle to be
+       defined and will now continue processing */
     break;
   case APP_CMD_GAINED_FOCUS:
     LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
     break;
+  case APP_CMD_WINDOW_RESIZED:
+    LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
+    if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
+      /* Make ProcessSingleEvent detect the new size, only available
+        after the next SwapBuffer */
+      glutPostRedisplay();
+    break;
+
+  case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
+    /* The system has asked us to save our current state, when it
+       pauses the application without destroying it right after. */
+    app->savedState = strdup("Detect me as non-NULL on next android_main");
+    app->savedStateSize = strlen(app->savedState) + 1;
+    LOGI("handle_cmd: APP_CMD_SAVE_STATE");
+    break;
+  case APP_CMD_PAUSE:
+    LOGI("handle_cmd: APP_CMD_PAUSE");
+    /* Cf. fgPlatformProcessSingleEvent */
+    break;
   case APP_CMD_LOST_FOCUS:
     LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
     break;
+  case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
+    /* The application is being hidden, but may be restored */
+    LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
+    fghPlatformCloseWindowEGL(window);
+    fgDisplay.pDisplay.single_native_window = NULL;
+    break;
+  case APP_CMD_STOP:
+    LOGI("handle_cmd: APP_CMD_STOP");
+    break;
+  case APP_CMD_DESTROY: /* Activity.onDestroy */
+    LOGI("handle_cmd: APP_CMD_DESTROY");
+    /* User closed the application for good, let's kill the window */
+    {
+      /* Can't use fgWindowByHandle as app->window is NULL */
+      SFG_Window* window = fgStructure.CurrentWindow;
+      if (window != NULL) {
+        fgDestroyWindow(window);
+      } else {
+        LOGI("APP_CMD_DESTROY: No current window");
+      }
+    }
+    /* glue has already set android_app->destroyRequested=1 */
+    break;
+
   case APP_CMD_CONFIG_CHANGED:
     /* Handle rotation / orientation change */
     LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
     break;
-  case APP_CMD_WINDOW_RESIZED:
-    LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
-    if (fgDisplay.pDisplay.single_window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
-      /* Make ProcessSingleEvent detect the new size, only available
-        after the next SwapBuffer */
-      glutPostRedisplay();
+  case APP_CMD_LOW_MEMORY:
+    LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
     break;
   default:
     LOGI("handle_cmd: unhandled cmd=%d", cmd);
   }
 }
 
+void fgPlatformOpenWindow( SFG_Window* window, const char* title,
+                           GLboolean positionUse, int x, int y,
+                           GLboolean sizeUse, int w, int h,
+                           GLboolean gameMode, GLboolean isSubWindow );
+
 void fgPlatformProcessSingleEvent ( void )
 {
-  static int32_t last_width = -1;
-  static int32_t last_height = -1;
-
   /* When the screen is resized, the window handle still points to the
      old window until the next SwapBuffer, while it's crucial to set
      the size (onShape) correctly before the next onDisplay callback.
@@ -353,13 +402,13 @@ void fgPlatformProcessSingleEvent ( void )
   /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
      2.3, that next SwapBuffer is fake (but still necessary to get the
      new size). */
-  SFG_Window* window = fgDisplay.pDisplay.single_window;
+  SFG_Window* window = fgStructure.CurrentWindow;
   if (window != NULL && window->Window.Handle != NULL) {
     int32_t width = ANativeWindow_getWidth(window->Window.Handle);
     int32_t height = ANativeWindow_getHeight(window->Window.Handle);
-    if (width != last_width || height != last_height) {
-      last_width = width;
-      last_height = height;
+    if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
+      window->State.pWState.LastWidth = width;
+      window->State.pWState.LastHeight = height;
       LOGI("width=%d, height=%d", width, height);
       if( FETCH_WCB( *window, Reshape ) )
        INVOKE_WCB( *window, Reshape, ( width, height ) );
@@ -384,11 +433,40 @@ void fgPlatformProcessSingleEvent ( void )
       source->process(source->app, source);
     }
   }
+
+  /* If we're not in RESUME state, Android paused us, so wait */
+  struct android_app* app = fgDisplay.pDisplay.app;
+  if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
+    int FOREVER = -1;
+    while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
+      if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
+        /* Process this event. */
+        if (source != NULL) {
+          source->process(source->app, source);
+        }
+      }
+    }
+    /* If coming back from a pause: */
+    /* - Recreate window context and surface */
+    /* - Call user-defined hook to restore resources (textures...) */
+    /* - Exit pause looop */
+    if (app->destroyRequested != 1) {
+      /* Android is full-screen only, simplified call.. */
+      /* Ideally we'd have a fgPlatformReopenWindow() */
+      /* If we're hidden by a non-fullscreen or translucent activity,
+         we'll be paused but not stopped, and keep the current
+         surface; in which case fgPlatformOpenWindow will no-op. */
+      fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
+      /* TODO: INVOKE_WCB(*window, Pause?); */
+      /* TODO: INVOKE_WCB(*window, LoadResources/ContextLost/...?); */
+      /* TODO: INVOKE_WCB(*window, Resume?); */
+    }
+  }
 }
 
 void fgPlatformMainLoopPreliminaryWork ( void )
 {
-  printf("fgPlatformMainLoopPreliminaryWork\n");
+  LOGI("fgPlatformMainLoopPreliminaryWork\n");
 
   key_init();
 
@@ -396,8 +474,3 @@ void fgPlatformMainLoopPreliminaryWork ( void )
   /* JNI entry points need to be bundled even when linking statically */
   app_dummy();
 }
-
-void fgPlatformDeinitialiseInputDevices ( void )
-{
-  fprintf(stderr, "fgPlatformDeinitialiseInputDevices: STUB\n");
-}