(*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
  *)

unit android_native_app_glue;

interface

uses ctypes,baseunix,unixtype,
     configuration,looper,log,input,rect,native_window,native_activity;

(**
 * The native activity interface provided by <android/native_activity.h>
 * is based on a set of application-provided callbacks that will be called
 * by the Activity's main thread when certain events occur.
 *
 * This means that each one of this callbacks _should_ _not_ block, or they
 * risk having the system force-close the application. This programming
 * model is direct, lightweight, but constraining.
 *
 * The 'threaded_native_app' static library is used to provide a different
 * execution model where the application can implement its own main event
 * loop in a different thread instead. Here's how it works:
 *
 * 1/ The application must provide a function named "android_main()" that
 *    will be called when the activity is created, in a new thread that is
 *    distinct from the activity's main thread.
 *
 * 2/ android_main() receives a pointer to a valid "android_app" structure
 *    that contains references to other important objects, e.g. the
 *    ANativeActivity obejct instance the application is running in.
 *
 * 3/ the "android_app" object holds an ALooper instance that already
 *    listens to two important things:
 *
 *      - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX
 *        declarations below.
 *
 *      - input events coming from the AInputQueue attached to the activity.
 *
 *    Each of these correspond to an ALooper identifier returned by
 *    ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT,
 *    respectively.
 *
 *    Your application can use the same ALooper to listen to additional
 *    file-descriptors.  They can either be callback based, or with return
 *    identifiers starting with LOOPER_ID_USER.
 *
 * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event,
 *    the returned data will point to an android_poll_source structure.  You
 *    can call the process() function on it, and fill in android_app->onAppCmd
 *    and android_app->onInputEvent to be called for your own processing
 *    of the event.
 *
 *    Alternatively, you can call the low-level functions to read and process
 *    the data directly...  look at the process_cmd() and process_input()
 *    implementations in the glue to see how to do this.
 *
 * See the sample named "native-activity" that comes with the NDK with a
 * full usage example.  Also look at the JavaDoc of NativeActivity.
  *)

(**
 * Data associated with an ALooper fd that will be returned as the "outData"
 * when that source has data ready.
  *)

type
  Pandroid_poll_source = ^android_poll_source;
  Pandroid_app = ^Tandroid_app;
  android_poll_source = packed record
    // The identifier of this source.  May be LOOPER_ID_MAIN or
    // LOOPER_ID_INPUT.
    id : cint32;
    // The android_app this ident is associated with.
    app : Pandroid_app;
    // Function to call to perform the standard processing of data from
    // this source.
    process : procedure(app: Pandroid_app; source: Pandroid_poll_source); cdecl;
  end;

(**
 * This is the interface for the standard glue code of a threaded
 * application.  In this model, the application's code is running
 * in its own thread separate from the main thread of the process.
 * It is not required that this thread be associated with the Java
 * VM, although it will need to be in order to make JNI calls any
 * Java objects.
  *)
  Tandroid_app = packed record
    // The application can place a pointer to its own state object
    // here if it likes.
    userData : Pointer;
    // Fill this in with the function to process main app commands (APP_CMD_*)
    onAppCmd : procedure(app: Pandroid_app; cmd: cint32); cdecl;
    // Fill this in with the function to process input events.  At this point
    // the event has already been pre-dispatched, and it will be finished upon
    // return. Return if you have handled the event, 0 for any default
    // dispatching.
    onInputEvent : function(app: Pandroid_app; event: PAInputEvent): cint32; cdecl;
    // The ANativeActivity object instance that this app is running in.
    activity : PANativeActivity;
    // The current configuration the app is running in.
    config : PAConfiguration;
    // This is the last instance's saved state, as provided at creation time.
    // It is NULL if there was no state.  You can use this as you need; the
    // memory will remain around until you call android_app_exec_cmd() for
    // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL.
    // These variables should only be changed when processing a APP_CMD_SAVE_STATE,
    // at which point they will be initialized to NULL and you can malloc your
    // state and place the information here.  In that case the memory will be
    // freed for you later.
    savedState : Pointer;
    savedStateSize : csize_t;
    // The ALooper associated with the app's thread.
    looper : PALooper;
    // When non-NULL, this is the input queue from which the app will
    // receive user input events.
    inputQueue : PAInputQueue;
    // When non-NULL, this is the window surface that the app can draw in.
    window : PANativeWindow;
    // Current content rectangle of the window; this is the area where the
    // window's content should be placed to be seen by the user.
    contentRect : ARect;
    // Current state of the app's activity.  May be either APP_CMD_START,
    // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below.
    activityState : cint;
    // This is non-zero when the application's NativeActivity is being
    // destroyed and waiting for the app thread to complete.
    destroyRequested : cint;
    // -------------------------------------------------
    // Below are "private" implementation of the glue code.
    mutex : pthread_mutex_t;
    cond : pthread_cond_t;
    msgread : cint;
    msgwrite : cint;
    thread : pthread_t;
    cmdPollSource : android_poll_source;
    inputPollSource : android_poll_source;
    running : cint;
    stateSaved : cint;
    destroyed : cint;
    redrawNeeded : cint;
    pendingInputQueue : PAInputQueue;
    pendingWindow : PANativeWindow;
    pendingContentRect : ARect;
  end;

