diff options
-rw-r--r-- | android/Bootstrap/src/org/libreoffice/android/Bootstrap.java | 107 | ||||
-rw-r--r-- | desktop/Library_sofficeapp.mk | 9 | ||||
-rw-r--r-- | sal/Library_sal.mk | 2 | ||||
-rw-r--r-- | sal/Module_sal.mk | 8 | ||||
-rw-r--r-- | sal/Package_inc.mk | 1 | ||||
-rw-r--r-- | sal/android/android_native_app_glue.c | 459 | ||||
-rw-r--r-- | sal/android/lo-bootstrap.c | 124 | ||||
-rw-r--r-- | sal/inc/osl/detail/android-bootstrap.h | 2 | ||||
-rw-r--r-- | sal/inc/osl/detail/android_native_app_glue.h | 351 | ||||
-rw-r--r-- | sal/inc/sal/main.h | 20 | ||||
-rw-r--r-- | sal/osl/unx/process_impl.cxx | 24 | ||||
-rw-r--r-- | vcl/Module_vcl.mk | 5 | ||||
-rw-r--r-- | vcl/android/androidinst.cxx | 702 | ||||
-rw-r--r-- | vcl/inc/android/androidinst.hxx | 14 | ||||
-rw-r--r-- | vcl/source/window/dialog.cxx | 13 |
15 files changed, 1790 insertions, 51 deletions
diff --git a/android/Bootstrap/src/org/libreoffice/android/Bootstrap.java b/android/Bootstrap/src/org/libreoffice/android/Bootstrap.java index aaa6e5fabd74..3db58446e740 100644 --- a/android/Bootstrap/src/org/libreoffice/android/Bootstrap.java +++ b/android/Bootstrap/src/org/libreoffice/android/Bootstrap.java @@ -29,15 +29,27 @@ 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 fi.iki.tml.CommandLine; + import java.io.File; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Scanner; + +// We extend 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 Android is actually built as a +// shared object, with a "lo_main" function. -public class Bootstrap +public class Bootstrap extends NativeActivity { private static String TAG = "lo-bootstrap"; @@ -50,6 +62,9 @@ public class Bootstrap String cacheDir, String apkFile); + public static native boolean setup(Object lo_main_argument, + int lo_main_delay); + // Extracts files in the .apk that need to be extraced into the app's tree static native void extract_files(); @@ -95,8 +110,8 @@ public class Bootstrap static boolean setup_done = false; - // This setup() method should be called from the upper Java level of - // LO-based apps. + // This setup() method is called 1) in apps that use *this* class as their activity from onCreate(), + // and 2) should be called from other kinds of LO code using apps. public static synchronized void setup(Activity activity) { if (setup_done) @@ -137,6 +152,92 @@ public class Bootstrap putenv("TMPDIR=" + activity.getCacheDir().getAbsolutePath()); } + @Override + protected void onCreate(Bundle savedInstanceState) + { + setup(this); + + String mainLibrary = getIntent().getStringExtra("lo-main-library"); + + if (mainLibrary == null) + mainLibrary = "libcppunittester"; + + 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) { + String indirectFile = getIntent().getStringExtra("lo-main-indirect-cmdline"); + if (indirectFile != null) { + try { + // Somewhat stupid but short way to read a file into a string + cmdLine = new Scanner(new File(indirectFile), "UTF-8").useDelimiter("\\A").next().trim(); + } + catch (java.io.FileNotFoundException e) { + Log.i(TAG, String.format("Could not read %s: %s",indirectFile, e.toString())); + } + } + + if (cmdLine == null) + cmdLine = ""; + } + + Log.i(TAG, String.format("cmdLine=%s", cmdLine)); + + String[] argv = CommandLine.split(cmdLine); + + // Handle env var assignments in the command line. + while (argv.length > 0 && + argv[0].matches("[A-Z_]+=.*")) { + putenv(argv[0]); + argv = Arrays.copyOfRange(argv, 1, argv.length); + } + + // argv[0] will be replaced by android_main() in lo-bootstrap.c by the + // pathname of the mainLibrary. + String[] newargv = new String[argv.length + 1]; + newargv[0] = "dummy-program-name"; + System.arraycopy(argv, 0, newargv, 1, argv.length); + argv = newargv; + + // Load the LO "program" here + System.loadLibrary(mainLibrary); + + // Start a strace on ourself if requested. + + // Note that the started strace will have its stdout and + // stderr connected to /dev/null, so you definitely want to + // specify an -o option in the lo-strace extra. Also, strace + // will trace only *this* thread, which is not the one that + // eventually will run android_main() and lo_main(), so you + // also want the -f option. + String strace_args = getIntent().getStringExtra("lo-strace"); + if (strace_args != null) + system("/system/xbin/strace -p " + getpid() + " " + (strace_args != "yes" ? strace_args : "" ) + " &"); + + int delay = 0; + String sdelay = getIntent().getStringExtra("lo-main-delay"); + if (sdelay != null) + delay = Integer.parseInt(sdelay); + + // Tell lo-bootstrap.c the stuff it needs to know + if (!setup(argv, delay)) + 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); + } + // Now with static loading we always have all native code in one native // library which we always call liblo-native-code.so, regardless of the // app. The library has already been unpacked into /data/data/<app diff --git a/desktop/Library_sofficeapp.mk b/desktop/Library_sofficeapp.mk index 2a635b5953c9..4687b5d05f41 100644 --- a/desktop/Library_sofficeapp.mk +++ b/desktop/Library_sofficeapp.mk @@ -94,4 +94,13 @@ ifeq ($(ENABLE_TELEPATHY),TRUE) $(eval $(call gb_Library_use_libraries,sofficeapp,tubes)) endif +# +# We need the lo_main symbol for our boostrap loader +# +ifeq ($(OS),ANDROID) +$(eval $(call gb_Library_add_cobjects,sofficeapp,\ + desktop/source/app/main \ +)) +endif + # vim: set ts=4 sw=4 et: diff --git a/sal/Library_sal.mk b/sal/Library_sal.mk index 533fb6c2c777..0bd28fae2f00 100644 --- a/sal/Library_sal.mk +++ b/sal/Library_sal.mk @@ -181,7 +181,7 @@ $(eval $(call gb_Library_add_exception_objects,sal,\ sal/osl/unx/module \ sal/osl/unx/process \ sal/osl/unx/process_impl \ - $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \ + sal/osl/unx/salinit \ sal/osl/unx/uunxapi \ )) $(eval $(call gb_Library_add_cobjects,sal,\ diff --git a/sal/Module_sal.mk b/sal/Module_sal.mk index 7d20fa388992..af97e4805703 100644 --- a/sal/Module_sal.mk +++ b/sal/Module_sal.mk @@ -31,8 +31,7 @@ $(eval $(call gb_Module_Module,sal)) $(eval $(call gb_Module_add_targets,sal,\ CustomTarget_generated \ CustomTarget_sal_allheaders \ - $(if $(filter DESKTOP,$(BUILD_TYPE)), \ - Executable_cppunittester) \ + Executable_cppunittester \ $(if $(filter $(OS),ANDROID), \ Library_lo-bootstrap) \ Library_sal \ @@ -46,14 +45,13 @@ $(eval $(call gb_Module_add_targets,sal,\ )) ifneq (,$(filter DESKTOP,$(BUILD_TYPE))) - $(eval $(call gb_Module_add_targets,sal,\ Executable_osl_process_child \ )) - $(eval $(call gb_Module_add_check_targets,sal,\ CppunitTest_sal_osl_process \ )) +endif $(eval $(call gb_Module_add_check_targets,sal,\ $(if $(filter TRUE,$(DISABLE_DYNLOADING)),,CppunitTest_Module_DLL) \ @@ -90,8 +88,6 @@ $(eval $(call gb_Module_add_check_targets,sal,\ CppunitTest_sal_rtl_math \ )) -endif - # CppunitTest_sal_osl_pipe has circular dependency on unotest # $(eval $(call gb_Module_add_subsequentcheck_targets,sal,\ CppunitTest_sal_osl_pipe \ diff --git a/sal/Package_inc.mk b/sal/Package_inc.mk index 15fb47717d35..0b78afdff514 100644 --- a/sal/Package_inc.mk +++ b/sal/Package_inc.mk @@ -61,6 +61,7 @@ $(eval $(call gb_Package_add_file,sal_inc,inc/osl/thread.hxx,osl/thread.hxx)) $(eval $(call gb_Package_add_file,sal_inc,inc/osl/time.h,osl/time.h)) $(eval $(call gb_Package_add_file,sal_inc,inc/osl/util.h,osl/util.h)) $(eval $(call gb_Package_add_file,sal_inc,inc/osl/detail/file.h,osl/detail/file.h)) +$(eval $(call gb_Package_add_file,sal_inc,inc/osl/detail/android_native_app_glue.h,osl/detail/android_native_app_glue.h)) $(eval $(call gb_Package_add_file,sal_inc,inc/osl/detail/android-bootstrap.h,osl/detail/android-bootstrap.h)) $(eval $(call gb_Package_add_file,sal_inc,inc/osl/detail/ios-bootstrap.h,osl/detail/ios-bootstrap.h)) $(eval $(call gb_Package_add_file,sal_inc,inc/osl/detail/component-mapping.h,osl/detail/component-mapping.h)) diff --git a/sal/android/android_native_app_glue.c b/sal/android/android_native_app_glue.c new file mode 100644 index 000000000000..0c2dd5e92f39 --- /dev/null +++ b/sal/android/android_native_app_glue.c @@ -0,0 +1,459 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 + */ + +#include <jni.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/resource.h> + +#include "osl/detail/android-bootstrap.h" +#include "osl/detail/android_native_app_glue.h" +#include <android/log.h> + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "lo-bootstrap", __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_WINDOW_REDRAW_NEEDED: + LOGI("APP_CMD_WINDOW_REDRAW_NEEDED - post\n"); + pthread_mutex_lock(&android_app->mutex); + 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_window_redraw_needed(struct android_app* android_app, ANativeWindow* window) { + pthread_mutex_lock(&android_app->mutex); + if (window != NULL) { + android_app_write_cmd(android_app, APP_CMD_WINDOW_REDRAW_NEEDED); + } + 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 onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window) { + LOGI("onNativeWindowRedrawNeeded: %p -- %p\n", activity, window); + android_app_window_redraw_needed((struct android_app*)activity->instance, window); +} + +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); +} + +__attribute__ ((visibility("default"))) 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->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; + activity->callbacks->onInputQueueCreated = onInputQueueCreated; + activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; + + activity->instance = android_app_create(activity, savedState, savedStateSize); +} diff --git a/sal/android/lo-bootstrap.c b/sal/android/lo-bootstrap.c index 61f2c7671977..9aeb32be4b21 100644 --- a/sal/android/lo-bootstrap.c +++ b/sal/android/lo-bootstrap.c @@ -30,22 +30,19 @@ * instead of those above. */ -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> #include <string.h> +#include <stdlib.h> +#include <stdio.h> #include <sys/stat.h> #include <sys/time.h> -#include <time.h> -#include <dlfcn.h> +#include <unistd.h> #include <fcntl.h> -#include <pthread.h> +#include <dlfcn.h> #include <sys/mman.h> -#include <unistd.h> -#include <jni.h> #include <zlib.h> +#include <jni.h> #include <android/log.h> @@ -53,6 +50,12 @@ #include "osl/detail/android-bootstrap.h" +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" + +#include "android_native_app_glue.c" + +#pragma GCC diagnostic warning "-Wdeclaration-after-statement" + #undef LOGI #undef LOGW @@ -69,6 +72,15 @@ struct engine { int dummy; }; +/* These vars are valid / used only when this library is used from + * NativeActivity-based apps. + */ +static struct android_app *app; +static int (*lo_main)(int, const char **); +static int lo_main_argc; +static const char **lo_main_argv; +static int sleep_time = 0; + /* These are valid / used in all apps. */ static const char *data_dir; static const char *cache_dir; @@ -266,6 +278,26 @@ setup_assets_tree(void) return 1; } +static void +engine_handle_cmd(struct android_app* state, + int32_t cmd) +{ + (void) state; + + 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; + } +} + /* The lo-native-code shared library is always loaded from Java, so this is * always called by JNI first. */ @@ -372,6 +404,7 @@ get_jni_string_array(JNIEnv *env, const char *s = (*env)->GetStringUTFChars(env, (*env)->GetObjectArrayElement(env, strv, i), NULL); (*argv)[i] = strdup(s); (*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, strv, i), s); + /* LOGI("argv[%d] = %s", i, lo_main_argv[i]); */ } (*argv)[*argc] = NULL; @@ -379,6 +412,26 @@ get_jni_string_array(JNIEnv *env, } +// public static native boolean setup(Object lo_main_argument, +// int lo_main_delay); + +__attribute__ ((visibility("default"))) +jboolean +Java_org_libreoffice_android_Bootstrap_setup__Ljava_lang_Object_2I(JNIEnv* env, + jobject clazz, + jobject lo_main_argument, + jint lo_main_delay) +{ + (void) clazz; + + if (!get_jni_string_array(env, "setup: lo_main_argument", lo_main_argument, &lo_main_argc, &lo_main_argv)) + return JNI_FALSE; + + sleep_time = lo_main_delay; + + return JNI_TRUE; +} + // public static native int getpid(); __attribute__ ((visibility("default"))) @@ -1257,4 +1310,59 @@ lo_get_app_data_dir(void) return data_dir; } +__attribute__ ((visibility("default"))) +struct android_app * +lo_get_app(void) +{ + return app; +} + +/* Note that android_main() is used only in NativeActivity-based apps. Only + * the android/qa/sc unit test app is such, and it is unclear whether there is + * any reason to continue maintaining that buildable. + */ +__attribute__ ((visibility("default"))) +void +android_main(struct android_app* state) +{ + jint nRet; + JNIEnv *pEnv = NULL; + struct engine engine; + Dl_info lo_main_info; + JavaVMAttachArgs aArgs = { + JNI_VERSION_1_2, + "LibreOfficeThread", + NULL + }; + + fprintf (stderr, "android_main in thread: %d\n", (int)pthread_self()); + + if (sleep_time != 0) { + LOGI("android_main: Sleeping for %d seconds, start ndk-gdb NOW if that is your intention", sleep_time); + sleep(sleep_time); + } + + nRet = (*(*state->activity->vm)->AttachCurrentThreadAsDaemon)(state->activity->vm, &pEnv, &aArgs); + fprintf (stderr, "attach thread returned %d %p\n", nRet, pEnv); + + app = state; + + memset(&engine, 0, sizeof(engine)); + state->userData = &engine; + state->onAppCmd = engine_handle_cmd; + + /* Look up lo_main() dynamically even if it is in the same .so as this code, + * but that is only in the case for code built to be used in a NativeActivity-based app. + */ + lo_main = dlsym(RTLD_DEFAULT, "lo_main"); + + if (dladdr(lo_main, &lo_main_info) != 0) { + lo_main_argv[0] = lo_main_info.dli_fname; + } + + lo_main(lo_main_argc, lo_main_argv); + nRet = (*(*state->activity->vm)->DetachCurrentThread)(state->activity->vm); + fprintf (stderr, "exit android_main\n"); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/inc/osl/detail/android-bootstrap.h b/sal/inc/osl/detail/android-bootstrap.h index 672536f25f35..d1022f90637e 100644 --- a/sal/inc/osl/detail/android-bootstrap.h +++ b/sal/inc/osl/detail/android-bootstrap.h @@ -62,6 +62,8 @@ JavaVM *lo_get_javavm(void); const char *lo_get_app_data_dir(void); +struct android_app *lo_get_app(void); + #ifdef __cplusplus } #endif diff --git a/sal/inc/osl/detail/android_native_app_glue.h b/sal/inc/osl/detail/android_native_app_glue.h new file mode 100644 index 000000000000..862ca9e990f8 --- /dev/null +++ b/sal/inc/osl/detail/android_native_app_glue.h @@ -0,0 +1,351 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 + */ + +#ifndef _ANDROID_NATIVE_APP_GLUE_H +#define _ANDROID_NATIVE_APP_GLUE_H +#if defined(ANDROID) + +#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 +#endif /* _ANDROID_NATIVE_APP_GLUE_H */ diff --git a/sal/inc/sal/main.h b/sal/inc/sal/main.h index 8648e38752ab..7cdc9bc62c0c 100644 --- a/sal/inc/sal/main.h +++ b/sal/inc/sal/main.h @@ -38,7 +38,25 @@ SAL_DLLPUBLIC void SAL_CALL sal_detail_deinitialize(); #if defined IOS || defined ANDROID -#error No code that includes this should be built for iOS or Android +#ifdef __cplusplus +extern "C" SAL_DLLPUBLIC_EXPORT void lo_main(int argc, char **argv); +#endif + +#define SAL_MAIN_WITH_ARGS_IMPL \ +SAL_DLLPUBLIC_EXPORT void lo_main(int argc, char **argv) \ +{ \ + sal_detail_initialize(argc, argv); \ + sal_main_with_args(argc, argv); \ + sal_detail_deinitialize(); \ +} + +#define SAL_MAIN_IMPL \ +SAL_DLLPUBLIC_EXPORT void lo_main(int argc, char **argv) \ +{ \ + sal_detail_initialize(argc, argv); \ + sal_main(); \ + sal_detail_deinitialize(); \ +} #else diff --git a/sal/osl/unx/process_impl.cxx b/sal/osl/unx/process_impl.cxx index 723d10bcd436..4a10af514557 100644 --- a/sal/osl/unx/process_impl.cxx +++ b/sal/osl/unx/process_impl.cxx @@ -104,10 +104,26 @@ oslProcessError SAL_CALL osl_bootstrap_getExecutableFile_Impl ( { oslProcessError result = osl_Process_E_NotFound; -#ifdef ANDROID - /* Now with just a single DSO, this one from lo-bootstrap.c is as good as - * any */ - void * addr = dlsym (RTLD_DEFAULT, "JNI_OnLoad"); +#if defined(ANDROID) && !defined(DISABLE_DYNLOADING) + /* On Android we in theory want the address of the "lo_main()" + * function, as that is what corresponds to "main()" in + * LibreOffice programs on normal desktop OSes. + * + * But that is true only for apps with a "native activity", using + * <sal/main.h> and the org.libreoffice.android.Bootstrap + * mechanism. For more normal (?) Android apps that just use + * LibreOffice libraries (components) where the main program is in + * Java, that just use LibreOffice libraries, there is no + * lo_main(). (Note that we don't know for sure yet how + * complicated it might be to write such Android apps...) + * + * Maybe best to just pick some function in liblo-bootstrap.so + * which also such Java apps *must* load as the very first + * LibreOffice native library. We store all LibreOffice native + * shared libraries an app uses in the same folder anyway, so it + * doesn't really matter. + */ + void * addr = (void *) &lo_dlopen; #else /* Determine address of "main()" function. */ void * addr = dlsym (RTLD_DEFAULT, "main"); diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk index 116e81544e11..f340800db0b8 100644 --- a/vcl/Module_vcl.mk +++ b/vcl/Module_vcl.mk @@ -24,9 +24,8 @@ $(eval $(call gb_Module_add_targets,vcl,\ CustomTarget_afm_hash \ Library_vcl \ Package_inc \ - $(if $(filter DESKTOP,$(BUILD_TYPE)), \ - StaticLibrary_vclmain \ - Executable_ui-previewer) \ + StaticLibrary_vclmain \ + Executable_ui-previewer \ UI_vcl \ )) diff --git a/vcl/android/androidinst.cxx b/vcl/android/androidinst.cxx index fc1c3f9a537b..916769e03517 100644 --- a/vcl/android/androidinst.cxx +++ b/vcl/android/androidinst.cxx @@ -31,7 +31,9 @@ #include <generic/gendata.hxx> #include <jni.h> #include <android/log.h> +#include <android/looper.h> #include <osl/detail/android-bootstrap.h> +#include <osl/detail/android_native_app_glue.h> #include <rtl/strbuf.hxx> #include <basebmp/scanlineformats.hxx> @@ -43,6 +45,531 @@ public: virtual bool ErrorTrapPop( bool ) { return false; } }; +static rtl::OString MotionEdgeFlagsToString(int32_t nFlags) +{ + rtl::OStringBuffer aStr; + if (nFlags == AMOTION_EVENT_EDGE_FLAG_NONE) + aStr.append ("no-edge"); + if (nFlags & AMOTION_EVENT_EDGE_FLAG_TOP) + aStr.append (":top"); + if (nFlags & AMOTION_EVENT_EDGE_FLAG_BOTTOM) + aStr.append (":bottom"); + if (nFlags & AMOTION_EVENT_EDGE_FLAG_LEFT) + aStr.append (":left"); + if (nFlags & AMOTION_EVENT_EDGE_FLAG_RIGHT) + aStr.append (":right"); + return aStr.makeStringAndClear(); +} + +static rtl::OString KeyMetaStateToString(int32_t nFlags) +{ + rtl::OStringBuffer aStr; + if (nFlags == AMETA_NONE) + aStr.append ("no-meta"); + if (nFlags & AMETA_ALT_ON) + aStr.append (":alt"); + if (nFlags & AMETA_SHIFT_ON) + aStr.append (":shift"); + if (nFlags & AMETA_SYM_ON) + aStr.append (":sym"); + return aStr.makeStringAndClear(); +} + +static sal_uInt16 KeyMetaStateToCode(AInputEvent *event) +{ + sal_uInt16 nCode = 0; + int32_t nFlags = AKeyEvent_getMetaState(event); + if (nFlags & AMETA_SHIFT_ON) + nCode |= KEY_SHIFT; + if (nFlags & AMETA_SYM_ON) + nCode |= KEY_MOD1; + if (nFlags & AMETA_ALT_ON) + nCode |= KEY_MOD2; + return nCode; +} + +static sal_uInt16 KeyToCode(AInputEvent *event) +{ + sal_uInt16 nCode = 0; + switch (AKeyEvent_getKeyCode(event)) { +#define MAP(a,b) \ + case AKEYCODE_##a: nCode = KEY_##b; break +#define MAP_SAME(a) MAP(a,a) + + MAP_SAME(HOME); + MAP_SAME(0); MAP_SAME(1); MAP_SAME(2); MAP_SAME(3); MAP_SAME(4); + MAP_SAME(5); MAP_SAME(6); MAP_SAME(7); MAP_SAME(8); MAP_SAME(9); + + MAP_SAME(A); MAP_SAME(B); MAP_SAME(C); MAP_SAME(D); + MAP_SAME(E); MAP_SAME(F); MAP_SAME(G); MAP_SAME(H); + MAP_SAME(I); MAP_SAME(J); MAP_SAME(K); MAP_SAME(L); + MAP_SAME(M); MAP_SAME(N); MAP_SAME(O); MAP_SAME(P); + MAP_SAME(Q); MAP_SAME(R); MAP_SAME(S); MAP_SAME(T); + MAP_SAME(U); MAP_SAME(V); MAP_SAME(W); MAP_SAME(X); + MAP_SAME(Y); MAP_SAME(Z); + + MAP_SAME(TAB); MAP_SAME(SPACE); MAP_SAME(COMMA); + + MAP(ENTER,RETURN); + MAP(PAGE_UP, PAGEUP); + MAP(PAGE_DOWN, PAGEDOWN); + MAP(DEL, DELETE); + MAP(PERIOD, POINT); + + MAP(DPAD_UP, UP); MAP(DPAD_DOWN, DOWN); + MAP(DPAD_LEFT, LEFT); MAP(DPAD_RIGHT, RIGHT); + + case AKEYCODE_BACK: // escape ? + case AKEYCODE_UNKNOWN: + case AKEYCODE_SOFT_LEFT: + case AKEYCODE_SOFT_RIGHT: + case AKEYCODE_CALL: + case AKEYCODE_ENDCALL: + case AKEYCODE_STAR: + case AKEYCODE_POUND: + case AKEYCODE_VOLUME_UP: + case AKEYCODE_VOLUME_DOWN: + case AKEYCODE_DPAD_CENTER: + case AKEYCODE_POWER: + case AKEYCODE_CAMERA: + case AKEYCODE_CLEAR: + case AKEYCODE_ALT_LEFT: + case AKEYCODE_ALT_RIGHT: + case AKEYCODE_SHIFT_LEFT: + case AKEYCODE_SHIFT_RIGHT: + case AKEYCODE_SYM: + case AKEYCODE_EXPLORER: + case AKEYCODE_ENVELOPE: + case AKEYCODE_GRAVE: + case AKEYCODE_MINUS: + case AKEYCODE_EQUALS: + case AKEYCODE_LEFT_BRACKET: + case AKEYCODE_RIGHT_BRACKET: + case AKEYCODE_BACKSLASH: + case AKEYCODE_SEMICOLON: + case AKEYCODE_APOSTROPHE: + case AKEYCODE_SLASH: + case AKEYCODE_AT: + case AKEYCODE_NUM: + case AKEYCODE_HEADSETHOOK: + case AKEYCODE_FOCUS: // not widget, but camera focus + case AKEYCODE_PLUS: + case AKEYCODE_MENU: + case AKEYCODE_NOTIFICATION: + case AKEYCODE_SEARCH: + case AKEYCODE_MEDIA_PLAY_PAUSE: + case AKEYCODE_MEDIA_STOP: + case AKEYCODE_MEDIA_NEXT: + case AKEYCODE_MEDIA_PREVIOUS: + case AKEYCODE_MEDIA_REWIND: + case AKEYCODE_MEDIA_FAST_FORWARD: + case AKEYCODE_MUTE: + case AKEYCODE_PICTSYMBOLS: + case AKEYCODE_SWITCH_CHARSET: + case AKEYCODE_BUTTON_A: + case AKEYCODE_BUTTON_B: + case AKEYCODE_BUTTON_C: + case AKEYCODE_BUTTON_X: + case AKEYCODE_BUTTON_Y: + case AKEYCODE_BUTTON_Z: + case AKEYCODE_BUTTON_L1: + case AKEYCODE_BUTTON_R1: + case AKEYCODE_BUTTON_L2: + case AKEYCODE_BUTTON_R2: + case AKEYCODE_BUTTON_THUMBL: + case AKEYCODE_BUTTON_THUMBR: + case AKEYCODE_BUTTON_START: + case AKEYCODE_BUTTON_SELECT: + case AKEYCODE_BUTTON_MODE: + fprintf (stderr, "un-mapped keycode %d\n", nCode); + nCode = 0; + break; +#undef MAP_SAME +#undef MAP + } + fprintf (stderr, "mapped %d -> %d\n", AKeyEvent_getKeyCode(event), nCode); + return nCode; +} + +static void BlitFrameRegionToWindow(ANativeWindow_Buffer *pOutBuffer, + const basebmp::BitmapDeviceSharedPtr& aDev, + const ARect &rSrcRect, + int nDestX, int nDestY) +{ + fprintf (stderr, "Blit frame src %d,%d->%d,%d to position %d, %d\n", + rSrcRect.left, rSrcRect.top, rSrcRect.right, rSrcRect.bottom, + nDestX, nDestY); + + basebmp::RawMemorySharedArray aSrcData = aDev->getBuffer(); + basegfx::B2IVector aDevSize = aDev->getSize(); + sal_Int32 nStride = aDev->getScanlineStride(); + unsigned char *pSrc = aSrcData.get(); + + // FIXME: do some cropping goodness on aSrcRect to ensure no overflows etc. + ARect aSrcRect = rSrcRect; + + // FIXME: by default we have WINDOW_FORMAT_RGB_565 = 4 ... + for (unsigned int y = 0; y < (unsigned int)(aSrcRect.bottom - aSrcRect.top); y++) + { + unsigned char *sp = ( pSrc + nStride * (aSrcRect.top + y) + + aSrcRect.left * 3 /* src pixel size */ ); + + switch (pOutBuffer->format) { + case WINDOW_FORMAT_RGBA_8888: + case WINDOW_FORMAT_RGBX_8888: + { + unsigned char *dp = ( (unsigned char *)pOutBuffer->bits + + pOutBuffer->stride * 4 * (y + nDestY) + + nDestX * 4 /* dest pixel size */ ); + for (unsigned int x = 0; x < (unsigned int)(aSrcRect.right - aSrcRect.left); x++) + { + dp[x*4 + 0] = sp[x*3 + 2]; // R + dp[x*4 + 1] = sp[x*3 + 1]; // G + dp[x*4 + 2] = sp[x*3 + 0]; // B + dp[x*4 + 3] = 255; // A + } + break; + } + case WINDOW_FORMAT_RGB_565: + { + unsigned char *dp = ( (unsigned char *)pOutBuffer->bits + + pOutBuffer->stride * 2 * (y + nDestY) + + nDestX * 2 /* dest pixel size */ ); + for (unsigned int x = 0; x < (unsigned int)(aSrcRect.right - aSrcRect.left); x++) + { + unsigned char b = sp[x*3 + 0]; // B + unsigned char g = sp[x*3 + 1]; // G + unsigned char r = sp[x*3 + 2]; // R + dp[x*2 + 0] = (r & 0xf8) | (g >> 5); + dp[x*2 + 1] = ((g & 0x1c) << 5) | ((b & 0xf8) >> 3); + } + break; + } + default: + fprintf (stderr, "unknown pixel format %d !\n", pOutBuffer->format); + break; + } + } +} + +void AndroidSalInstance::BlitFrameToWindow(ANativeWindow_Buffer *pOutBuffer, + const basebmp::BitmapDeviceSharedPtr& aDev) +{ + basegfx::B2IVector aDevSize = aDev->getSize(); + ARect aWhole = { 0, 0, aDevSize.getX(), aDevSize.getY() }; + BlitFrameRegionToWindow(pOutBuffer, aDev, aWhole, 0, 0); +} + +void AndroidSalInstance::RedrawWindows(ANativeWindow *pWindow) +{ + if (!pWindow) + return; + + ANativeWindow_Buffer aOutBuffer; + memset ((void *)&aOutBuffer, 0, sizeof (aOutBuffer)); + +// ARect aRect; + fprintf (stderr, "pre lock #3\n"); + int32_t nRet = ANativeWindow_lock(pWindow, &aOutBuffer, NULL); + fprintf (stderr, "locked window %d returned " // rect: %d,%d->%d,%d " + "buffer: %dx%d stride %d, format %d, bits %p\n", + nRet, // aRect.left, aRect.top, aRect.right, aRect.bottom, + aOutBuffer.width, aOutBuffer.height, aOutBuffer.stride, + aOutBuffer.format, aOutBuffer.bits); + if (aOutBuffer.bits != NULL) + { + +#if 0 // pre-'clean' the buffer with cruft: + // hard-code / guess at a format ... + int32_t *p = (int32_t *)aOutBuffer.bits; + for (int32_t y = 0; y < aOutBuffer.height; y++) + { + for (int32_t x = 0; x < aOutBuffer.stride; x++) + *p++ = (y << 24) + (x << 10) + 0xff ; + } + +#endif + int i = 0; + std::list< SalFrame* >::const_iterator it; + for ( it = getFrames().begin(); it != getFrames().end(); i++, it++ ) + { + SvpSalFrame *pFrame = static_cast<SvpSalFrame *>(*it); + + if (pFrame->IsVisible()) + { + fprintf( stderr, "render visible frame %d\n", i ); +#ifndef REGION_RE_RENDER + BlitFrameToWindow (&aOutBuffer, pFrame->getDevice()); +#else + // Sadly it seems that due to double buffering, we don't + // get back in our buffer what we had there last time - so we cannot + // do incremental rendering. Presumably this will require us to + // render to a bitmap, and keep that updated instead in future. + + // Intersect re-rendering region with this frame + Region aClipped( maRedrawRegion ); + basegfx::B2IVector aDevSize = pFrame->getDevice()->getSize(); + aClipped.Intersect( Rectangle( 0, 0, aDevSize.getX(), aDevSize.getY() ) ); + + Rectangle aSubRect; + RegionHandle aHdl = aClipped.BeginEnumRects(); + while( aClipped.GetNextEnumRect( aHdl, aSubRect ) ) + { + ARect aASubRect = { aSubRect.Left(), aSubRect.Top(), + aSubRect.Right(), aSubRect.Bottom() }; + BlitFrameRegionToWindow(&aOutBuffer, pFrame->getDevice(), + aASubRect, + aSubRect.Left(), aSubRect.Top()); + } + aClipped.EndEnumRects( aHdl ); +#endif + } + } + } + else + fprintf (stderr, "no buffer for locked window\n"); + ANativeWindow_unlockAndPost(pWindow); + + fprintf (stderr, "done render!\n"); + maRedrawRegion.SetEmpty(); + mbQueueReDraw = false; +} + +void AndroidSalInstance::damaged(AndroidSalFrame */* frame */, const Rectangle &rRect) +{ + // FIXME: translate rRect to the frame's offset ... + maRedrawRegion.Union( rRect ); + mbQueueReDraw = true; +} + +static const char *app_cmd_name(int cmd) +{ + switch (cmd) { + case APP_CMD_INPUT_CHANGED: return "INPUT_CHANGED"; + case APP_CMD_INIT_WINDOW: return "INIT_WINDOW"; + case APP_CMD_TERM_WINDOW: return "TERM_WINDOW"; + case APP_CMD_WINDOW_RESIZED: return "WINDOW_RESIZED"; + case APP_CMD_WINDOW_REDRAW_NEEDED: return "WINDOW_REDRAW_NEEDED"; + case APP_CMD_CONTENT_RECT_CHANGED: return "CONTENT_RECT_CHANGED"; + case APP_CMD_GAINED_FOCUS: return "GAINED_FOCUS"; + case APP_CMD_LOST_FOCUS: return "LOST_FOCUS"; + case APP_CMD_CONFIG_CHANGED: return "CONFIG_CHANGED"; + case APP_CMD_LOW_MEMORY: return "LOW_MEMORY"; + case APP_CMD_START: return "START"; + case APP_CMD_RESUME: return "RESUME"; + case APP_CMD_SAVE_STATE: return "SAVE_STATE"; + case APP_CMD_PAUSE: return "PAUSE"; + case APP_CMD_STOP: return "STOP"; + case APP_CMD_DESTROY: return "DESTROY"; + default: + static char buf[10]; + sprintf(buf, "%d", cmd); + return buf; + } +} + +void AndroidSalInstance::GetWorkArea( Rectangle& rRect ) +{ + if (!mpApp || !mpApp->window) + rRect = Rectangle( Point( 0, 0 ), + Size( 800, 600 ) ); + else + rRect = Rectangle( Point( 0, 0 ), + Size( ANativeWindow_getWidth( mpApp->window ), + ANativeWindow_getHeight( mpApp->window ) ) ); +} + +void AndroidSalInstance::onAppCmd (struct android_app* app, int32_t cmd) +{ + fprintf (stderr, "app cmd for app %p: %s\n", app, app_cmd_name(cmd)); + ANativeWindow *pWindow = mpApp->window; + switch (cmd) { + case APP_CMD_INIT_WINDOW: + { + ARect aRect = { 0, 0, 0, 0 }; + aRect.right = ANativeWindow_getWidth(pWindow); + aRect.bottom = ANativeWindow_getHeight(pWindow); + int nRet = ANativeWindow_setBuffersGeometry( + pWindow, ANativeWindow_getWidth(pWindow), + ANativeWindow_getHeight(pWindow), + WINDOW_FORMAT_RGBA_8888); + fprintf (stderr, "we have an app window ! %p %dx%x (%d) set %d\n", + pWindow, aRect.right, aRect.bottom, + ANativeWindow_getFormat(pWindow), nRet); + maRedrawRegion = Region( Rectangle( 0, 0, ANativeWindow_getWidth(pWindow), + ANativeWindow_getHeight(pWindow) ) ); + mbQueueReDraw = true; + break; + } + case APP_CMD_WINDOW_RESIZED: + { + ARect aRect = { 0, 0, 0, 0 }; + aRect.right = ANativeWindow_getWidth(pWindow); + aRect.bottom = ANativeWindow_getHeight(pWindow); + fprintf (stderr, "app window resized to ! %p %dx%x (%d)\n", + pWindow, aRect.right, aRect.bottom, + ANativeWindow_getFormat(pWindow)); + break; + } + + case APP_CMD_WINDOW_REDRAW_NEEDED: + { + fprintf (stderr, "redraw needed\n"); + maRedrawRegion = Region( Rectangle( 0, 0, ANativeWindow_getWidth(pWindow), + ANativeWindow_getHeight(pWindow) ) ); + mbQueueReDraw = true; + break; + } + + case APP_CMD_CONTENT_RECT_CHANGED: + { + ARect aRect = mpApp->contentRect; + fprintf (stderr, "content rect changed [ k/b popped up etc. ] %d,%d->%d,%d\n", + aRect.left, aRect.top, aRect.right, aRect.bottom); + break; + } + default: + fprintf (stderr, "unhandled app cmd %d\n", cmd); + break; + } +} + +/* + * Try too hard to get a frame, in the absence of anything better to do + */ +SalFrame *AndroidSalInstance::getFocusFrame() const +{ + SalFrame *pFocus = SvpSalFrame::GetFocusFrame(); + if (!pFocus) { + fprintf (stderr, "no focus frame, re-focusing first visible frame\n"); + const std::list< SalFrame* >& rFrames( getFrames() ); + for( std::list< SalFrame* >::const_iterator it = rFrames.begin(); it != rFrames.end(); ++it ) + { + SvpSalFrame *pFrame = const_cast<SvpSalFrame*>(static_cast<const SvpSalFrame*>(*it)); + if( pFrame->IsVisible() ) + { + pFrame->GetFocus(); + pFocus = pFrame; + break; + } + } + } + return pFocus; +} + +int32_t AndroidSalInstance::onInputEvent (struct android_app* app, AInputEvent* event) +{ + bool bHandled = false; + fprintf (stderr, "input event for app %p, event %p type %d source %d device id %d\n", + app, event, + AInputEvent_getType(event), + AInputEvent_getSource(event), + AInputEvent_getDeviceId(event)); + + switch (AInputEvent_getType(event)) + { + case AINPUT_EVENT_TYPE_KEY: + { + int32_t nAction = AKeyEvent_getAction(event); + fprintf (stderr, "key event keycode %d '%s' %s flags (0x%x) 0x%x\n", + AKeyEvent_getKeyCode(event), + nAction == AKEY_EVENT_ACTION_DOWN ? "down" : + nAction == AKEY_EVENT_ACTION_UP ? "up" : "multiple", + KeyMetaStateToString(AKeyEvent_getMetaState(event)).getStr(), + AKeyEvent_getMetaState (event), + AKeyEvent_getFlags (event)); + + // FIXME: the whole SALEVENT_KEYMODCHANGE stuff is going to be interesting + // can we really emit that ? no input method madness required though. + sal_uInt16 nEvent; + SalKeyEvent aEvent; + int64_t nNsTime = AKeyEvent_getEventTime(event); + + // FIXME: really we need a Java wrapper app as Mozilla has that does + // key event translation for us, and provides -much- cleaner events. + nEvent = (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP ? + SALEVENT_KEYUP : SALEVENT_KEYINPUT); + sal_uInt16 nCode = KeyToCode(event); + sal_uInt16 nMetaState = KeyMetaStateToCode(event); + if (nCode >= KEY_0 && nCode <= KEY_9) + aEvent.mnCharCode = '0' + nCode - KEY_0; + else if (nCode >= KEY_A && nCode <= KEY_Z) + aEvent.mnCharCode = (nMetaState & KEY_SHIFT ? 'A' : 'a') + nCode - KEY_A; + else if (nCode == KEY_SPACE) + aEvent.mnCharCode = ' '; + else if (nCode == KEY_COMMA) + aEvent.mnCharCode = ','; + else if (nCode == KEY_POINT) + aEvent.mnCharCode = '.'; + else + aEvent.mnCharCode = 0; + aEvent.mnTime = nNsTime / (1000 * 1000); + aEvent.mnCode = nMetaState | nCode; + aEvent.mnRepeat = AKeyEvent_getRepeatCount(event); + + SalFrame *pFocus = getFocusFrame(); + if (pFocus) + bHandled = pFocus->CallCallback( nEvent, &aEvent ); + else + fprintf (stderr, "no focused frame to emit event on\n"); + + fprintf( stderr, "bHandled == %s\n", bHandled? "true": "false" ); + break; + } + case AINPUT_EVENT_TYPE_MOTION: + { + size_t nPoints = AMotionEvent_getPointerCount(event); + fprintf (stderr, "motion event %d %g,%g %d points: %s\n", + AMotionEvent_getAction(event), + AMotionEvent_getXOffset(event), + AMotionEvent_getYOffset(event), + (int)nPoints, + MotionEdgeFlagsToString(AMotionEvent_getEdgeFlags(event)).getStr()); + for (size_t i = 0; i < nPoints; i++) + fprintf(stderr, "\t%d: %g,%g - pressure %g\n", + (int)i, + AMotionEvent_getX(event, i), + AMotionEvent_getY(event, i), + AMotionEvent_getPressure(event, i)); + + SalMouseEvent aMouseEvent; + sal_uInt16 nEvent = 0; + + // FIXME: all this filing the nEvent and aMouseEvent has to be cleaned up + nEvent = AMotionEvent_getAction(event)? SALEVENT_MOUSEBUTTONUP: SALEVENT_MOUSEBUTTONDOWN; + + if (nPoints > 0) + { + aMouseEvent.mnX = AMotionEvent_getX(event, 0); + aMouseEvent.mnY = AMotionEvent_getY(event, 0); + } else { + aMouseEvent.mnX = AMotionEvent_getXOffset(event); + aMouseEvent.mnY = AMotionEvent_getYOffset(event); + } + + int64_t nNsTime = AMotionEvent_getEventTime(event); + aMouseEvent.mnTime = nNsTime / (1000 * 1000); + aMouseEvent.mnCode = 0; // FIXME + aMouseEvent.mnButton = MOUSE_LEFT; // FIXME + + SalFrame *pFocus = getFocusFrame(); + if (pFocus) + bHandled = pFocus->CallCallback( nEvent, &aMouseEvent ); + else + fprintf (stderr, "no focused frame to emit event on\n"); + + fprintf( stderr, "bHandled == %s\n", bHandled? "true": "false" ); + + break; + } + default: + fprintf (stderr, "unknown input event type %p %d\n", + event, AInputEvent_getType(event)); + break; + } + return bHandled ? 1 : 0; +} + AndroidSalInstance *AndroidSalInstance::getInstance() { if (!ImplGetSVData()) @@ -53,11 +580,35 @@ AndroidSalInstance *AndroidSalInstance::getInstance() return static_cast<AndroidSalInstance *>(pData->m_pInstance); } +extern "C" { + void onAppCmd_cb (struct android_app *app, int32_t cmd) + { + AndroidSalInstance::getInstance()->onAppCmd(app, cmd); + } + + int32_t onInputEvent_cb (struct android_app *app, AInputEvent *event) + { + return AndroidSalInstance::getInstance()->onInputEvent(app, event); + } +} + AndroidSalInstance::AndroidSalInstance( SalYieldMutex *pMutex ) : SvpSalInstance( pMutex ) + , mpApp( NULL ) + , mbQueueReDraw( false ) { - fprintf (stderr, "created Android Sal Instance (thread: %d)\n", + mpApp = lo_get_app(); + fprintf (stderr, "created Android Sal Instance for app %p window %p thread: %d\n", + mpApp, + mpApp ? mpApp->window : NULL, (int)pthread_self()); + if (mpApp) + { + pthread_mutex_lock (&mpApp->mutex); + mpApp->onAppCmd = onAppCmd_cb; + mpApp->onInputEvent = onInputEvent_cb; + pthread_mutex_unlock (&mpApp->mutex); + } } AndroidSalInstance::~AndroidSalInstance() @@ -68,19 +619,55 @@ AndroidSalInstance::~AndroidSalInstance() void AndroidSalInstance::Wakeup() { fprintf (stderr, "Wakeup alooper\n"); - fprintf (stderr, "busted - no global looper\n"); + if (mpApp && mpApp->looper) + ALooper_wake (mpApp->looper); + else + fprintf (stderr, "busted - no global looper\n"); } -void AndroidSalInstance::DoReleaseYield (int /* nTimeoutMS */) +void AndroidSalInstance::DoReleaseYield (int nTimeoutMS) { - // Presumably this should never be called at all as we don't do - // NativeActivity-based apps any more? - static bool beenhere = false; - if (!beenhere) + // Presumably this should never be called at all except in + // NativeActivity-based apps with a GUI, like android/qa/desktop, where + // the message pump is run here in vcl? + if (!mpApp) { + static bool beenhere = false; + if (!beenhere) + { + fprintf (stderr, "**** Huh, %s called in non-NativeActivity app\n", __FUNCTION__); + beenhere = true; + } + return; + } + + // release yield mutex + sal_uLong nAcquireCount = ReleaseYieldMutex(); + + fprintf (stderr, "DoReleaseYield #3 %d thread: %d ms\n", + nTimeoutMS, (int)pthread_self()); + + struct android_poll_source *pSource = NULL; + int outFd = 0, outEvents = 0; + + if (mbQueueReDraw) + nTimeoutMS = 0; + + int nRet; + nRet = ALooper_pollAll (nTimeoutMS, &outFd, &outEvents, (void**)&pSource); + fprintf (stderr, "ret #6 %d %d %d %p\n", nRet, outFd, outEvents, pSource); + + // acquire yield mutex again + AcquireYieldMutex(nAcquireCount); + + if (nRet >= 0) { - fprintf (stderr, "**** Huh, %s called in non-NativeActivity app\n", __FUNCTION__); - beenhere = true; + // Process this event. + if (pSource != NULL) + pSource->process(mpApp, pSource); } + + if (mbQueueReDraw && mpApp && mpApp->window) + AndroidSalInstance::getInstance()->RedrawWindows (mpApp->window); } bool AndroidSalInstance::AnyInput( sal_uInt16 nType ) @@ -107,6 +694,94 @@ SalSystem *AndroidSalInstance::CreateSalSystem() return new AndroidSalSystem(); } +class AndroidSalFrame : public SvpSalFrame +{ +public: + AndroidSalFrame( AndroidSalInstance *pInstance, + SalFrame *pParent, + sal_uLong nSalFrameStyle, + SystemParentData *pSysParent ) + : SvpSalFrame( pInstance, pParent, nSalFrameStyle, + true, basebmp::Format::THIRTYTWO_BIT_TC_MASK_RGBA, + pSysParent ) + { + enableDamageTracker(); + } + + virtual void GetWorkArea( Rectangle& rRect ) + { + AndroidSalInstance::getInstance()->GetWorkArea( rRect ); + } + + virtual void damaged( const basegfx::B2IBox& rDamageRect) + { + long long area = rDamageRect.getWidth() * rDamageRect.getHeight(); +// if( area > 32 * 1024 ) + fprintf( stderr, "bitmap damaged %d %d (%dx%d) area %lld\n", + (int) rDamageRect.getMinX(), + (int) rDamageRect.getMinY(), + (int) rDamageRect.getWidth(), + (int) rDamageRect.getHeight(), + area ); + if (rDamageRect.getWidth() <= 0 || + rDamageRect.getHeight() <= 0) + { + fprintf (stderr, "ERROR: damage region has tiny / negative size\n"); + return; + } + Rectangle aRect( std::max((long) 0, (long) rDamageRect.getMinX() ), + std::max((long) 0, (long) rDamageRect.getMinY() ), + std::max((long) 0, (long) ( rDamageRect.getMinX() + + rDamageRect.getWidth() ) ), + std::max((long) 0, (long) ( rDamageRect.getMinY() + + rDamageRect.getHeight() ) ) ); + AndroidSalInstance::getInstance()->damaged( this, aRect ); + } + + virtual void UpdateSettings( AllSettings &rSettings ) + { + // Clobber the UI fonts +#if 0 + psp::FastPrintFontInfo aInfo; + aInfo.m_aFamilyName = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Roboto" ) ); + aInfo.m_eItalic = ITALIC_NORMAL; + aInfo.m_eWeight = WEIGHT_NORMAL; + aInfo.m_eWidth = WIDTH_NORMAL; + psp::PrintFontManager::get().matchFont( aInfo, rSettings.GetUILocale() ); +#endif + + // FIXME: is 12 point enough ? + Font aFont( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Roboto" ) ), + Size( 0, 14 ) ); + + StyleSettings aStyleSet = rSettings.GetStyleSettings(); + aStyleSet.SetAppFont( aFont ); + aStyleSet.SetHelpFont( aFont ); + aStyleSet.SetMenuFont( aFont ); + aStyleSet.SetToolFont( aFont ); + aStyleSet.SetLabelFont( aFont ); + aStyleSet.SetInfoFont( aFont ); + aStyleSet.SetRadioCheckFont( aFont ); + aStyleSet.SetPushButtonFont( aFont ); + aStyleSet.SetFieldFont( aFont ); + aStyleSet.SetIconFont( aFont ); + aStyleSet.SetGroupFont( aFont ); + + rSettings.SetStyleSettings( aStyleSet ); + } +}; + +SalFrame *AndroidSalInstance::CreateChildFrame( SystemParentData* pParent, sal_uLong nStyle ) +{ + return new AndroidSalFrame( this, NULL, nStyle, pParent ); +} + +SalFrame *AndroidSalInstance::CreateFrame( SalFrame* pParent, sal_uLong nStyle ) +{ + return new AndroidSalFrame( this, pParent, nStyle, NULL ); +} + + // All the interesting stuff is slaved from the AndroidSalInstance void InitSalData() {} void DeInitSalData() {} @@ -167,13 +842,10 @@ int AndroidSalSystem::ShowNativeDialog( const rtl::OUString& rTitle, const std::list< rtl::OUString >& rButtons, int nDefButton ) { - (void)rButtons; - (void)nDefButton; - + (void)rButtons; (void)nDefButton; fprintf (stderr, "LibreOffice native dialog '%s': '%s'\n", rtl::OUStringToOString(rTitle, RTL_TEXTENCODING_ASCII_US).getStr(), rtl::OUStringToOString(rMessage, RTL_TEXTENCODING_ASCII_US).getStr()); - __android_log_print(ANDROID_LOG_INFO, "LibreOffice", "Dialog '%s': '%s'", rtl::OUStringToOString(rTitle, RTL_TEXTENCODING_ASCII_US).getStr(), rtl::OUStringToOString(rMessage, RTL_TEXTENCODING_ASCII_US).getStr()); @@ -188,13 +860,9 @@ int AndroidSalSystem::ShowNativeDialog( const rtl::OUString& rTitle, // horror would be needed to use it directly here. Probably we // want some easier to use magic wrapper, hmm. -#if 0 // Let's not drag in this stuff, we are not going to display anythinf - // anyway using a VCL ErrorBox - ErrorBox aVclErrBox( NULL, WB_OK, rTitle ); aVclErrBox.SetText( rMessage ); aVclErrBox.Execute(); -#endif } else fprintf (stderr, "VCL not initialized\n"); diff --git a/vcl/inc/android/androidinst.hxx b/vcl/inc/android/androidinst.hxx index 2fd2718a7e25..addec800070a 100644 --- a/vcl/inc/android/androidinst.hxx +++ b/vcl/inc/android/androidinst.hxx @@ -50,13 +50,27 @@ public: virtual SalSystem* CreateSalSystem(); + // frame management + void GetWorkArea( Rectangle& rRect ); + SalFrame* CreateFrame( SalFrame* pParent, sal_uLong nStyle ); + SalFrame* CreateChildFrame( SystemParentData* pParent, sal_uLong nStyle ); + // mainloop pieces virtual void Wakeup(); virtual bool AnyInput( sal_uInt16 nType ); + // incoming android event handlers: + void onAppCmd (struct android_app* app, int32_t cmd); + int32_t onInputEvent (struct android_app* app, AInputEvent* event); + void RedrawWindows(ANativeWindow *pWindow); + SalFrame *getFocusFrame() const; + + void damaged(AndroidSalFrame *frame, const Rectangle &rRect); protected: virtual void DoReleaseYield( int nTimeoutMS ); + struct android_app *mpApp; Region maRedrawRegion; + bool mbQueueReDraw; }; #endif // ANDROID_SALINST_H diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx index 63ea451f3674..e69a20e4d385 100644 --- a/vcl/source/window/dialog.cxx +++ b/vcl/source/window/dialog.cxx @@ -50,8 +50,6 @@ // ======================================================================= -#ifndef ANDROID - static rtl::OString ImplGetDialogText( Dialog* pDialog ) { rtl::OStringBuffer aErrorStr(rtl::OUStringToOString( @@ -69,8 +67,6 @@ static rtl::OString ImplGetDialogText( Dialog* pDialog ) return aErrorStr.makeStringAndClear(); } -#endif - // ======================================================================= static sal_Bool ImplIsMnemonicCtrl( Window* pWindow ) @@ -796,9 +792,11 @@ sal_Bool Dialog::ImplStartExecuteModal() { #ifdef ANDROID // If a non-NativeActivity app, we shouldn't be showing any dialogs - fprintf(stderr, "%s: Should not do anything, returning false\n", __FUNCTION__); - return sal_False; -#else + if (lo_get_app() == NULL) { + fprintf(stderr, "%s: Should not do anything, returning false\n", __FUNCTION__); + return sal_False; + } +#endif if ( mbInExecute ) { @@ -874,7 +872,6 @@ sal_Bool Dialog::ImplStartExecuteModal() pSVData->maAppData.mnModalMode++; return sal_True; -#endif } // ----------------------------------------------------------------------- |