# HG changeset patch # User John Tsiombikas # Date 1443841830 -10800 # Node ID 7d795dade0bced99de5810df49955a2beadb774d # Parent c6971ff4795eddece52bebee7b8b088b9fa46ea1 first pass at android port... compiles! diff -r c6971ff4795e -r 7d795dade0bc .hgignore --- a/.hgignore Fri Oct 02 07:10:19 2015 +0300 +++ b/.hgignore Sat Oct 03 06:10:30 2015 +0300 @@ -11,3 +11,11 @@ ^data/ \.DS_Store ^test$ +^android/bin/ +^android/libs/ +^android/res/ +^android/src/ +^android/assets/ +\.xml$ +\.properties$ +proguard-project\.txt$ diff -r c6971ff4795e -r 7d795dade0bc android/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/Makefile Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,106 @@ +bin = stereotunnel +root = .. +src = $(wildcard $(root)/src/*.c) +ccsrc = $(wildcard $(root)/src/*.cc) + +include $(root)/libs/Makefile + +# --- android settings --- +android_platform = android-19 +name = $(bin) +pkgprefix = com.mutantstargoat +# ------------------------ + +src += $(wildcard $(root)/src/android/*.c) + +obj = $(src:.c=.o) $(ccsrc:.cc=.o) +lib = libs/armeabi/lib$(name).so +apk-release = bin/$(name).apk +apk-debug = bin/$(name)-debug.apk + +pkg = $(pkgprefix).$(name) +act = android.app.NativeActivity + +CC = arm-linux-androideabi-gcc +CXX = arm-linux-androideabi-g++ + +android_libs = -lstdc++ -llog -landroid -lEGL -lGLESv2 + +defs += -DANDROID -DAPP_NAME=\"$(name)\" -DNO_FREETYPE -DHAVE_OPENGL_H +CXXFLAGS = -Wall -g $(defs) -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 \ + $(android_inc) -I$(root)/src -I$(root)/src/android $(incdir) +CFLAGS = -std=c99 $(CXXFLAGS) +LDFLAGS = -Wl,--fix-cortex-a8 -Wl,-z,defs $(android_libs) $(libpaths) -lm + +.PHONY: debug +debug: $(apk-debug) + +.PHONY: release +release: $(apk-release) + +$(apk-debug): $(lib) $(jsrc) AndroidManifest.xml assets/data assets/sdr + ant debug + +$(apk-release): $(lib) $(jsrc) AndroidManifest.xml assets/data assets/sdr + ant release + +assets/data: + mkdir -p assets + cd assets && rm -f data && ln -s ../../data . + +assets/sdr: + mkdir -p assets + cd assets && rm -f sdr && ln -s ../../sdr . + +.PHONY: lib +lib: $(lib) + +$(lib): $(obj) + @mkdir -p libs/armeabi + $(CXX) -o $@ -shared $(obj) $(LDFLAGS) + +ant_files = *.xml *.properties proguard-project.txt +ant_dirs = bin libs res obj src gen + +.PHONY: clean +clean: + rm -f $(obj) $(lib) $(apk-release) $(apk-debug) $(ant_files) + rm -rf $(ant_dirs) + rm -f assets/data assets/sdr + [ -d assets ] && rmdir assets || true + +.PHONY: install +install: install-debug + +.PHONY: install-debug +install-debug: + adb install -r $(apk-debug) + +.PHONY: install-release +install-release: + adb install -r $(apk-release) + +.PHONY: uninstall +uninstall: + adb uninstall $(pkg) + +.PHONY: run +run: + adb shell am start -n $(pkg)/$(act) + +.PHONY: stop +stop: + adb shell am force-stop $(pkg) + +AndroidManifest.xml: manifest.xml.in + android create project -p . -t $(android_platform) -k $(pkg) -a NativeActivity -n $(name) + cat manifest.xml.in | sed 's/$$APPNAME/$(name)/g' | sed 's/$$APPTITLE/$(title)/g' >$@ +# cd src && rm -f *.java && ln -s ../../src/android/*.java . + +.PHONY: update-project +update-project: build.xml + android update project -p . -t $(android_platform) + +.PHONY: logcat +logcat: + adb logcat $(name):V AndroidRuntime:V DEBUG:V '*:S' diff -r c6971ff4795e -r 7d795dade0bc android/manifest.xml.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/manifest.xml.in Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff -r c6971ff4795e -r 7d795dade0bc libs/Makefile --- a/libs/Makefile Fri Oct 02 07:10:19 2015 +0300 +++ b/libs/Makefile Sat Oct 03 06:10:30 2015 +0300 @@ -1,11 +1,12 @@ -src += $(wildcard libs/zlib/*.c) \ - $(wildcard libs/libpng/*.c) \ - $(wildcard libs/libjpeg/*.c) \ - $(wildcard libs/imago2/*.c) \ - $(wildcard libs/vmath/*.c) \ - $(wildcard libs/drawtext/*.c) +src += $(wildcard $(root)/libs/zlib/*.c) \ + $(wildcard $(root)/libs/libpng/*.c) \ + $(wildcard $(root)/libs/libjpeg/*.c) \ + $(wildcard $(root)/libs/imago2/*.c) \ + $(wildcard $(root)/libs/vmath/*.c) \ + $(wildcard $(root)/libs/drawtext/*.c) -ccsrc += $(wildcard libs/goatkit/*.cc) +ccsrc += $(wildcard $(root)/libs/goatkit/*.cc) -incdir += -Ilibs -Ilibs/imago2 -Ilibs/zlib -Ilibs/libpng -Ilibs/libjpeg \ - -Ilibs/vmath -Ilibs/drawtext +incdir += -I$(root)/libs -I$(root)/libs/imago2 -I$(root)/libs/zlib \ + -I$(root)/libs/libpng -I$(root)/libs/libjpeg \ + -I$(root)/libs/vmath -I$(root)/libs/drawtext diff -r c6971ff4795e -r 7d795dade0bc src/android/amain.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/android/amain.c Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,275 @@ +#include +#include +#include +#include +#include +#include "android_native_app_glue.h" +#include +#include "logger.h" +#include "istereo.h" + +struct android_app *app; + +static void handle_command(struct android_app *app, int32_t cmd); +static int handle_input(struct android_app *app, AInputEvent *ev); +static int handle_touch_input(struct android_app *app, AInputEvent *ev); +static int init_gl(void); +static void destroy_gl(void); + +static EGLDisplay dpy; +static EGLSurface surf; +static EGLContext ctx; + +static int redisp_pending = 1; /* TODO stop busy-looping */ + +static int width, height; +static int init_done; + +void android_main(struct android_app *app_ptr) +{ + app_dummy(); + app = app_ptr; + + app->onAppCmd = handle_command; + app->onInputEvent = handle_input; + + start_logger(); + + for(;;) { + int num_events; + struct android_poll_source *pollsrc; + + while(ALooper_pollAll(0, 0, &num_events, (void**)&pollsrc) >= 0) { + if(pollsrc) { + pollsrc->process(app, pollsrc); + } + } + + if(app->destroyRequested) { + return; + } + + if(init_done && redisp_pending) { + redraw(); + eglSwapBuffers(dpy, surf); + } + } +} + +void set_mouse_pos(int x, int y) +{ +} + +void set_mouse_cursor(int enable) +{ +} + +/* TODO */ +void ad_banner_show(void) +{ +} + +void ad_banner_hide(void) +{ +} + +static void handle_command(struct android_app *app, int32_t cmd) +{ + switch(cmd) { + case APP_CMD_SAVE_STATE: + /* save the application state to be reloaded on restart if needed */ + break; + + case APP_CMD_INIT_WINDOW: + if(init_gl() == -1) { + exit(1); + } + /* initialize the application */ + if(init() == -1) { + exit(1); /* initialization failed, quit */ + } + init_done = 1; + break; + + case APP_CMD_TERM_WINDOW: + /* cleanup */ + init_done = 0; + cleanup(); + destroy_gl(); + break; + + case APP_CMD_GAINED_FOCUS: + /* app focused */ + break; + + case APP_CMD_LOST_FOCUS: + /* app lost focus */ + break; + + case APP_CMD_WINDOW_RESIZED: + case APP_CMD_CONFIG_CHANGED: + { + int nx = ANativeWindow_getWidth(app->window); + int ny = ANativeWindow_getHeight(app->window); + if(nx != width || ny != height) { + reshape(nx, ny); + width = nx; + height = ny; + } + } + break; + + default: + break; + } +} + +static int handle_input(struct android_app *app, AInputEvent *ev) +{ + int evtype = AInputEvent_getType(ev); + + switch(evtype) { + case AINPUT_EVENT_TYPE_MOTION: + return handle_touch_input(app, ev); + + default: + break; + } + return 0; +} + +#define MAX_TOUCH_IDS 32 + +static int handle_touch_input(struct android_app *app, AInputEvent *ev) +{ + int x, y, idx, touch_id; + unsigned int action; + static int prev_pos[MAX_TOUCH_IDS][2]; + + action = AMotionEvent_getAction(ev); + + idx = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + touch_id = AMotionEvent_getPointerId(ev, idx); + + x = AMotionEvent_getX(ev, idx); + y = AMotionEvent_getY(ev, idx); + + switch(action & AMOTION_EVENT_ACTION_MASK) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + if(touch_id == 0) { + mouse_button(0, 1, x, y); + } + if(touch_id < MAX_TOUCH_IDS) { + prev_pos[touch_id][0] = x; + prev_pos[touch_id][1] = y; + } + break; + + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + if(touch_id == 0) { + mouse_button(0, 0, x, y); + } + if(touch_id < MAX_TOUCH_IDS) { + prev_pos[touch_id][0] = x; + prev_pos[touch_id][1] = y; + } + break; + + case AMOTION_EVENT_ACTION_MOVE: + { + int i, pcount = AMotionEvent_getPointerCount(ev); + for(i=0; iwindow, 0, 0, vis); + + if(!(surf = eglCreateWindowSurface(dpy, eglcfg, app->window, 0))) { + fprintf(stderr, "failed to create window\n"); + destroy_gl(); + return -1; + } + + if(!(ctx = eglCreateContext(dpy, eglcfg, EGL_NO_CONTEXT, ctxattr))) { + fprintf(stderr, "failed to create OpenGL ES context\n"); + destroy_gl(); + return -1; + } + eglMakeCurrent(dpy, surf, surf, ctx); + + eglQuerySurface(dpy, surf, EGL_WIDTH, &width); + eglQuerySurface(dpy, surf, EGL_HEIGHT, &height); + reshape(width, height); + + return 0; +} + +static void destroy_gl(void) +{ + if(!dpy) return; + + eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if(ctx) { + eglDestroyContext(dpy, ctx); + ctx = 0; + } + if(surf) { + eglDestroySurface(dpy, surf); + surf = 0; + } + + eglTerminate(dpy); + dpy = 0; +} diff -r c6971ff4795e -r 7d795dade0bc src/android/android_native_app_glue.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/android/android_native_app_glue.c Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,440 @@ +/* + * 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. + * + */ + +#include + +#include +#include +#include +#include + +#include "android_native_app_glue.h" +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) + +/* For debug builds, always enable the debug traces in this library */ +#ifndef NDEBUG +# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__)) +#else +# define LOGV(...) ((void)0) +#endif + +static void free_saved_state(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + if (android_app->savedState != NULL) { + free(android_app->savedState); + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + pthread_mutex_unlock(&android_app->mutex); +} + +int8_t android_app_read_cmd(struct android_app* android_app) { + int8_t cmd; + if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { + switch (cmd) { + case APP_CMD_SAVE_STATE: + free_saved_state(android_app); + break; + } + return cmd; + } else { + LOGE("No data on command pipe!"); + } + return -1; +} + +static void print_cur_config(struct android_app* android_app) { + char lang[2], country[2]; + AConfiguration_getLanguage(android_app->config, lang); + AConfiguration_getCountry(android_app->config, country); + + LOGV("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)); +} + +void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { + switch (cmd) { + case APP_CMD_INPUT_CHANGED: + LOGV("APP_CMD_INPUT_CHANGED\n"); + pthread_mutex_lock(&android_app->mutex); + if (android_app->inputQueue != NULL) { + AInputQueue_detachLooper(android_app->inputQueue); + } + android_app->inputQueue = android_app->pendingInputQueue; + if (android_app->inputQueue != NULL) { + LOGV("Attaching input queue to looper"); + AInputQueue_attachLooper(android_app->inputQueue, + android_app->looper, LOOPER_ID_INPUT, NULL, + &android_app->inputPollSource); + } + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_INIT_WINDOW: + LOGV("APP_CMD_INIT_WINDOW\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->window = android_app->pendingWindow; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW\n"); + pthread_cond_broadcast(&android_app->cond); + break; + + case APP_CMD_RESUME: + case APP_CMD_START: + case APP_CMD_PAUSE: + case APP_CMD_STOP: + LOGV("activityState=%d\n", cmd); + pthread_mutex_lock(&android_app->mutex); + android_app->activityState = cmd; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_CONFIG_CHANGED: + LOGV("APP_CMD_CONFIG_CHANGED\n"); + AConfiguration_fromAssetManager(android_app->config, + android_app->activity->assetManager); + print_cur_config(android_app); + break; + + case APP_CMD_DESTROY: + LOGV("APP_CMD_DESTROY\n"); + android_app->destroyRequested = 1; + break; + } +} + +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { + switch (cmd) { + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->window = NULL; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_SAVE_STATE: + LOGV("APP_CMD_SAVE_STATE\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->stateSaved = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_RESUME: + free_saved_state(android_app); + break; + } +} + +void app_dummy() { + +} + +static void android_app_destroy(struct android_app* android_app) { + LOGV("android_app_destroy!"); + free_saved_state(android_app); + pthread_mutex_lock(&android_app->mutex); + if (android_app->inputQueue != NULL) { + 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. +} + +static void process_input(struct android_app* app, struct android_poll_source* source) { + AInputEvent* event = NULL; + while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { + LOGV("New input event: type=%d\n", AInputEvent_getType(event)); + if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { + continue; + } + int32_t handled = 0; + if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); + AInputQueue_finishEvent(app->inputQueue, event, handled); + } +} + +static void process_cmd(struct android_app* app, struct android_poll_source* source) { + int8_t cmd = android_app_read_cmd(app); + android_app_pre_exec_cmd(app, cmd); + if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); + android_app_post_exec_cmd(app, cmd); +} + +static void* android_app_entry(void* param) { + struct android_app* android_app = (struct android_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; + + ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, + &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); + return NULL; +} + +// -------------------------------------------------------------------- +// Native activity interaction (called from main thread) +// -------------------------------------------------------------------- + +static struct android_app* android_app_create(ANativeActivity* activity, + void* savedState, size_t savedStateSize) { + struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); + memset(android_app, 0, sizeof(struct android_app)); + android_app->activity = activity; + + pthread_mutex_init(&android_app->mutex, NULL); + pthread_cond_init(&android_app->cond, NULL); + + if (savedState != NULL) { + android_app->savedState = malloc(savedStateSize); + android_app->savedStateSize = savedStateSize; + memcpy(android_app->savedState, savedState, savedStateSize); + } + + int msgpipe[2]; + if (pipe(msgpipe)) { + LOGE("could not create pipe: %s", strerror(errno)); + return NULL; + } + android_app->msgread = msgpipe[0]; + android_app->msgwrite = msgpipe[1]; + + pthread_attr_t attr; + 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) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); + + return android_app; +} + +static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { + if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { + LOGE("Failure writing android_app cmd: %s\n", strerror(errno)); + } +} + +static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { + 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) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { + pthread_mutex_lock(&android_app->mutex); + if (android_app->pendingWindow != NULL) { + android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); + } + android_app->pendingWindow = window; + if (window != NULL) { + android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); + } + while (android_app->window != android_app->pendingWindow) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { + pthread_mutex_lock(&android_app->mutex); + android_app_write_cmd(android_app, cmd); + while (android_app->activityState != cmd) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_free(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + android_app_write_cmd(android_app, APP_CMD_DESTROY); + while (!android_app->destroyed) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); + + close(android_app->msgread); + close(android_app->msgwrite); + pthread_cond_destroy(&android_app->cond); + pthread_mutex_destroy(&android_app->mutex); + free(android_app); +} + +static void onDestroy(ANativeActivity* activity) { + LOGV("Destroy: %p\n", activity); + android_app_free((struct android_app*)activity->instance); +} + +static void onStart(ANativeActivity* activity) { + LOGV("Start: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); +} + +static void onResume(ANativeActivity* activity) { + LOGV("Resume: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); +} + +static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { + struct android_app* android_app = (struct android_app*)activity->instance; + void* savedState = NULL; + + LOGV("SaveInstanceState: %p\n", 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) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + + if (android_app->savedState != NULL) { + savedState = android_app->savedState; + *outLen = android_app->savedStateSize; + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + + pthread_mutex_unlock(&android_app->mutex); + + return savedState; +} + +static void onPause(ANativeActivity* activity) { + LOGV("Pause: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); +} + +static void onStop(ANativeActivity* activity) { + LOGV("Stop: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); +} + +static void onConfigurationChanged(ANativeActivity* activity) { + struct android_app* android_app = (struct android_app*)activity->instance; + LOGV("ConfigurationChanged: %p\n", activity); + android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); +} + +static void onLowMemory(ANativeActivity* activity) { + struct android_app* android_app = (struct android_app*)activity->instance; + LOGV("LowMemory: %p\n", activity); + android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); +} + +static void onWindowFocusChanged(ANativeActivity* activity, int focused) { + LOGV("WindowFocusChanged: %p -- %d\n", activity, focused); + android_app_write_cmd((struct android_app*)activity->instance, + focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); +} + +static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowCreated: %p -- %p\n", activity, window); + android_app_set_window((struct android_app*)activity->instance, window); +} + +static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window); + android_app_set_window((struct android_app*)activity->instance, NULL); +} + +static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueCreated: %p -- %p\n", activity, queue); + android_app_set_input((struct android_app*)activity->instance, queue); +} + +static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue); + android_app_set_input((struct android_app*)activity->instance, NULL); +} + +void ANativeActivity_onCreate(ANativeActivity* activity, + void* savedState, size_t savedStateSize) { + LOGV("Creating: %p\n", 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); +} diff -r c6971ff4795e -r 7d795dade0bc src/android/android_native_app_glue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/android/android_native_app_glue.h Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,349 @@ +/* + * 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. + * + */ + +#ifndef _ANDROID_NATIVE_APP_GLUE_H +#define _ANDROID_NATIVE_APP_GLUE_H + +#include +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The native activity interface provided by + * 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 'android_native_app_glue' 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. + */ + +struct android_app; + +/** + * Data associated with an ALooper fd that will be returned as the "outData" + * when that source has data ready. + */ +struct android_poll_source { + // The identifier of this source. May be LOOPER_ID_MAIN or + // LOOPER_ID_INPUT. + int32_t id; + + // The android_app this ident is associated with. + struct android_app* app; + + // Function to call to perform the standard processing of data from + // this source. + void (*process)(struct android_app* app, struct android_poll_source* source); +}; + +/** + * 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. + */ +struct android_app { + // The application can place a pointer to its own state object + // here if it likes. + void* userData; + + // Fill this in with the function to process main app commands (APP_CMD_*) + void (*onAppCmd)(struct android_app* app, int32_t cmd); + + // 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 1 if you have handled the event, 0 for any default + // dispatching. + int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); + + // The ANativeActivity object instance that this app is running in. + ANativeActivity* activity; + + // The current configuration the app is running in. + AConfiguration* config; + + // 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. + void* savedState; + size_t savedStateSize; + + // The ALooper associated with the app's thread. + ALooper* looper; + + // When non-NULL, this is the input queue from which the app will + // receive user input events. + AInputQueue* inputQueue; + + // When non-NULL, this is the window surface that the app can draw in. + ANativeWindow* window; + + // Current content rectangle of the window; this is the area where the + // window's content should be placed to be seen by the user. + ARect contentRect; + + // 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. + int activityState; + + // This is non-zero when the application's NativeActivity is being + // destroyed and waiting for the app thread to complete. + int destroyRequested; + + // ------------------------------------------------- + // Below are "private" implementation of the glue code. + + pthread_mutex_t mutex; + pthread_cond_t cond; + + int msgread; + int msgwrite; + + pthread_t thread; + + struct android_poll_source cmdPollSource; + struct android_poll_source inputPollSource; + + int running; + int stateSaved; + int destroyed; + int redrawNeeded; + AInputQueue* pendingInputQueue; + ANativeWindow* pendingWindow; + ARect pendingContentRect; +}; + +enum { + /** + * 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, +}; + +enum { + /** + * 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, + + /** + * 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, + + /** + * 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, + + /** + * Command from main thread: the current ANativeWindow has been resized. + * Please redraw with its new size. + */ + APP_CMD_WINDOW_RESIZED, + + /** + * 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, + + /** + * 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, + + /** + * Command from main thread: the app's activity window has gained + * input focus. + */ + APP_CMD_GAINED_FOCUS, + + /** + * Command from main thread: the app's activity window has lost + * input focus. + */ + APP_CMD_LOST_FOCUS, + + /** + * Command from main thread: the current device configuration has changed. + */ + APP_CMD_CONFIG_CHANGED, + + /** + * Command from main thread: the system is running low on memory. + * Try to reduce your memory use. + */ + APP_CMD_LOW_MEMORY, + + /** + * Command from main thread: the app's activity has been started. + */ + APP_CMD_START, + + /** + * Command from main thread: the app's activity has been resumed. + */ + APP_CMD_RESUME, + + /** + * 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, + + /** + * Command from main thread: the app's activity has been paused. + */ + APP_CMD_PAUSE, + + /** + * Command from main thread: the app's activity has been stopped. + */ + APP_CMD_STOP, + + /** + * 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, +}; + +/** + * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next + * app command message. + */ +int8_t android_app_read_cmd(struct android_app* android_app); + +/** + * 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. + */ +void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * 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. + */ +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * Dummy function you can call to ensure glue code isn't stripped. + */ +void app_dummy(); + +/** + * This is the function that application code must implement, representing + * the main entry to the app. + */ +extern void android_main(struct android_app* app); + +#ifdef __cplusplus +} +#endif + +#endif /* _ANDROID_NATIVE_APP_GLUE_H */ diff -r c6971ff4795e -r 7d795dade0bc src/android/assman.c --- a/src/android/assman.c Fri Oct 02 07:10:19 2015 +0300 +++ b/src/android/assman.c Sat Oct 03 06:10:30 2015 +0300 @@ -1,8 +1,9 @@ #include #include #include "assman.h" -#include "native_glue.h" -#include "amain.h" +#include "android_native_app_glue.h" + +struct android_app *app; /* defined in android/amain.c */ ass_file *ass_fopen(const char *fname, const char *mode) { diff -r c6971ff4795e -r 7d795dade0bc src/android/logger.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/android/logger.c Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include "logger.h" + +static void *thread_func(void *arg); + +static int pfd[2]; +static pthread_t thr; +static int initialized; + +int start_logger(void) +{ + if(initialized) { + return 1; + } + + /* set stdout to line-buffered, and stderr to unbuffered */ + setvbuf(stdout, 0, _IOLBF, 0); + setvbuf(stderr, 0, _IONBF, 0); + + if(pipe(pfd) == -1) { + perror("failed to create logging pipe"); + return -1; + } + assert(pfd[0] > 2 && pfd[1] > 2); + + /* redirect stdout & stderr to the write-end of the pipe */ + dup2(pfd[1], 1); + dup2(pfd[1], 2); + + /* start the logging thread */ + if(pthread_create(&thr, 0, thread_func, 0) == -1) { + perror("failed to spawn logging thread"); + return -1; + } + pthread_detach(thr); + return 0; +} + +static void *thread_func(void *arg) +{ + ssize_t rdsz; + char buf[257]; + + __android_log_print(ANDROID_LOG_DEBUG, APP_NAME, "logger starting up..."); + + while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { + if(buf[rdsz - 1] == '\n') { + --rdsz; + } + buf[rdsz] = 0; + __android_log_write(ANDROID_LOG_DEBUG, APP_NAME, buf); + } + + __android_log_print(ANDROID_LOG_DEBUG, APP_NAME, "logger shutting down..."); + return 0; +} diff -r c6971ff4795e -r 7d795dade0bc src/android/logger.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/android/logger.h Sat Oct 03 06:10:30 2015 +0300 @@ -0,0 +1,6 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +int start_logger(void); + +#endif /* LOGGER_H_ */ diff -r c6971ff4795e -r 7d795dade0bc src/opengl.h --- a/src/opengl.h Fri Oct 02 07:10:19 2015 +0300 +++ b/src/opengl.h Sat Oct 03 06:10:30 2015 +0300 @@ -1,6 +1,6 @@ /* Stereoscopic tunnel for iOS. -Copyright (C) 2011 John Tsiombikas +Copyright (C) 2011-2015 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,13 @@ #define glClearDepth glClearDepthf #define GLDEF #include "sanegl.h" + +#elif defined(ANDROID) +#include +#include +#define GLDEF +#include "sanegl.h" + #else #include