const
  (**
     * Looper data ID of commands coming from the app's main thread, which
     * is returned as an identifier from ALooper_pollOnce().  The data for this
     * identifier is a pointer to an android_poll_source structure.
     * These can be retrieved and processed with android_app_read_cmd()
     * and android_app_exec_cmd().
      *)
  LOOPER_ID_MAIN = 1;
  (**
     * Looper data ID of events coming from the AInputQueue of the
     * application's window, which is returned as an identifier from
     * ALooper_pollOnce().  The data for this identifier is a pointer to an
     * android_poll_source structure.  These can be read via the inputQueue
     * object of android_app.
      *)
  LOOPER_ID_INPUT = 2;
  (**
     * Start of user-defined ALooper identifiers.
      *)
  LOOPER_ID_USER = 3;

const
  (**
     * Command from main thread: the AInputQueue has changed.  Upon processing
     * this command, android_app->inputQueue will be updated to the new queue
     * (or NULL).
      *)
  APP_CMD_INPUT_CHANGED = 0;
  (**
     * Command from main thread: a new ANativeWindow is ready for use.  Upon
     * receiving this command, android_app->window will contain the new window
     * surface.
      *)
  APP_CMD_INIT_WINDOW = 1;
  (**
     * Command from main thread: the existing ANativeWindow needs to be
     * terminated.  Upon receiving this command, android_app->window still
     * contains the existing window; after calling android_app_exec_cmd
     * it will be set to NULL.
      *)
  APP_CMD_TERM_WINDOW = 2;
  (**
     * Command from main thread: the current ANativeWindow has been resized.
     * Please redraw with its new size.
      *)
  APP_CMD_WINDOW_RESIZED = 3;
  (**
     * Command from main thread: the system needs that the current ANativeWindow
     * be redrawn.  You should redraw the window before handing this to
     * android_app_exec_cmd() in order to avoid transient drawing glitches.
      *)
  APP_CMD_WINDOW_REDRAW_NEEDED = 4;
  (**
     * Command from main thread: the content area of the window has changed,
     * such as from the soft input window being shown or hidden.  You can
     * find the new content rect in android_app::contentRect.
      *)
  APP_CMD_CONTENT_RECT_CHANGED = 5;
  (**
     * Command from main thread: the app's activity window has gained
     * input focus.
      *)
  APP_CMD_GAINED_FOCUS = 6;
  (**
     * Command from main thread: the app's activity window has lost
     * input focus.
      *)
  APP_CMD_LOST_FOCUS = 7;
  (**
     * Command from main thread: the current device configuration has changed.
      *)
  APP_CMD_CONFIG_CHANGED = 8;
  (**
     * Command from main thread: the system is running low on memory.
     * Try to reduce your memory use.
      *)
  APP_CMD_LOW_MEMORY = 9;
  (**
     * Command from main thread: the app's activity has been started.
      *)
  APP_CMD_START = 10;
  (**
     * Command from main thread: the app's activity has been resumed.
      *)
  APP_CMD_RESUME = 11;
  (**
     * Command from main thread: the app should generate a new saved state
     * for itself, to restore from later if needed.  If you have saved state,
     * allocate it with malloc and place it in android_app.savedState with
     * the size in android_app.savedStateSize.  The will be freed for you
     * later.
      *)
  APP_CMD_SAVE_STATE = 12;
  (**
     * Command from main thread: the app's activity has been paused.
      *)
  APP_CMD_PAUSE = 13;
  (**
     * Command from main thread: the app's activity has been stopped.
      *)
  APP_CMD_STOP = 14;
  (**
     * Command from main thread: the app's activity is being destroyed,
     * and waiting for the app thread to clean up and exit before proceeding.
      *)
  APP_CMD_DESTROY = 15;

