diff options
author | Tor Lillqvist <tlillqvist@suse.com> | 2011-11-15 01:41:57 +0200 |
---|---|---|
committer | Tor Lillqvist <tlillqvist@suse.com> | 2011-11-15 01:54:49 +0200 |
commit | 2bf110d691053353d6cb06ad2bbd1b78fef60f19 (patch) | |
tree | 159fcfed4e1d335f65a1e17802a2445a7cfe2487 /sal | |
parent | d14416463a0e62cf624cf66e8a599abac5694cd2 (diff) |
Rework the bootstrap mechanism of a LO program on Android
Do use NativeActivity and android_native_app_glue after all.
I hope this enables us to have a "message pump" (a loop that typically
would call ALooper_pollAll()) inside the LO "program" being run, as
expected by LO code.
(On Android, a "program", even one mostly implemented in native code,
is actually a shared library loaded by the main Java code of an app.)
The android_native_app_glue code and the android_main() it calls
belongs in the bootstrap library, though. Not in SAL_MAIN_IMPL.
The earlier idea, having a "normal" Java Activity subclass, would mean
events come in as method calls to that class. To then turn those into
something the LO code can "get", we would have effectively had to
re-implement what android_native_app_glue does anyway.
Diffstat (limited to 'sal')
-rw-r--r-- | sal/inc/sal/main.h | 3 | ||||
-rw-r--r-- | sal/osl/android/AndroidManifest.xml | 9 | ||||
-rw-r--r-- | sal/osl/android/Makefile | 2 | ||||
-rw-r--r-- | sal/osl/android/jni/Android.mk | 2 | ||||
-rw-r--r-- | sal/osl/android/jni/android_native_app_glue.c | 435 | ||||
-rw-r--r-- | sal/osl/android/jni/android_native_app_glue.h | 349 | ||||
-rw-r--r-- | sal/osl/android/jni/lo-bootstrap.c | 524 | ||||
-rw-r--r-- | sal/osl/android/project.properties | 2 | ||||
-rw-r--r-- | sal/osl/android/src/org/libreoffice/android/Bootstrap.java | 150 |
9 files changed, 1286 insertions, 190 deletions
diff --git a/sal/inc/sal/main.h b/sal/inc/sal/main.h index 6bbbb1e8e2a2..d1d3fffde862 100644 --- a/sal/inc/sal/main.h +++ b/sal/inc/sal/main.h @@ -133,6 +133,7 @@ static int sal_main_with_args(int argc, char **argv); \ \ void lo_main(int argc, char **argv) \ { \ + LOGI("in lo_main, argc=%d, argv[0]=%s, argv[1]=%s", argc, argv[0], argv[1]); \ sal_detail_initialize(argc, argv); \ sal_main_with_args(argc, argv); \ sal_detail_deinitialize(); \ @@ -143,7 +144,7 @@ static int sal_main(void); \ \ void lo_main(int argc, char **argv) \ { \ - LOGI("in lo_main, argc=%d, argv[0]=%s, argv[1]=%s", argc, argv[0], argv[1]); \ + LOGI("in lo_main, argc=%d, argv[0]=%s, argv[1]=%s", argc, argv[0], argv[1]); \ sal_detail_initialize(argc, argv); \ sal_main(); \ sal_detail_deinitialize(); \ diff --git a/sal/osl/android/AndroidManifest.xml b/sal/osl/android/AndroidManifest.xml index c88a9d11576c..159bbf105d75 100644 --- a/sal/osl/android/AndroidManifest.xml +++ b/sal/osl/android/AndroidManifest.xml @@ -4,10 +4,17 @@ android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="9" /> + <!-- This .apk uses a subclass of NativeActivity, so it *has* Java code. --> + <!-- android:hasCode="false" --> <application android:label="@string/app_name" android:debuggable="true"> + <!-- <activity android:name="android.app.NativeActivity" --> <activity android:name=".Bootstrap" - android:label="@string/app_name"> + android:label="LibreOffice Bootstrap" + android:configChanges="orientation|keyboardHidden"> + <!-- Tell NativeActivity the name of our .so --> + <meta-data android:name="android.app.lib_name" + android:value="lo-bootstrap" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/sal/osl/android/Makefile b/sal/osl/android/Makefile index 8ee14b8bf2d9..4e89f669ecd7 100644 --- a/sal/osl/android/Makefile +++ b/sal/osl/android/Makefile @@ -15,4 +15,4 @@ run: all unset JAVA_HOME && ant debug install # # Note: this is of course just an example - adb shell am start -a org.libreoffice.android -n org.libreoffice.android/.Bootstrap -e lo-main-library libcppunittester -e lo-main-cmdline "cppunittester /data/data/org.libreoffice.android/lib/libqa_sal_types.so" + adb shell am start -n org.libreoffice.android/.Bootstrap -e lo-main-library libcppunittester -e lo-main-cmdline "cppunittester /data/data/org.libreoffice.android/lib/libqa_sal_types.so" diff --git a/sal/osl/android/jni/Android.mk b/sal/osl/android/jni/Android.mk index ea6845d4b443..dbb014a63dc9 100644 --- a/sal/osl/android/jni/Android.mk +++ b/sal/osl/android/jni/Android.mk @@ -18,6 +18,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := lo-bootstrap LOCAL_SRC_FILES := lo-bootstrap.c -LOCAL_LDLIBS := -llog +LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) diff --git a/sal/osl/android/jni/android_native_app_glue.c b/sal/osl/android/jni/android_native_app_glue.c new file mode 100644 index 000000000000..8ff9f69d9946 --- /dev/null +++ b/sal/osl/android/jni/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/osl/android/jni/android_native_app_glue.h b/sal/osl/android/jni/android_native_app_glue.h new file mode 100644 index 000000000000..1b8c1f10725d --- /dev/null +++ b/sal/osl/android/jni/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/osl/android/jni/lo-bootstrap.c b/sal/osl/android/jni/lo-bootstrap.c index 56b72fef5f3e..327d868a3ccf 100644 --- a/sal/osl/android/jni/lo-bootstrap.c +++ b/sal/osl/android/jni/lo-bootstrap.c @@ -28,19 +28,61 @@ */ #include <string.h> +#include <stdlib.h> +#include <sys/stat.h> + #include <unistd.h> #include <fcntl.h> #include <dlfcn.h> + #include <jni.h> + #include <linux/elf.h> + #include <android/log.h> +#include <../../../inc/osl/android-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__)) +struct engine { + int dummy; +}; + +static struct android_app *app; +static JNIEnv *jni_env; +static const char **library_locations; +static int (*lo_main)(int, const char **); +static int lo_main_argc; +static const char **lo_main_argv; + +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(JNIEnv *env, - int fd, +read_section(int fd, Elf32_Shdr *shdr) { char *result; @@ -60,15 +102,230 @@ read_section(JNIEnv *env, return result; } +static void +free_ptrarray(void **pa) +{ + void **rover = pa; + + while (*rover != NULL) + free(*rover++); + + free(pa); +} + +static void +setup_library_locations(const char *lib_dir) +{ + int n; + char *ld_library_path; + char *elem; + + ld_library_path = getenv("LD_LIBRARY_PATH"); + if (ld_library_path == NULL) + ld_library_path = "/vendor/lib:/system/lib"; + ld_library_path = strdup(ld_library_path); + + n = 1; + elem = ld_library_path; + while ((elem = strchr(elem, ':')) != NULL) { + n++; + elem++; + } + library_locations = malloc((n + 2) * sizeof(char *)); + library_locations[0] = lib_dir; + elem = ld_library_path; + library_locations[1] = elem; + n = 2; + while ((elem = strchr(elem, ':')) != NULL) { + *elem = '\0'; + elem++; + library_locations[n++] = elem; + } + library_locations[n] = NULL; + + for (n = 0; library_locations[n] != NULL; n++) + LOGI("library_locations[%d] = %s", n, library_locations[n]); +} + jobjectArray Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, jobject clazz, jstring library) { - int i, fd; + 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) { + LOGI("Could not find the String class"); + free_ptrarray((void **) needed); + return NULL; + } + + result = (*env)->NewObjectArray(env, n_needed, String, NULL); + if (result == NULL) { + LOGI("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) { + LOGI("Could not find String[] class"); + return 0; + } + + if ((*env)->IsInstanceOf(env, argument, StringArray)) { + LOGI("Yes, a string array argument of length %d", (*env)->GetArrayLength(env, argument)); + 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; +} + +jboolean +Java_org_libreoffice_android_Bootstrap_setup__Ljava_lang_String_2(JNIEnv* env, + jobject this, + jstring dataDir) +{ + int i; + const jbyte *dataDirPath; + char *lib_dir; + + LOGI("in %s! this=%p", __FUNCTION__, this); + + 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); + + setup_library_locations(lib_dir); + + return JNI_TRUE; +} + +jboolean +Java_org_libreoffice_android_Bootstrap_setup__ILjava_lang_Object_2(JNIEnv* env, + jobject this, + void *lo_main_ptr, + jobject lo_main_argument) +{ + jclass StringArray; + int i; + + LOGI("in %s! this=%p", __FUNCTION__, this); + + lo_main = lo_main_ptr; + + StringArray = (*env)->FindClass(env, "[Ljava/lang/String;"); + if (StringArray == NULL) { + LOGI("Could not find String[] class"); + return JNI_FALSE; + } + + if (!(*env)->IsInstanceOf(env, lo_main_argument, StringArray)) { + LOGI("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; + + return JNI_TRUE; +} + +char ** +lo_dlneeds(const char *library) +{ + int i, fd; + int n_needed; + char **result; char *shstrtab, *dynstr; Elf32_Ehdr hdr; Elf32_Shdr shdr; @@ -76,13 +333,8 @@ Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, /* Open library and read ELF header */ - libName = (*env)->GetStringUTFChars(env, library, NULL); - - LOGI("dlneeds(%s)\n", libName); - - fd = open (libName, O_RDONLY); - - (*env)->ReleaseStringUTFChars(env, library, libName); + LOGI("lo_dlneeds(%s)\n", library); + fd = open(library, O_RDONLY); if (fd == -1) { LOGI("Could not open library"); @@ -90,44 +342,44 @@ Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, } if (read(fd, &hdr, sizeof(hdr)) < sizeof(hdr)) { - close(fd); LOGI("Could not read ELF header"); + close(fd); return NULL; } /* Read in .shstrtab */ if (lseek(fd, hdr.e_shoff + hdr.e_shstrndx * sizeof(shdr), SEEK_SET) < 0) { - close(fd); LOGI("Could not seek to .shstrtab section header"); + close(fd); return NULL; } if (read(fd, &shdr, sizeof(shdr)) < sizeof(shdr)) { - close(fd); LOGI("Could not read section header"); + close(fd); return NULL; } - shstrtab = read_section(env, fd, &shdr); + 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) { - close(fd); LOGI("Could not seek to section headers"); + close(fd); return NULL; } for (i = 0; i < hdr.e_shnum; i++) { if (read(fd, &shdr, sizeof(shdr)) < sizeof(shdr)) { - close(fd); LOGI("Could not read section header"); + close(fd); return NULL; } if (shdr.sh_type == SHT_STRTAB && strcmp(shstrtab + shdr.sh_name, ".dynstr") == 0) { - dynstr = read_section(env, fd, &shdr); + dynstr = read_section(fd, &shdr); if (dynstr == NULL) { free(shstrtab); return NULL; @@ -137,40 +389,39 @@ Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, } if (i == hdr.e_shnum) { - close(fd); LOGI("No .dynstr section"); + close(fd); return NULL; } /* Read section headers, looking for .dynamic section */ if (lseek(fd, hdr.e_shoff, SEEK_SET) < 0) { - close(fd); LOGI("Could not seek to section headers"); + close(fd); return NULL; } for (i = 0; i < hdr.e_shnum; i++) { if (read(fd, &shdr, sizeof(shdr)) < sizeof(shdr)) { - close(fd); LOGI("Could not read section header"); + close(fd); return NULL; } if (shdr.sh_type == SHT_DYNAMIC) { int dynoff; int *libnames; - jclass String; /* Count number of DT_NEEDED entries */ n_needed = 0; if (lseek(fd, shdr.sh_offset, SEEK_SET) < 0) { - close(fd); LOGI("Could not seek to .dynamic section"); + close(fd); return NULL; } for (dynoff = 0; dynoff < shdr.sh_size; dynoff += sizeof(dyn)) { if (read(fd, &dyn, sizeof(dyn)) < sizeof(dyn)) { - close(fd); LOGI("Could not read .dynamic entry"); + close(fd); return NULL; } if (dyn.d_tag == DT_NEEDED) @@ -179,37 +430,25 @@ Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, LOGI("Found %d DT_NEEDED libs", n_needed); - /* Allocate return value */ - - String = (*env)->FindClass(env, "java/lang/String"); - if (String == NULL) { - close(fd); - LOGI("Could not find the String class"); - return NULL; - } - - result = (*env)->NewObjectArray(env, n_needed, String, NULL); - if (result == NULL) { - close (fd); - LOGI("Could not create the String array"); - return NULL; - } + result = malloc((n_needed+1) * sizeof(char *)); n_needed = 0; if (lseek(fd, shdr.sh_offset, SEEK_SET) < 0) { - close(fd); LOGI("Could not seek to .dynamic section"); + close(fd); + free(result); return NULL; } for (dynoff = 0; dynoff < shdr.sh_size; dynoff += sizeof(dyn)) { if (read(fd, &dyn, sizeof(dyn)) < sizeof(dyn)) { - close(fd); LOGI("Could not read .dynamic entry"); + close(fd); + free(result); return NULL; } if (dyn.d_tag == DT_NEEDED) { LOGI("needs: %s\n", dynstr + dyn.d_un.d_val); - (*env)->SetObjectArrayElement(env, result, n_needed, (*env)->NewStringUTF(env, dynstr + dyn.d_un.d_val)); + result[n_needed] = strdup(dynstr + dyn.d_un.d_val); n_needed++; } } @@ -217,6 +456,7 @@ Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, close(fd); free(dynstr); free(shstrtab); + result[n_needed] = NULL; return result; } } @@ -224,76 +464,170 @@ Java_org_libreoffice_android_Bootstrap_dlneeds(JNIEnv* env, return NULL; } -jint -Java_org_libreoffice_android_Bootstrap_dlopen(JNIEnv* env, - jobject clazz, - jstring library) +void * +lo_dlopen(const char *library) { - const jbyte *libName = (*env)->GetStringUTFChars(env, library, NULL); - void *p = dlopen (libName, RTLD_LOCAL); - LOGI("dlopen(%s) = %p", libName, p); - (*env)->ReleaseStringUTFChars(env, library, libName); - if (p == NULL) { - LOGI(dlerror()); - return 0; + /* + * 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 = 0; + + 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); + + 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); + } } - return (jint) p; + + if (!found) { + LOGI("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; + } + } + + p = dlopen(full_name, RTLD_LOCAL); + LOGI("dlopen(%s) = %p", full_name, p); + free(full_name); + if (p == NULL) + LOGI(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; } -jint -Java_org_libreoffice_android_Bootstrap_dlsym(JNIEnv* env, - jobject clazz, - jint handle, - jstring symbol) +void * +lo_dlsym(void *handle, + const char *symbol) { - const jbyte *symName = (*env)->GetStringUTFChars(env, symbol, NULL); - void *p = dlsym ((void *)handle, symName); - LOGI("dlsym(%p,%s) = %p", handle, symName, p); - (*env)->ReleaseStringUTFChars(env, symbol, symName); - if (p == NULL) { + void *p = dlsym(handle, symbol); + LOGI("dlsym(%p, %s) = %p", handle, symbol, p); + if (p == NULL) LOGI(dlerror()); - return 0; - } - return (jint) p; + return p; } -jint -Java_org_libreoffice_android_Bootstrap_dlcall(JNIEnv* env, - jobject clazz, - jint function, - jobject argument) +int +lo_dlcall_argc_argv(void *function, + int argc, + const char **argv) { - jclass StringArray = (*env)->FindClass(env, "[Ljava/lang/String;"); + int (*fp)(int, const char **) = function; + int result = fp(argc, argv); - if (StringArray == NULL) { - LOGI("Could not find String[] class"); - return 0; - } + return result; +} - if ((*env)->IsInstanceOf(env, argument, StringArray)) { - LOGI("Yes, a string array argument of length %d", (*env)->GetArrayLength(env, argument)); - 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; +void android_main(struct android_app* state) +{ + struct engine engine; - int (*fp)(int, const char **) = function; + LOGI("here we are %s:%d", __FUNCTION__, __LINE__); - result = fp(argc, argv); + app = state; - for (i = 0; i < argc; i++) - (*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, argument, i), argv[i]); + memset(&engine, 0, sizeof(engine)); + state->userData = &engine; + state->onAppCmd = engine_handle_cmd; - free(argv); - return result; - } + char cwd[1000]; + getcwd(cwd, sizeof(cwd)); + LOGI("cwd=%s", cwd); - /* To be implemented */ - return 0; + // lo_main(lo_main_argc, lo_main_argv); + + // loop waiting for stuff to do. + + while (1) { + // Read all pending events. + int ident; + int events; + struct android_poll_source* source; + + while ((ident=ALooper_pollAll(-1, NULL, &events, + (void**)&source)) >= 0) { + LOGI("got an event ident=%d", ident); + + // Process this event. + if (source != NULL) { + source->process(state, source); + } + + // Check if we are exiting. + if (state->destroyRequested != 0) { + return; + } + } + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/android/project.properties b/sal/osl/android/project.properties index 5a7094538fde..730e911f2f9e 100644 --- a/sal/osl/android/project.properties +++ b/sal/osl/android/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-7 +target=android-14 diff --git a/sal/osl/android/src/org/libreoffice/android/Bootstrap.java b/sal/osl/android/src/org/libreoffice/android/Bootstrap.java index 5ba969ad41e3..fe65e0a8e875 100644 --- a/sal/osl/android/src/org/libreoffice/android/Bootstrap.java +++ b/sal/osl/android/src/org/libreoffice/android/Bootstrap.java @@ -28,96 +28,43 @@ package org.libreoffice.android; -import android.app.Activity; +import android.app.NativeActivity; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Log; -import java.io.File; -import java.util.HashMap; +// We override NativeActivity so that we can get at the intent of the +// activity and its extra parameters, that we use to tell us what +// actual LibreOffice "program" to run. I.e. something that on desktop +// OSes would be a program, but for Androis is actually built as a +// shared object, with an "lo_main" function. -public class Bootstrap extends Activity +public class Bootstrap extends NativeActivity { private static String TAG = "lo-bootstrap"; - private String dataDir; - // A native method to list the DT_NEEDED names in a ELF shared object - public static native String[] dlneeds(String library); + public native boolean setup(String dataDir); - // A native method to call dlopen(library, RTLD_LOCAL) + public native boolean setup(int lo_main_ptr, + Object lo_main_argument); + + // This is not just a wrapper for the C library dlopen(), but also + // loads recursively dependent libraries. public static native int dlopen(String library); - // A native method to call dlsym(handle, symbol) + // This is just a wrapper for the C library dlsym(). public static native int dlsym(int handle, String symbol); - // A native method to call (*function)(argument) - public static native int dlcall(int function, Object argument); - - // Already loaded libraries - private HashMap<String, Integer> presentLibs = new HashMap<String, Integer>(); - - private int loadLibrary(String 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 infurtating 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. - - Integer handle; - - if ((handle = presentLibs.get(library)) != null) - return handle; - - String fullName = null; - boolean found = false; - String[] libraryLocations = { dataDir + "/lib/", "/system/lib/" }; - for (String dir : libraryLocations ) { - fullName = dir + library; - if (new File(fullName).exists()) { - found = true; - break; - } - } - if (!found) { - Log.i(TAG, String.format("Library not found: %s\n", library)); - return 0; - } - - String[] needs = dlneeds(fullName); - if (needs == null) - return 0; - - for (String neededLibrary : needs) { - if (loadLibrary(neededLibrary) == 0) - return 0; - } - if ((handle = dlopen(fullName)) == 0) - return 0; - - presentLibs.put(library, handle); - return handle; - } - - /** Called when the activity is first created. */ @Override - public void onCreate(Bundle savedInstanceState) + protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + String dataDir = null; try { ApplicationInfo ai = this.getPackageManager().getApplicationInfo ("org.libreoffice.android", PackageManager.GET_META_DATA); - Log.i(TAG, String.format("sourceDir=%s\n", ai.sourceDir)); dataDir = ai.dataDir; Log.i(TAG, String.format("dataDir=%s\n", dataDir)); } @@ -125,39 +72,62 @@ public class Bootstrap extends Activity return; } + // This inspects LD_LIBRARY_PATH and dataDir + if (!setup(dataDir)) + return; + String mainLibrary = getIntent().getStringExtra("lo-main-library"); if (mainLibrary == null) mainLibrary = "libcppunittester"; - if (mainLibrary != null) { - int loLib = loadLibrary(mainLibrary + ".so"); + mainLibrary += ".so"; + + Log.i(TAG, String.format("mainLibrary=%s", mainLibrary)); + + // Get "command line" to pass to the LO "program" + String cmdLine = getIntent().getStringExtra("lo-main-cmdline"); + + if (cmdLine == null) + cmdLine = "cppunittester /data/data/org.libreoffice.android/lib/libqa_sal_types.so"; - if (loLib == 0) - return; + Log.i(TAG, String.format("cmdLine=%s", cmdLine)); - // Get "command line" to pass to the LO "program" - String cmdLine = getIntent().getStringExtra("lo-main-cmdline"); + String[] argv = cmdLine.split(" "); - if (cmdLine == null) - cmdLine = "cppunittester /data/data/org.libreoffice.android/lib/libqa_rtl_strings.so"; + // Load the LO "program" here and look up lo_main + int loLib = dlopen(mainLibrary); - String[] argv; - if (cmdLine != null) - argv = cmdLine.split(" "); - else - argv = new String[0]; - int loLibMain = dlsym(loLib, "lo_main"); - if (loLibMain != 0) - dlcall(loLibMain, argv); + if (loLib == 0) { + Log.i(TAG, String.format("Could not load %s", mainLibrary)); + return; } + + int lo_main = dlsym(loLib, "lo_main"); + if (lo_main == 0) { + Log.i(TAG, String.format("No lo_main in %s", mainLibrary)); + return; + } + + // This saves lo_main and argv + if (!setup(lo_main, argv)) + return; + + // Finally, call our super-class, NativeActivity's onCreate(), + // which eventually calls the ANativeActivity_onCreate() in + // android_native_app_glue.c, which starts a thread in which + // android_main() from lo-bootstrap.c is called. + + // android_main() calls the lo_main() defined in sal/main.h + // through the function pointer passed to setup() above, with + // the argc and argv also saved from the setup() call. + super.onCreate(savedInstanceState); } - /* This is used to load the 'lo-bootstrap' library on application - * startup. The library has already been unpacked into - * /data/data/<app name>/lib/liblo-bootstrap.so at - * installation time by the package manager. - */ + // This is used to load the 'lo-bootstrap' library on application + // startup. The library has already been unpacked into + // /data/data/<app name>/lib/liblo-bootstrap.so at installation + // time by the package manager. static { System.loadLibrary("lo-bootstrap"); } |