summaryrefslogtreecommitdiff
path: root/sal/android
diff options
context:
space:
mode:
authorTor Lillqvist <tlillqvist@suse.com>2011-11-30 21:39:18 +0200
committerTor Lillqvist <tlillqvist@suse.com>2011-11-30 21:52:52 +0200
commit5510127e89d6971a219ce3664e4631d6c6dda2b1 (patch)
tree7e2d7d06892e086700d5b6bddc8a3ec99c476f6b /sal/android
parenta7fe1a0a088b2d3d68e3fc4b0d3e8d5a34f3e203 (diff)
Android code refactorig and hacking
Sorry for the large unstructured commit. But hey, the Android code is experimental so far. Extract the native lo-bootstrap code into a fairly normal library built in sal. (Previously it was the JNI part of the "Bootstrap" app.) Just linkink normally to liblo-bootstrap from C++ code that uses it works fine, no need to do a dlsym lookup. Bootstrap is still a subclass of NativeActivity and can thus still be used as an "app" (to start unit tests, or whatever), but can also be used from some other app's Java code to just get access to the lo-bootstrap native methods. Introduce a new top-level "module", android, for Bootstrap and the experiments with DocumentLoader. Note that the experimental DocumentLoader app still crashes. It can't create the com.sun.star.frame.Desktop instance. I spent lots of time debugging in the painfully inadequate ndk-gdb. (Even the newer gdb build from the "mingw-and-ndk" project is quite crappy in many ways.) I should really experiment with corresponding code on a normal platform first before even trying on Android. Basically, I think that if I just can get the concept of Java code that instantiates and uses LO components *in-process* working on a normal desktop platform, it should work on Android, too.
Diffstat (limited to 'sal/android')
-rw-r--r--sal/android/android_native_app_glue.c435
-rw-r--r--sal/android/android_native_app_glue.h349
-rw-r--r--sal/android/lo-bootstrap.c1057
-rw-r--r--sal/android/makefile.mk59
4 files changed, 1900 insertions, 0 deletions
diff --git a/sal/android/android_native_app_glue.c b/sal/android/android_native_app_glue.c
new file mode 100644
index 000000000000..8ff9f69d9946
--- /dev/null
+++ b/sal/android/android_native_app_glue.c
@@ -0,0 +1,435 @@
+/*
+ * 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 <jni.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/resource.h>
+
+#include "android_native_app_glue.h"
+#include <android/log.h>
+
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
+
+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 {
+ LOGI("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);
+
+ LOGI("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:
+ LOGI("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) {
+ LOGI("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:
+ LOGI("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:
+ LOGI("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:
+ LOGI("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:
+ LOGI("APP_CMD_CONFIG_CHANGED\n");
+ AConfiguration_fromAssetManager(android_app->config,
+ android_app->activity->assetManager);
+ print_cur_config(android_app);
+ break;
+
+ case APP_CMD_DESTROY:
+ LOGI("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:
+ LOGI("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:
+ LOGI("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) {
+ LOGI("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;
+ (void) source;
+ if (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
+ LOGI("New input event: type=%d\n", AInputEvent_getType(event));
+ if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
+ return;
+ }
+ int32_t handled = 0;
+ if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
+ AInputQueue_finishEvent(app->inputQueue, event, handled);
+ } else {
+ LOGI("Failure reading next input event: %s\n", strerror(errno));
+ }
+}
+
+static void process_cmd(struct android_app* app, struct android_poll_source* source) {
+ int8_t cmd = android_app_read_cmd(app);
+ (void) source;
+ 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)) {
+ LOGI("could not create pipe: %s", strerror(errno));
+ }
+ 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)) {
+ LOGI("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) {
+ LOGI("Destroy: %p\n", activity);
+ android_app_free((struct android_app*)activity->instance);
+}
+
+static void onStart(ANativeActivity* activity) {
+ LOGI("Start: %p\n", activity);
+ android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START);
+}
+
+static void onResume(ANativeActivity* activity) {
+ LOGI("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;
+
+ LOGI("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) {
+ LOGI("Pause: %p\n", activity);
+ android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE);
+}
+
+static void onStop(ANativeActivity* activity) {
+ LOGI("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;
+ LOGI("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;
+ LOGI("LowMemory: %p\n", activity);
+ android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY);
+}
+
+static void onWindowFocusChanged(ANativeActivity* activity, int focused) {
+ LOGI("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) {
+ LOGI("NativeWindowCreated: %p -- %p\n", activity, window);
+ android_app_set_window((struct android_app*)activity->instance, window);
+}
+
+static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
+ LOGI("NativeWindowDestroyed: %p -- %p\n", activity, window);
+ android_app_set_window((struct android_app*)activity->instance, NULL);
+}
+
+static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
+ LOGI("InputQueueCreated: %p -- %p\n", activity, queue);
+ android_app_set_input((struct android_app*)activity->instance, queue);
+}
+
+static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
+ LOGI("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) {
+ LOGI("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 --git a/sal/android/android_native_app_glue.h b/sal/android/android_native_app_glue.h
new file mode 100644
index 000000000000..1b8c1f10725d
--- /dev/null
+++ b/sal/android/android_native_app_glue.h
@@ -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 <poll.h>
+#include <pthread.h>
+#include <sched.h>
+
+#include <android/configuration.h>
+#include <android/looper.h>
+#include <android/native_activity.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * 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.
+ */
+
+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 --git a/sal/android/lo-bootstrap.c b/sal/android/lo-bootstrap.c
new file mode 100644
index 000000000000..6ac285d498d1
--- /dev/null
+++ b/sal/android/lo-bootstrap.c
@@ -0,0 +1,1057 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Version: MPL 1.1 / GPLv3+ / LGPLv3+
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License or as specified alternatively below. You may obtain a copy of
+ * the License at http: *www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * Major Contributor(s):
+ * Copyright (C) 2011 Tor Lillqvist <tml@iki.fi> (initial developer)
+ * Copyright (C) 2011 SUSE Linux http://suse.com (initial developer's employer)
+ *
+ * Zip parsing code lifted from Mozilla's other-licenses/android/APKOpen.cpp,
+ * by Michael Wu <mwu@mozilla.com>.
+ *
+ * All Rights Reserved.
+ *
+ * For minor contributions see the git repository.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 3 or later (the "GPLv3+"), or
+ * the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
+ * in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
+ * instead of those above.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <sys/mman.h>
+
+#include <jni.h>
+
+#include <linux/elf.h>
+
+#include <android/log.h>
+
+#include "lo-bootstrap.h"
+
+#include "android_native_app_glue.c"
+
+#undef LOGI
+#undef LOGW
+
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "lo-bootstrap", __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "lo-bootstrap", __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "lo-bootstrap", __VA_ARGS__))
+#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "lo-bootstrap", __VA_ARGS__))
+
+#define ROUND_DOWN(ptr,multiple) (void *)(((unsigned) (ptr)) & ~((multiple)-1))
+
+struct engine {
+ int dummy;
+};
+
+static struct android_app *app;
+static const char **library_locations;
+static void *apk_file;
+static int apk_file_size;
+static int (*lo_main)(int, const char **);
+static int lo_main_argc;
+static const char **lo_main_argv;
+static int sleep_time = 0;
+
+/* Zip data structures */
+
+/* compression methods */
+#define STORE 0
+#define DEFLATE 8
+#define LZMA 14
+
+struct local_file_header {
+ uint32_t signature;
+ uint16_t min_version;
+ uint16_t general_flag;
+ uint16_t compression;
+ uint16_t lastmod_time;
+ uint16_t lastmod_date;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_size;
+ uint16_t extra_field_size;
+ char data[0];
+} __attribute__((__packed__));
+
+struct cdir_entry {
+ uint32_t signature;
+ uint16_t creator_version;
+ uint16_t min_version;
+ uint16_t general_flag;
+ uint16_t compression;
+ uint16_t lastmod_time;
+ uint16_t lastmod_date;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_size;
+ uint16_t extra_field_size;
+ uint16_t file_comment_size;
+ uint16_t disk_num;
+ uint16_t internal_attr;
+ uint32_t external_attr;
+ uint32_t offset;
+ char data[0];
+} __attribute__((__packed__));
+
+#define CDIR_END_SIG 0x06054b50
+
+struct cdir_end {
+ uint32_t signature;
+ uint16_t disk_num;
+ uint16_t cdir_disk;
+ uint16_t disk_entries;
+ uint16_t cdir_entries;
+ uint32_t cdir_size;
+ uint32_t cdir_offset;
+ uint16_t comment_size;
+ char comment[0];
+} __attribute__((__packed__));
+
+/* End of Zip data structures */
+
+static void
+engine_handle_cmd(struct android_app* app,
+ int32_t cmd)
+{
+ struct engine* engine = (struct engine*)app->userData;
+ switch (cmd) {
+ case APP_CMD_SAVE_STATE:
+ break;
+ case APP_CMD_INIT_WINDOW:
+ break;
+ case APP_CMD_TERM_WINDOW:
+ break;
+ case APP_CMD_GAINED_FOCUS:
+ break;
+ case APP_CMD_LOST_FOCUS:
+ break;
+ }
+}
+
+static char *
+read_section(int fd,
+ Elf32_Shdr *shdr)
+{
+ char *result;
+
+ result = malloc(shdr->sh_size);
+ if (lseek(fd, shdr->sh_offset, SEEK_SET) < 0) {
+ close(fd);
+ free(result);
+ return NULL;
+ }
+ if (read(fd, result, shdr->sh_size) < shdr->sh_size) {
+ close(fd);
+ free(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+static void
+free_ptrarray(void **pa)
+{
+ void **rover = pa;
+
+ while (*rover != NULL)
+ free(*rover++);
+
+ free(pa);
+}
+
+jobjectArray
+Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env,
+ jobject clazz,
+ jstring library)
+{
+ char **needed;
+ int n_needed;
+ const jbyte *libName;
+ jclass String;
+ jobjectArray result;
+
+ libName = (*env)->GetStringUTFChars(env, library, NULL);
+
+ needed = lo_dlneeds(libName);
+
+ (*env)->ReleaseStringUTFChars(env, library, libName);
+
+ if (needed == NULL)
+ return NULL;
+
+ n_needed = 0;
+ while (needed[n_needed] != NULL)
+ n_needed++;
+
+ /* Allocate return value */
+
+ String = (*env)->FindClass(env, "java/lang/String");
+ if (String == NULL) {
+ LOGE("Could not find the String class");
+ free_ptrarray((void **) needed);
+ return NULL;
+ }
+
+ result = (*env)->NewObjectArray(env, n_needed, String, NULL);
+ if (result == NULL) {
+ LOGE("Could not create the String array");
+ free_ptrarray((void **) needed);
+ return NULL;
+ }
+
+ for (n_needed = 0; needed[n_needed] != NULL; n_needed++)
+ (*env)->SetObjectArrayElement(env, result, n_needed, (*env)->NewStringUTF(env, needed[n_needed]));
+
+ free_ptrarray((void **) needed);
+
+ return result;
+}
+
+jint
+Java_org_libreoffice_android_Bootstrap_dlopen(JNIEnv* env,
+ jobject clazz,
+ jstring library)
+{
+ const jbyte *libName = (*env)->GetStringUTFChars(env, library, NULL);
+ void *p = lo_dlopen (libName);
+
+ (*env)->ReleaseStringUTFChars(env, library, libName);
+
+ return (jint) p;
+}
+
+jint
+Java_org_libreoffice_android_Bootstrap_dlsym(JNIEnv* env,
+ jobject clazz,
+ jint handle,
+ jstring symbol)
+{
+ const jbyte *symName = (*env)->GetStringUTFChars(env, symbol, NULL);
+ void *p = lo_dlsym ((void *) handle, symName);
+
+ (*env)->ReleaseStringUTFChars(env, symbol, symName);
+
+ return (jint) p;
+}
+
+jint
+Java_org_libreoffice_android_Bootstrap_dlcall(JNIEnv* env,
+ jobject clazz,
+ jint function,
+ jobject argument)
+{
+ jclass StringArray = (*env)->FindClass(env, "[Ljava/lang/String;");
+
+ if (StringArray == NULL) {
+ LOGE("Could not find String[] class");
+ return 0;
+ }
+
+ if ((*env)->IsInstanceOf(env, argument, StringArray)) {
+ int argc = (*env)->GetArrayLength(env, argument);
+ const char **argv = malloc(sizeof(char *) * (argc+1));
+ int i, result;
+ for (i = 0; i < argc; i++) {
+ argv[i] = (*env)->GetStringUTFChars(env, (*env)->GetObjectArrayElement(env, argument, i), NULL);
+ /* LOGI("argv[%d] = %s", i, argv[i]); */
+ }
+ argv[argc] = NULL;
+
+ result = lo_dlcall_argc_argv((void *) function, argc, argv);
+
+ for (i = 0; i < argc; i++)
+ (*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, argument, i), argv[i]);
+
+ free(argv);
+ return result;
+ }
+
+ return 0;
+}
+
+// public static native boolean setup(String dataDir,
+// String apkFile,
+// String[] ld_library_path);
+
+jboolean
+Java_org_libreoffice_android_Bootstrap_setup__Ljava_lang_String_2Ljava_lang_String_2_3Ljava_lang_String_2
+ (JNIEnv* env,
+ jobject clazz,
+ jstring dataDir,
+ jstring apkFile,
+ jobjectArray ld_library_path)
+{
+ struct stat st;
+ int i, n, fd;
+ const jbyte *dataDirPath;
+ const jbyte *apkFilePath;
+ char *lib_dir;
+
+ n = (*env)->GetArrayLength(env, ld_library_path);
+
+ library_locations = malloc((n+2) * sizeof(char *));
+
+ dataDirPath = (*env)->GetStringUTFChars(env, dataDir, NULL);
+
+ lib_dir = malloc(strlen(dataDirPath) + 5);
+ strcpy(lib_dir, dataDirPath);
+ strcat(lib_dir, "/lib");
+
+ (*env)->ReleaseStringUTFChars(env, dataDir, dataDirPath);
+
+ library_locations[0] = lib_dir;
+
+ for (i = 0; i < n; i++) {
+ const jbyte *s = (*env)->GetStringUTFChars(env, (*env)->GetObjectArrayElement(env, ld_library_path, i), NULL);
+ library_locations[i+1] = strdup(s);
+ (*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, ld_library_path, i), s);
+ }
+
+ library_locations[n+1] = NULL;
+
+ for (n = 0; library_locations[n] != NULL; n++)
+ LOGI("library_locations[%d] = %s", n, library_locations[n]);
+
+ apkFilePath = (*env)->GetStringUTFChars(env, apkFile, NULL);
+
+ fd = open(apkFilePath, O_RDONLY);
+ if (fd == -1) {
+ LOGE("Could not open %s", apkFilePath);
+ (*env)->ReleaseStringUTFChars(env, apkFile, apkFilePath);
+ return JNI_FALSE;
+ }
+ if (fstat(fd, &st) == -1) {
+ LOGE("Could not fstat %s", apkFilePath);
+ close(fd);
+ (*env)->ReleaseStringUTFChars(env, apkFile, apkFilePath);
+ return JNI_FALSE;
+ }
+ apk_file = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
+
+ if (apk_file == MAP_FAILED) {
+ LOGE("Could not mmap %s", apkFilePath);
+ (*env)->ReleaseStringUTFChars(env, apkFile, apkFilePath);
+ return JNI_FALSE;
+ }
+ apk_file_size = st.st_size;
+
+ (*env)->ReleaseStringUTFChars(env, apkFile, apkFilePath);
+
+ return JNI_TRUE;
+}
+
+// public statuc native boolean setup(int lo_main_ptr,
+// Object lo_main_argument,
+// int lo_main_delay);
+
+jboolean
+Java_org_libreoffice_android_Bootstrap_setup__ILjava_lang_Object_2I(JNIEnv* env,
+ jobject clazz,
+ void *lo_main_ptr,
+ jobject lo_main_argument,
+ jint lo_main_delay)
+{
+ jclass StringArray;
+ int i;
+
+ lo_main = lo_main_ptr;
+
+ StringArray = (*env)->FindClass(env, "[Ljava/lang/String;");
+ if (StringArray == NULL) {
+ LOGE("Could not find String[] class");
+ return JNI_FALSE;
+ }
+
+ if (!(*env)->IsInstanceOf(env, lo_main_argument, StringArray)) {
+ LOGE("lo_main_argument is not a String[]?");
+ return JNI_FALSE;
+ }
+
+ lo_main_argc = (*env)->GetArrayLength(env, lo_main_argument);
+ lo_main_argv = malloc(sizeof(char *) * (lo_main_argc+1));
+
+ for (i = 0; i < lo_main_argc; i++) {
+ const jbyte *s = (*env)->GetStringUTFChars(env, (*env)->GetObjectArrayElement(env, lo_main_argument, i), NULL);
+ lo_main_argv[i] = strdup(s);
+ (*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, lo_main_argument, i), s);
+ /* LOGI("argv[%d] = %s", i, lo_main_argv[i]); */
+ }
+ lo_main_argv[lo_main_argc] = NULL;
+
+ sleep_time = lo_main_delay;
+
+ return JNI_TRUE;
+}
+
+// public static native int getpid();
+
+jint
+Java_org_libreoffice_android_Bootstrap_getpid(JNIEnv* env,
+ jobject clazz)
+{
+ return getpid();
+}
+
+
+// public static native void system(String cmdline);
+
+jint
+Java_org_libreoffice_android_Bootstrap_system(JNIEnv* env,
+ jobject clazz,
+ jstring cmdline)
+{
+ const jbyte *s = (*env)->GetStringUTFChars(env, cmdline, NULL);
+
+ LOGI("system(%s)", s);
+
+ system(s);
+
+ (*env)->ReleaseStringUTFChars(env, cmdline, s);
+}
+
+// public static native void putenv(String string);
+
+void
+Java_org_libreoffice_android_Bootstrap_putenv(JNIEnv* env,
+ jobject clazz,
+ jstring string)
+{
+ const jbyte *s = (*env)->GetStringUTFChars(env, string, NULL);
+
+ LOGI("putenv(%s)", s);
+
+ putenv(s);
+
+ (*env)->ReleaseStringUTFChars(env, string, s);
+}
+
+char **
+lo_dlneeds(const char *library)
+{
+ int i, fd;
+ int n_needed;
+ char **result;
+ char *shstrtab, *dynstr;
+ Elf32_Ehdr hdr;
+ Elf32_Shdr shdr;
+ Elf32_Dyn dyn;
+
+ /* Open library and read ELF header */
+
+ fd = open(library, O_RDONLY);
+
+ if (fd == -1) {
+ LOGE("lo_dlneeds: Could not open library %s: %s", library, strerror(errno));
+ return NULL;
+ }
+
+ if (read(fd, &hdr, sizeof(hdr)) < sizeof(hdr)) {
+ LOGE("lo_dlneeds: Could not read ELF header of %s", library);
+ close(fd);
+ return NULL;
+ }
+
+ /* Read in .shstrtab */
+
+ if (lseek(fd, hdr.e_shoff + hdr.e_shstrndx * sizeof(shdr), SEEK_SET) < 0) {
+ LOGE("lo_dlneeds: Could not seek to .shstrtab section header of %s", library);
+ close(fd);
+ return NULL;
+ }
+ if (read(fd, &shdr, sizeof(shdr)) < sizeof(shdr)) {
+ LOGE("lo_dlneeds: Could not read section header of %s", library);
+ close(fd);
+ return NULL;
+ }
+
+ shstrtab = read_section(fd, &shdr);
+ if (shstrtab == NULL)
+ return NULL;
+
+ /* Read section headers, looking for .dynstr section */
+
+ if (lseek(fd, hdr.e_shoff, SEEK_SET) < 0) {
+ LOGE("lo_dlneeds: Could not seek to section headers of %s", library);
+ close(fd);
+ return NULL;
+ }
+ for (i = 0; i < hdr.e_shnum; i++) {
+ if (read(fd, &shdr, sizeof(shdr)) < sizeof(shdr)) {
+ LOGE("lo_dlneeds: Could not read section header of %s", library);
+ close(fd);
+ return NULL;
+ }
+ if (shdr.sh_type == SHT_STRTAB &&
+ strcmp(shstrtab + shdr.sh_name, ".dynstr") == 0) {
+ dynstr = read_section(fd, &shdr);
+ if (dynstr == NULL) {
+ free(shstrtab);
+ return NULL;
+ }
+ break;
+ }
+ }
+
+ if (i == hdr.e_shnum) {
+ LOGE("lo_dlneeds: No .dynstr section in %s", library);
+ close(fd);
+ return NULL;
+ }
+
+ /* Read section headers, looking for .dynamic section */
+
+ if (lseek(fd, hdr.e_shoff, SEEK_SET) < 0) {
+ LOGE("lo_dlneeds: Could not seek to section headers of %s", library);
+ close(fd);
+ return NULL;
+ }
+ for (i = 0; i < hdr.e_shnum; i++) {
+ if (read(fd, &shdr, sizeof(shdr)) < sizeof(shdr)) {
+ LOGE("lo_dlneeds: Could not read section header of %s", library);
+ close(fd);
+ return NULL;
+ }
+ if (shdr.sh_type == SHT_DYNAMIC) {
+ int dynoff;
+ int *libnames;
+
+ /* Count number of DT_NEEDED entries */
+ n_needed = 0;
+ if (lseek(fd, shdr.sh_offset, SEEK_SET) < 0) {
+ LOGE("lo_dlneeds: Could not seek to .dynamic section of %s", library);
+ close(fd);
+ return NULL;
+ }
+ for (dynoff = 0; dynoff < shdr.sh_size; dynoff += sizeof(dyn)) {
+ if (read(fd, &dyn, sizeof(dyn)) < sizeof(dyn)) {
+ LOGE("lo_dlneeds: Could not read .dynamic entry of %s", library);
+ close(fd);
+ return NULL;
+ }
+ if (dyn.d_tag == DT_NEEDED)
+ n_needed++;
+ }
+
+ /* LOGI("Found %d DT_NEEDED libs", n_needed); */
+
+ result = malloc((n_needed+1) * sizeof(char *));
+
+ n_needed = 0;
+ if (lseek(fd, shdr.sh_offset, SEEK_SET) < 0) {
+ LOGE("lo_dlneeds: Could not seek to .dynamic section of %s", library);
+ close(fd);
+ free(result);
+ return NULL;
+ }
+ for (dynoff = 0; dynoff < shdr.sh_size; dynoff += sizeof(dyn)) {
+ if (read(fd, &dyn, sizeof(dyn)) < sizeof(dyn)) {
+ LOGE("lo_dlneeds: Could not read .dynamic entry in %s", library);
+ close(fd);
+ free(result);
+ return NULL;
+ }
+ if (dyn.d_tag == DT_NEEDED) {
+ /* LOGI("needs: %s\n", dynstr + dyn.d_un.d_val); */
+ result[n_needed] = strdup(dynstr + dyn.d_un.d_val);
+ n_needed++;
+ }
+ }
+
+ close(fd);
+ free(dynstr);
+ free(shstrtab);
+ result[n_needed] = NULL;
+ return result;
+ }
+ }
+
+ LOGE("lo_dlneeds: Could not find .dynamic section in %s", library);
+ close(fd);
+ return NULL;
+}
+
+void *
+lo_dlopen(const char *library)
+{
+ /*
+ * We should *not* try to just dlopen() the bare library name
+ * first, as the stupid dynamic linker remembers for each library
+ * basename if loading it has failed. Thus if you try loading it
+ * once, and it fails because of missing needed libraries, and
+ * your load those, and then try again, it fails with an
+ * infuriating message "failed to load previously" in the log.
+ *
+ * We *must* first dlopen() all needed libraries, recursively. It
+ * shouldn't matter if we dlopen() a library that already is
+ * loaded, dlopen() just returns the same value then.
+ */
+
+ typedef struct loadedLib {
+ const char *name;
+ void *handle;
+ struct loadedLib *next;
+ } *loadedLib;
+ static loadedLib loaded_libraries = NULL;
+
+ loadedLib rover;
+ loadedLib new_loaded_lib;
+
+ struct stat st;
+ void *p;
+ char *full_name;
+ char **needed;
+ int i;
+ int found;
+
+ rover = loaded_libraries;
+ while (rover != NULL &&
+ strcmp(rover->name, library) != 0)
+ rover = rover->next;
+
+ if (rover != NULL)
+ return rover->handle;
+
+ /* LOGI("lo_dlopen(%s)", library); */
+
+ found = 0;
+ if (library[0] == '/') {
+ full_name = strdup(library);
+
+ if (stat(full_name, &st) == 0 &&
+ S_ISREG(st.st_mode))
+ found = 1;
+ else
+ free(full_name);
+ } else {
+ for (i = 0; !found && library_locations[i] != NULL; i++) {
+ full_name = malloc(strlen(library_locations[i]) + 1 + strlen(library) + 1);
+ strcpy(full_name, library_locations[i]);
+ strcat(full_name, "/");
+ strcat(full_name, library);
+
+ if (stat(full_name, &st) == 0 &&
+ S_ISREG(st.st_mode))
+ found = 1;
+ else
+ free(full_name);
+ }
+ }
+
+ if (!found) {
+ LOGE("lo_dlopen: Library %s not found", library);
+ return NULL;
+ }
+
+ needed = lo_dlneeds(full_name);
+ if (needed == NULL) {
+ free(full_name);
+ return NULL;
+ }
+
+ for (i = 0; needed[i] != NULL; i++) {
+ if (lo_dlopen(needed[i]) == NULL) {
+ free_ptrarray((void **) needed);
+ free(full_name);
+ return NULL;
+ }
+ }
+ free_ptrarray((void **) needed);
+
+ p = dlopen(full_name, RTLD_LOCAL);
+ LOGI("dlopen(%s) = %p", full_name, p);
+ free(full_name);
+ if (p == NULL)
+ LOGE("lo_dlopen: Error from dlopen(%s): %s", library, dlerror());
+
+ new_loaded_lib = malloc(sizeof(*new_loaded_lib));
+ new_loaded_lib->name = strdup(library);
+ new_loaded_lib->handle = p;
+
+ new_loaded_lib->next = loaded_libraries;
+ loaded_libraries = new_loaded_lib;
+
+ return p;
+}
+
+void *
+lo_dlsym(void *handle,
+ const char *symbol)
+{
+ void *p = dlsym(handle, symbol);
+ /* LOGI("dlsym(%p, %s) = %p", handle, symbol, p); */
+ if (p == NULL)
+ LOGE("lo_dlsym: %s", dlerror());
+ return p;
+}
+
+int
+lo_dladdr(void *addr,
+ Dl_info *info)
+{
+ FILE *maps;
+ char line[200];
+ int result;
+ int found;
+
+ result = dladdr(addr, info);
+ if (result == 0) {
+ /* LOGI("dladdr(%p) = 0", addr); */
+ return 0;
+ }
+
+ maps = fopen("/proc/self/maps", "r");
+ if (maps == NULL) {
+ LOGE("lo_dladdr: Could not open /proc/self/maps: %s", strerror(errno));
+ return 0;
+ }
+
+ found = 0;
+ while (fgets(line, sizeof(line), maps) != NULL &&
+ line[strlen(line)-1] == '\n') {
+ void *lo, *hi;
+ char file[sizeof(line)];
+ file[0] = '\0';
+ if (sscanf(line, "%x-%x %*s %*x %*x:%*x %*d %[^\n]", &lo, &hi, file) == 3) {
+ /* LOGI("got %p-%p: %s", lo, hi, file); */
+ if (addr >= lo && addr < hi) {
+ if (info->dli_fbase != lo) {
+ LOGE("lo_dladdr: Base for %s in /proc/self/maps %p doesn't match what dladdr() said", file, lo);
+ fclose(maps);
+ return 0;
+ }
+ /* LOGI("dladdr(%p) = { %s:%p, %s:%p }: %s",
+ addr,
+ info->dli_fname, info->dli_fbase,
+ info->dli_sname ? info->dli_sname : "(none)", info->dli_saddr,
+ file); */
+ info->dli_fname = strdup(file);
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found)
+ LOGE("lo_dladdr: Did not find %p in /proc/self/maps", addr);
+ fclose(maps);
+
+ return result;
+}
+
+static uint32_t cdir_entry_size (struct cdir_entry *entry)
+{
+ return sizeof(*entry) +
+ letoh16(entry->filename_size) +
+ letoh16(entry->extra_field_size) +
+ letoh16(entry->file_comment_size);
+}
+
+static struct cdir_entry *
+find_cdir_entry (struct cdir_entry *entry, int count, const char *name)
+{
+ size_t name_size = strlen(name);
+ while (count--) {
+ if (letoh16(entry->filename_size) == name_size &&
+ !memcmp(entry->data, name, name_size))
+ return entry;
+ entry = (struct cdir_entry *)((char *)entry + cdir_entry_size(entry));
+ }
+ return NULL;
+}
+
+void *
+lo_apkentry(const char *filename,
+ size_t *size)
+{
+ struct cdir_end *dirend = (struct cdir_end *)((char *) apk_file + apk_file_size - sizeof(*dirend));
+ uint32_t cdir_offset;
+ uint16_t cdir_entries;
+ struct cdir_entry *cdir_start;
+ struct cdir_entry *entry;
+ struct local_file_header *file;
+ void *data;
+
+ while ((void *)dirend > apk_file &&
+ letoh32(dirend->signature) != CDIR_END_SIG)
+ dirend = (struct cdir_end *)((char *)dirend - 1);
+ if (letoh32(dirend->signature) != CDIR_END_SIG) {
+ LOGE("lo_apkentry: Could not find end of central directory record");
+ return;
+ }
+
+ cdir_offset = letoh32(dirend->cdir_offset);
+ cdir_entries = letoh16(dirend->cdir_entries);
+ cdir_start = (struct cdir_entry *)((char *)apk_file + cdir_offset);
+
+ if (*filename == '/')
+ filename++;
+
+ entry = find_cdir_entry(cdir_start, cdir_entries, filename);
+
+ if (entry == NULL) {
+ LOGE("lo_apkentry: Could not find %s", filename);
+ return NULL;
+ }
+ file = (struct local_file_header *)((char *)apk_file + letoh32(entry->offset));
+
+ if (letoh16(file->compression) != STORE) {
+ LOGE("lo_apkentry: File %s is compressed", filename);
+ return NULL;
+ }
+
+ data = ((char *)&file->data) + letoh16(file->filename_size) + letoh16(file->extra_field_size);
+ *size = file->uncompressed_size;
+
+ /* LOGI("lo_apkentry(%s): %p, %d", filename, data, *size); */
+
+ return data;
+}
+
+int
+lo_dlcall_argc_argv(void *function,
+ int argc,
+ const char **argv)
+{
+ int (*fp)(int, const char **) = function;
+ int result = fp(argc, argv);
+
+ return result;
+}
+
+/* There is a bug in std::type_info::operator== and
+ * std::type_info::before() in libgnustl_shared.so in NDK r7 at
+ * least. They compare the type name pointers instead of comparing the
+ * type name strings. See
+ * http://code.google.com/p/android/issues/detail?id=22165 . So patch
+ * that, poke in jumps to our own code snippets below instead.
+ */
+
+/* Replacement std::type_info::operator== */
+
+__asm(" .arm\n"
+ " .global replacement_operator_equals_arm\n"
+ "replacement_operator_equals_arm:\n"
+ " push {lr}\n"
+
+ /* Load name pointers into r0 and r1 */
+ " ldr r0, [r0, #4]\n"
+ " ldr r1, [r1, #4]\n"
+
+ /* First compare pointers */
+ " cmp r0, r1\n"
+
+ /* If equal, return true */
+ " beq .L.equals.1\n"
+
+ /* Otherwise call strcmp */
+ " bl strcmp\n"
+
+ /* And return true or false */
+ " cmp r0, #0\n"
+ " moveq r0, #1\n"
+ " movne r0, #0\n"
+ " b .L.equals.9\n"
+
+ ".L.equals.1:\n"
+ " mov r0, #1\n"
+
+ ".L.equals.9:\n"
+ " pop {pc}\n"
+ );
+
+extern unsigned int replacement_operator_equals_arm;
+
+/* The ARM (not Thumb) code of the operator== in NDK r7 */
+static unsigned int expected_operator_equals_r7_code[] = {
+ 0xe5903004, /* ldr r3, [r0, #4] */
+ 0xe5910004, /* ldr r0, [r1, #4] */
+ 0xe1530000, /* cmp r3, r0 */
+ 0x13a00000, /* movne, #0 */
+ 0x03a00001, /* moveq r0, #1 */
+ 0xe12fff1e /* bx lr */
+};
+
+/* Ditto for std::type_info::before() */
+
+__asm(" .arm\n"
+ " .global replacement_method_before_arm\n"
+ "replacement_method_before_arm:\n"
+ " push {lr}\n"
+
+ /* Load name pointers into r0 and r1 */
+ " ldr r0, [r0, #4]\n"
+ " ldr r1, [r1, #4]\n"
+
+ /* First compare pointers */
+ " cmp r0, r1\n"
+
+ /* If equal, return false */
+ " beq .L.before.1\n"
+
+ /* Otherwise call strcmp */
+ " bl strcmp\n"
+
+ /* And return true or false */
+ " cmp r0, #0\n"
+ " movlt r0, #1\n"
+ " movge r0, #0\n"
+ " b .L.before.9\n"
+
+ ".L.before.1:\n"
+ " mov r0, #0\n"
+
+ ".L.before.9:\n"
+ " pop {pc}\n"
+ );
+
+extern unsigned int replacement_method_before_arm;
+
+static unsigned int expected_method_before_r7_code[] = {
+ 0xe5903004, /* ldr r3, [r0, #4] */
+ 0xe5910004, /* ldr r0, [r1, #4] */
+ 0xe1530000, /* cmp r3, r0 */
+ 0x23a00000, /* movcs r0, #0 */
+ 0x33a00001, /* movcc r0, #1 */
+ 0xe12fff1e /* bx lr */
+};
+
+static void
+patch(const char *symbol,
+ const char *plaintext,
+ unsigned *expected_code,
+ size_t expected_code_size,
+ unsigned *replacement_code)
+{
+
+ void *libgnustl_shared;
+ void *code;
+
+ void *base;
+ size_t size;
+
+ /* libgnustl_shared.so should be already loaded as we build
+ * all LO code against it, so as we have loaded the .so
+ * containing lo_main() already, libgnustl_shared.so will have
+ * been brought in, too.
+ */
+ libgnustl_shared = dlopen("libgnustl_shared.so", RTLD_LOCAL);
+ if (libgnustl_shared == NULL) {
+ LOGF("android_main: libgnustl_shared.so not mapped??");
+ exit(0);
+ }
+
+ code = dlsym(libgnustl_shared, symbol);
+ if (code == NULL) {
+ LOGF("android_main: %s not found!?", plaintext);
+ exit(0);
+ }
+ /* LOGI("%s is at %p", plaintext, operator_equals); */
+
+ if ((((unsigned) code) & 0x03) != 0) {
+ LOGE("android_main: Address of %s is not at word boundary, huh?", plaintext);
+ return;
+ }
+
+ if ((((unsigned) &replacement_code) & 0x03) != 0) {
+ LOGE("android_main: Address of replacement %s is not at word boundary, huh?", plaintext);
+ return;
+ }
+
+ if (memcmp(code, expected_code, expected_code_size) != 0) {
+ LOGI("android_main: Code for %s does not match that in NDK r7; not patching it", plaintext);
+ return;
+ }
+
+ base = ROUND_DOWN(code, getpagesize());
+ size = code + sizeof(expected_code_size) - ROUND_DOWN(code, getpagesize());
+ if (mprotect(base, size, PROT_READ|PROT_WRITE|PROT_EXEC) == -1) {
+ LOGE("android_main: mprotect() failed: %s", strerror(errno));
+ return;
+ }
+
+ /* Poke a "b replacement_code" into it instead */
+ *((unsigned *) code) =
+ (0xEA000000 |
+ ((((int) replacement_code - ((int) code + 8)) / 4) & 0x00FFFFFF));
+}
+
+static void
+patch_libgnustl_shared(void)
+{
+ patch("_ZNKSt9type_infoeqERKS_",
+ "std::type_info::operator==",
+ expected_operator_equals_r7_code,
+ sizeof(expected_operator_equals_r7_code),
+ &replacement_operator_equals_arm);
+
+ patch("_ZNKSt9type_info6beforeERKS_",
+ "std::type_info::before()",
+ expected_method_before_r7_code,
+ sizeof(expected_method_before_r7_code),
+ &replacement_method_before_arm);
+}
+
+void
+Java_org_libreoffice_android_Bootstrap_patch_libgnustl_shared(JNIEnv* env,
+ jobject clazz)
+{
+ patch_libgnustl_shared();
+}
+
+JavaVM *
+lo_get_javavm(void)
+{
+ return app->activity->vm;
+}
+
+void
+android_main(struct android_app* state)
+{
+ struct engine engine;
+ Dl_info lo_main_info;
+
+ app = state;
+
+ memset(&engine, 0, sizeof(engine));
+ state->userData = &engine;
+ state->onAppCmd = engine_handle_cmd;
+
+ if (lo_dladdr(lo_main, &lo_main_info) != 0) {
+ lo_main_argv[0] = lo_main_info.dli_fname;
+ }
+
+ if (sleep_time != 0)
+ sleep(sleep_time);
+
+ patch_libgnustl_shared();
+
+ lo_main(lo_main_argc, lo_main_argv);
+
+ exit(0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sal/android/makefile.mk b/sal/android/makefile.mk
new file mode 100644
index 000000000000..74de4ee1997a
--- /dev/null
+++ b/sal/android/makefile.mk
@@ -0,0 +1,59 @@
+# Version: MPL 1.1 / GPLv3+ / LGPLv3+
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License or as specified alternatively below. You may obtain a copy of
+# the License at http:#www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# Major Contributor(s):
+# Copyright (C) 2011 Tor Lillqvist <tml@iki.fi> (initial developer)
+# Copyright (C) 2011 SUSE Linux http://suse.com (initial developer's employer)
+#
+# Zip parsing code lifted from Mozilla's other-licenses/android/APKOpen.cpp,
+# by Michael Wu <mwu@mozilla.com>.
+#
+# All Rights Reserved.
+#
+# For minor contributions see the git repository.
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 3 or later (the "GPLv3+"), or
+# the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
+# in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
+# instead of those above.
+
+PRJ = ..
+PRJNAME = sal
+TARGET = lo-bootstrap
+
+# Too many warnings from android_native_app_glue.[ch]
+EXTERNAL_WARNINGS_NOT_ERRORS = TRUE
+
+.INCLUDE : settings.mk
+
+.IF "$(OS)" != "ANDROID"
+ALL:
+ @: do nothing
+.ENDIF
+
+SHL1TARGET = $(TARGET)
+
+SHL1OBJS = \
+ $(SLO)$/lo-bootstrap.obj
+
+# We don't want to link liblo-bootstrap.so against
+# libgnustl_shared.so. The Android dynamic linker won't find it
+# anyway. One very point of liblo-bootstrap is its wrapper for
+# dlopen() that searches also in the app's lib folder for needed
+# shared libraries. So just re-define STDSHLCUIMT.
+
+STDSHLCUIMT := -llog -landroid
+
+# Also don't pointless
+
+.INCLUDE : target.mk