(**
 * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
 * app command message.
  *)

function android_app_read_cmd(android_app: Pandroid_app): cint8; cdecl;

(**
 * Call with the command returned by android_app_read_cmd() to do the
 * initial pre-processing of the given command.  You can perform your own
 * actions for the command after calling this function.
  *)

procedure android_app_pre_exec_cmd(android_app: Pandroid_app; cmd: cint8); cdecl;

(**
 * Call with the command returned by android_app_read_cmd() to do the
 * final post-processing of the given command.  You must have done your own
 * actions for the command before calling this function.
  *)

procedure android_app_post_exec_cmd(android_app: Pandroid_app; cmd: cint8); cdecl;

(**
 * Dummy function you can call to ensure glue code isn't stripped.
  *)

procedure app_dummy;

(**
 * This is the function that application code must implement, representing
 * the main entry to the app.
  *)
procedure android_main(app: Pandroid_app); cdecl; external;
procedure ANativeActivity_onCreate(activity: PANativeActivity; savedState: Pointer; savedStateSize: csize_t); cdecl;

implementation

uses cmem;

function strerror(i: longint): pchar; cdecl;
begin
   result := 'Undefined!';
end;

type
 ppthread_t = ^pthread_t;
 ppthread_attr_t = ^pthread_attr_t;
 ppthread_mutex_t = ^pthread_mutex_t;
 ppthread_cond_t = ^pthread_cond_t;
 ppthread_mutexattr_t = ^pthread_mutexattr_t;
 ppthread_condattr_t = ^pthread_condattr_t;

 __start_routine_t = pointer;

const
 PTHREAD_CREATE_DETACHED = 1;

function pthread_create(__thread:ppthread_t; __attr:ppthread_attr_t;__start_routine: __start_routine_t;__arg:pointer):longint;cdecl;external 'libc.so';
function pthread_attr_init(__attr:ppthread_attr_t):longint;cdecl;external 'libc.so';
function pthread_attr_setdetachstate(__attr:ppthread_attr_t; __detachstate:longint):longint;cdecl;external 'libc.so';
function pthread_mutex_init(__mutex:ppthread_mutex_t; __mutex_attr:ppthread_mutexattr_t):longint;cdecl;external 'libc.so';
function pthread_mutex_destroy(__mutex:ppthread_mutex_t):longint;cdecl;external 'libc.so';
function pthread_mutex_lock(__mutex: ppthread_mutex_t):longint;cdecl;external 'libc.so';
function pthread_mutex_unlock(__mutex: ppthread_mutex_t):longint;cdecl;external 'libc.so';
function pthread_cond_init(__cond:ppthread_cond_t; __cond_attr:ppthread_condattr_t):longint;cdecl;external 'libc.so';
function pthread_cond_destroy(__cond:ppthread_cond_t):longint;cdecl;external 'libc.so';
function pthread_cond_signal(__cond:ppthread_cond_t):longint;cdecl;external 'libc.so';
function pthread_cond_broadcast(__cond:ppthread_cond_t):longint;cdecl;external 'libc.so';
function pthread_cond_wait(__cond:ppthread_cond_t; __mutex:ppthread_mutex_t):longint;cdecl;external 'libc.so';


procedure free_saved_state(android_app: Pandroid_app);
begin
   pthread_mutex_lock(@android_app^.mutex);
   if android_app^.savedState <> Nil then
   begin
      free(android_app^.savedState);
      android_app^.savedState := nil;
      android_app^.savedStateSize := 0;
   end;
   pthread_mutex_unlock(@android_app^.mutex);
end;

function android_app_read_cmd(android_app: Pandroid_app): cint8; cdecl;
var cmd: cint8;
begin
    result := -1;
    if fpread(android_app^.msgread, @cmd, sizeof(cmd)) = sizeof(cmd) then
    begin
        case cmd of
           APP_CMD_SAVE_STATE:
               free_saved_state(android_app);
        end;
        result := cmd;
    end
    else
        LOGI(ANDROID_LOG_FATAL,'Crap','No data on command pipe!');
end;

procedure print_cur_config(android_app: Pandroid_app);
var lang, country: array[0..1] of char;
begin
    AConfiguration_getLanguage(android_app^.config, @lang[0]);
    AConfiguration_getCountry(android_app^.config, @country[0]);

    LOGI(ANDROID_LOG_FATAL,'Crap','Config: mcc:=%d mnc:=%d lang:=%c%c cnt:=%c%c orien:=%d touch:=%d dens:=%d '+
            'keys:=%d nav:=%d keysHid:=%d navHid:=%d sdk:=%d size:=%d long:=%d '+
            'modetype:=%d modenight:=%d',
            AConfiguration_getMcc(android_app^.config),
            AConfiguration_getMnc(android_app^.config),
            lang[0], lang[1], country[0], country[1],
            AConfiguration_getOrientation(android_app^.config),
            AConfiguration_getTouchscreen(android_app^.config),
            AConfiguration_getDensity(android_app^.config),
            AConfiguration_getKeyboard(android_app^.config),
            AConfiguration_getNavigation(android_app^.config),
            AConfiguration_getKeysHidden(android_app^.config),
            AConfiguration_getNavHidden(android_app^.config),
            AConfiguration_getSdkVersion(android_app^.config),
            AConfiguration_getScreenSize(android_app^.config),
            AConfiguration_getScreenLong(android_app^.config),
            AConfiguration_getUiModeType(android_app^.config),
            AConfiguration_getUiModeNight(android_app^.config));
end;

procedure android_app_pre_exec_cmd(android_app: Pandroid_app; cmd: cint8); cdecl;
begin
    case cmd of
        APP_CMD_INPUT_CHANGED:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_INPUT_CHANGED');
               pthread_mutex_lock(@android_app^.mutex);
               if android_app^.inputQueue <> nil then
                   AInputQueue_detachLooper(android_app^.inputQueue);
               android_app^.inputQueue := android_app^.pendingInputQueue;
               if android_app^.inputQueue <> nil then
               begin
                   LOGI(ANDROID_LOG_FATAL,'Crap','Attaching input queue to looper');
                   AInputQueue_attachLooper(android_app^.inputQueue,
                           android_app^.looper, LOOPER_ID_INPUT, nil,
                           @android_app^.inputPollSource);
               end;
               pthread_cond_broadcast(@android_app^.cond);
               pthread_mutex_unlock(@android_app^.mutex);
            end;

        APP_CMD_INIT_WINDOW:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_INIT_WINDOW');
               pthread_mutex_lock(@android_app^.mutex);
               android_app^.window := android_app^.pendingWindow;
               pthread_cond_broadcast(@android_app^.cond);
               pthread_mutex_unlock(@android_app^.mutex);
            end;

        APP_CMD_TERM_WINDOW:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_TERM_WINDOW');
               pthread_mutex_lock(@android_app^.mutex);
               android_app^.window := nil;
               pthread_cond_broadcast(@android_app^.cond);
               pthread_mutex_unlock(@android_app^.mutex);
            end;

        APP_CMD_RESUME,
        APP_CMD_START,
        APP_CMD_PAUSE,
        APP_CMD_STOP:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','activityState:=%d', cmd);
               pthread_mutex_lock(@android_app^.mutex);
               android_app^.activityState := cmd;
               pthread_cond_broadcast(@android_app^.cond);
               pthread_mutex_unlock(@android_app^.mutex);
            end;

        APP_CMD_CONFIG_CHANGED:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_CONFIG_CHANGED');
               AConfiguration_fromAssetManager(android_app^.config,
                       android_app^.activity^.assetManager);
               print_cur_config(android_app);
            end;

        APP_CMD_DESTROY:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_DESTROY');
               android_app^.destroyRequested := 1;
            end;
    end;
end;

procedure android_app_post_exec_cmd(android_app: Pandroid_app; cmd: cint8); cdecl;
begin
    case cmd of
        APP_CMD_TERM_WINDOW:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_TERM_WINDOW');
               pthread_mutex_lock(@android_app^.mutex);
               android_app^.window := nil;
               pthread_cond_broadcast(@android_app^.cond);
               pthread_mutex_unlock(@android_app^.mutex);
            end;

        APP_CMD_SAVE_STATE:
            begin
               LOGI(ANDROID_LOG_FATAL,'Crap','APP_CMD_SAVE_STATE');
               pthread_mutex_lock(@android_app^.mutex);
               android_app^.stateSaved := 1;
               pthread_cond_broadcast(@android_app^.cond);
               pthread_mutex_unlock(@android_app^.mutex);
            end;

        APP_CMD_RESUME:
            free_saved_state(android_app);
    end;
end;

procedure app_dummy;
begin

end;

procedure android_app_destroy(android_app: Pandroid_app);
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','android_app_destroy!');
    free_saved_state(android_app);
    pthread_mutex_lock(@android_app^.mutex);
    if (android_app^.inputQueue <> nil) then
        AInputQueue_detachLooper(android_app^.inputQueue);
    AConfiguration_delete(android_app^.config);
    android_app^.destroyed := 1;
    pthread_cond_broadcast(@android_app^.cond);
    pthread_mutex_unlock(@android_app^.mutex);
    // Can't touch android_app object after this.
end;

procedure process_input(app: Pandroid_app; source: Pandroid_poll_source); cdecl;
var event: PAInputEvent;
    handled: cint32;
begin
    event := nil;
    if (AInputQueue_getEvent(app^.inputQueue, @event) >= 0) then
    begin
        LOGI(ANDROID_LOG_FATAL,'Crap','New input event: type:=%d',AInputEvent_getType(event));
        if AInputQueue_preDispatchEvent(app^.inputQueue, event) <> 0 then exit;
        handled := 0;
        if (app^.onInputEvent <> nil) then handled := app^.onInputEvent(app, event);
        AInputQueue_finishEvent(app^.inputQueue, event, handled);
    end
    else
        LOGI(ANDROID_LOG_FATAL,'Crap','Failure reading next input event: %s', strerror(errno));
end;

procedure process_cmd(app: Pandroid_app; source: Pandroid_poll_source); cdecl;
var cmd: cint8;
begin
    cmd := android_app_read_cmd(app);
    android_app_pre_exec_cmd(app, cmd);
    if (app^.onAppCmd <> nil) then app^.onAppCmd(app, cmd);
    android_app_post_exec_cmd(app, cmd);
end;

function android_app_entry(param: pointer): Pointer; cdecl;
var android_app: Pandroid_app;
    looper: PALooper;
begin
    android_app := Pandroid_app(param);

    android_app^.config := AConfiguration_new();
    AConfiguration_fromAssetManager(android_app^.config, android_app^.activity^.assetManager);

    print_cur_config(android_app);

    android_app^.cmdPollSource.id := LOOPER_ID_MAIN;
    android_app^.cmdPollSource.app := android_app;
    android_app^.cmdPollSource.process := @process_cmd;
    android_app^.inputPollSource.id := LOOPER_ID_INPUT;
    android_app^.inputPollSource.app := android_app;
    android_app^.inputPollSource.process := @process_input;

    looper := ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
    ALooper_addFd(looper, android_app^.msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, nil,
            @android_app^.cmdPollSource);
    android_app^.looper := looper;

    pthread_mutex_lock(@android_app^.mutex);
    android_app^.running := 1;
    pthread_cond_broadcast(@android_app^.cond);
    pthread_mutex_unlock(@android_app^.mutex);

    android_main(android_app);

    android_app_destroy(android_app);
    result := nil;
end;

// --------------------------------------------------------------------
// Native activity interaction (called from main thread)
// --------------------------------------------------------------------

function android_app_create(activity: PANativeActivity; savedState: Pointer; savedStateSize: csize_t): Pandroid_app;
var android_app: Pandroid_app;
    msgpipe: array[0..1] of cint;
    attr: pthread_attr_t;
begin
    android_app := Pandroid_app(malloc(sizeof(Tandroid_app)));
    fillchar(android_app^, sizeof(tandroid_app), 0);
    android_app^.activity := activity;

    pthread_mutex_init(@android_app^.mutex, nil);
    pthread_cond_init(@android_app^.cond, nil);

    if (savedState <> nil) then
    begin
        android_app^.savedState := malloc(savedStateSize);
        android_app^.savedStateSize := savedStateSize;
        move(pbyte(savedState)^, pbyte(android_app^.savedState)^, savedStateSize);
    end;

    if FpPipe(msgpipe) <> 0 then
        LOGI(ANDROID_LOG_FATAL,'Crap','could not create pipe: %s', strerror(errno));

    android_app^.msgread := msgpipe[0];
    android_app^.msgwrite := msgpipe[1];

    pthread_attr_init(@attr);
    pthread_attr_setdetachstate(@attr, PTHREAD_CREATE_DETACHED);
    pthread_create(@android_app^.thread, @attr, @android_app_entry, android_app);

    // Wait for thread to start.
    pthread_mutex_lock(@android_app^.mutex);
    while android_app^.running = 0 do
        pthread_cond_wait(@android_app^.cond, @android_app^.mutex);

    pthread_mutex_unlock(@android_app^.mutex);

    result := android_app;
end;

procedure android_app_write_cmd(android_app: Pandroid_app; cmd: cint8);
begin
    if fpwrite(android_app^.msgwrite, cmd, sizeof(cmd)) <> sizeof(cmd) then
        LOGI(ANDROID_LOG_FATAL,'Crap','Failure writing android_app cmd: %s', strerror(errno));
end;

procedure android_app_set_input(android_app: Pandroid_app; inputQueue: PAInputQueue);
begin
    pthread_mutex_lock(@android_app^.mutex);
    android_app^.pendingInputQueue := inputQueue;
    android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED);
    while (android_app^.inputQueue <> android_app^.pendingInputQueue) do
        pthread_cond_wait(@android_app^.cond, @android_app^.mutex);
    pthread_mutex_unlock(@android_app^.mutex);
end;

procedure android_app_set_window(android_app: Pandroid_app; window: PANativeWindow);
begin
    pthread_mutex_lock(@android_app^.mutex);
    if (android_app^.pendingWindow <> nil) then
        android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);

    android_app^.pendingWindow := window;
    if (window <> nil) then
        android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);

    while (android_app^.window <> android_app^.pendingWindow) do
        pthread_cond_wait(@android_app^.cond, @android_app^.mutex);

    pthread_mutex_unlock(@android_app^.mutex);
end;

procedure android_app_set_activity_state(android_app: Pandroid_app; cmd: cint8);
begin
    logw(' Setting activity state!');
    pthread_mutex_lock(@android_app^.mutex);
    android_app_write_cmd(android_app, cmd);
    while (android_app^.activityState <> cmd) do
        pthread_cond_wait(@android_app^.cond, @android_app^.mutex);

    pthread_mutex_unlock(@android_app^.mutex);
end;

procedure android_app_free(android_app: Pandroid_app);
begin
    pthread_mutex_lock(@android_app^.mutex);
    android_app_write_cmd(android_app, APP_CMD_DESTROY);
    while android_app^.destroyed = 0 do
        pthread_cond_wait(@android_app^.cond, @android_app^.mutex);

    pthread_mutex_unlock(@android_app^.mutex);

    fpclose(android_app^.msgread);
    fpclose(android_app^.msgwrite);
    pthread_cond_destroy(@android_app^.cond);
    pthread_mutex_destroy(@android_app^.mutex);
    free(android_app);
end;

procedure onDestroy(activity: PANativeActivity); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','Destroy: %p', activity);
    android_app_free(Pandroid_app(activity^.instance));
end;

procedure onStart(activity: PANativeActivity); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','Start: %p', activity);
    android_app_set_activity_state(Pandroid_app(activity^.instance), APP_CMD_START);
end;

procedure onResume(activity: PANativeActivity); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','Resume: %p', activity);
    android_app_set_activity_state(Pandroid_app(activity^.instance), APP_CMD_RESUME);
end;

function onSaveInstanceState(activity: PANativeActivity; outLen: pcsize_t): Pointer; cdecl;
var android_app: Pandroid_app;
    savedState: pointer;
begin
    android_app := activity^.instance;
    savedState := nil;

    LOGI(ANDROID_LOG_FATAL,'Crap','SaveInstanceState: %p', activity);
    pthread_mutex_lock(@android_app^.mutex);
    android_app^.stateSaved := 0;
    android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
    while android_app^.stateSaved = 0 do
        pthread_cond_wait(@android_app^.cond, @android_app^.mutex);

    if android_app^.savedState <> nil then
    begin
        savedState := android_app^.savedState;
        outLen^ := android_app^.savedStateSize;
        android_app^.savedState := nil;
        android_app^.savedStateSize := 0;
    end;

    pthread_mutex_unlock(@android_app^.mutex);

    result := savedState;
end;

procedure onPause(activity: PANativeActivity); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','Pause: %p', activity);
    android_app_set_activity_state(Pandroid_app(activity^.instance), APP_CMD_PAUSE);
end;

procedure onStop(activity: PANativeActivity); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','Stop: %p', activity);
    android_app_set_activity_state(Pandroid_app(activity^.instance), APP_CMD_STOP);
end;

procedure onConfigurationChanged(activity: PANativeActivity); cdecl;
var android_app: Pandroid_app;
begin
    android_app := activity^.instance;
    LOGI(ANDROID_LOG_FATAL,'Crap','ConfigurationChanged: %p', activity);
    android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED);
end;

procedure onLowMemory(activity: PANativeActivity); cdecl;
var android_app: Pandroid_app;
begin
    android_app := activity^.instance;
    LOGI(ANDROID_LOG_FATAL,'Crap','LowMemory: %p', activity);
    android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY);
end;

procedure onWindowFocusChanged(activity: PANativeActivity; focused: cint); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','WindowFocusChanged: %p -- %d', activity, focused);

    if focused <> 0 then
       android_app_write_cmd(activity^.instance, APP_CMD_GAINED_FOCUS)
    else
       android_app_write_cmd(activity^.instance, APP_CMD_LOST_FOCUS);
end;

procedure onNativeWindowCreated(activity: PANativeActivity; window: PANativeWindow); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','NativeWindowCreated: %p -- %p', activity, window);
    android_app_set_window(activity^.instance, window);
end;

procedure onNativeWindowDestroyed(activity: PANativeActivity; window: PANativeWindow); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','NativeWindowDestroyed: %p -- %p', activity, window);
    android_app_set_window(activity^.instance, nil);
end;

procedure onInputQueueCreated(activity: PANativeActivity; queue: PAInputQueue); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','InputQueueCreated: %p -- %p', activity, queue);
    android_app_set_input(activity^.instance, queue);
end;

procedure onInputQueueDestroyed(activity: PANativeActivity; queue: PAInputQueue); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','InputQueueDestroyed: %p -- %p', activity, queue);
    android_app_set_input(activity^.instance, nil);
end;

procedure ANativeActivity_onCreate(activity: PANativeActivity; savedState: Pointer; savedStateSize: csize_t); cdecl;
begin
    LOGI(ANDROID_LOG_FATAL,'Crap','Creating: %p', activity);
    activity^.callbacks^.onDestroy := @onDestroy;
    activity^.callbacks^.onStart := @onStart;
    activity^.callbacks^.onResume := @onResume;
    activity^.callbacks^.onSaveInstanceState := @onSaveInstanceState;
    activity^.callbacks^.onPause := @onPause;
    activity^.callbacks^.onStop := @onStop;
    activity^.callbacks^.onConfigurationChanged := @onConfigurationChanged;
    activity^.callbacks^.onLowMemory := @onLowMemory;
    activity^.callbacks^.onWindowFocusChanged := @onWindowFocusChanged;
    activity^.callbacks^.onNativeWindowCreated := @onNativeWindowCreated;
    activity^.callbacks^.onNativeWindowDestroyed := @onNativeWindowDestroyed;
    activity^.callbacks^.onInputQueueCreated := @onInputQueueCreated;
    activity^.callbacks^.onInputQueueDestroyed := @onInputQueueDestroyed;

    activity^.instance := android_app_create(activity, savedState, savedStateSize);
end;

end.

