diff options
Diffstat (limited to 'src/android.c')
-rw-r--r-- | src/android.c | 7711 |
1 files changed, 7711 insertions, 0 deletions
diff --git a/src/android.c b/src/android.c new file mode 100644 index 00000000000..dcd5c6d99c7 --- /dev/null +++ b/src/android.c @@ -0,0 +1,7711 @@ +/* Android initialization for GNU Emacs. + +Copyright (C) 2023-2024 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ + +#include <config.h> + +#include <allocator.h> +#include <assert.h> +#include <careadlinkat.h> +#include <errno.h> +#include <fcntl.h> +#include <fingerprint.h> +#include <intprops.h> +#include <libgen.h> +#include <limits.h> +#include <math.h> +#include <pthread.h> +#include <semaphore.h> +#include <signal.h> +#include <stat-time.h> +#include <stdckdint.h> +#include <string.h> +#include <timespec.h> +#include <unistd.h> + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/select.h> + +/* Old NDK versions lack MIN and MAX. */ +#include <minmax.h> + +#include "android.h" +#include "androidgui.h" + +#include "lisp.h" +#include "blockinput.h" +#include "coding.h" +#include "epaths.h" +#include "systime.h" + +/* Whether or not Emacs is running inside the application process and + Android windowing should be enabled. */ +bool android_init_gui; + +#ifndef ANDROID_STUBIFY + +#include <android/bitmap.h> +#include <android/log.h> + +#include <linux/unistd.h> + +#include <sys/syscall.h> + +#ifdef __aarch64__ +#include <arm_neon.h> +#endif /* __aarch64__ */ + +struct android_emacs_pixmap +{ + jclass class; + jmethodID constructor_mutable; +}; + +struct android_graphics_point +{ + jclass class; + jmethodID constructor; +}; + +struct android_emacs_drawable +{ + jclass class; + jmethodID get_bitmap; +}; + +struct android_emacs_window +{ + jclass class; + jmethodID swap_buffers; + jmethodID toggle_on_screen_keyboard; + jmethodID lookup_string; + jmethodID set_fullscreen; + jmethodID change_window_background; + jmethodID reparent_to; + jmethodID map_window; + jmethodID unmap_window; + jmethodID resize_window; + jmethodID move_window; + jmethodID make_input_focus; + jmethodID raise; + jmethodID lower; + jmethodID reconfigure; + jmethodID get_window_geometry; + jmethodID translate_coordinates; + jmethodID set_dont_accept_focus; + jmethodID set_dont_focus_on_map; + jmethodID define_cursor; + jmethodID damage_rect; + jmethodID recreate_activity; + jmethodID clear_window; + jmethodID clear_area; +}; + +struct android_emacs_cursor +{ + jclass class; + jmethodID constructor; +}; + +struct android_key_character_map +{ + jclass class; + jmethodID get_dead_char; +}; + +/* The API level of the current device. */ +static int android_api_level; + +/* The directory used to store site-lisp. */ +char *android_site_load_path; + +/* The directory used to store native libraries. */ +char *android_lib_dir; + +/* The directory used to store game files. */ +char *android_game_path; + +/* The directory used to store temporary files. */ +char *android_cache_dir; + +/* The list of archive files within which the Java virtual macine + looks for class files. */ +char *android_class_path; + +/* The display's pixel densities. */ +double android_pixel_density_x, android_pixel_density_y; + +/* The display pixel density used to convert between point and pixel + font sizes. */ +double android_scaled_pixel_density; + +/* The Android application data directory. */ +static char *android_files_dir; + +/* The Java environment being used for the main thread. */ +JNIEnv *android_java_env; + +#ifdef THREADS_ENABLED + +/* The Java VM new threads attach to. */ +JavaVM *android_jvm; + +#endif /* THREADS_ENABLED */ + +/* The EmacsGC class. */ +static jclass emacs_gc_class; + +/* Various fields. */ +static jfieldID emacs_gc_foreground, emacs_gc_background; +static jfieldID emacs_gc_function, emacs_gc_clip_rects; +static jfieldID emacs_gc_clip_x_origin, emacs_gc_clip_y_origin; +static jfieldID emacs_gc_stipple, emacs_gc_clip_mask; +static jfieldID emacs_gc_fill_style, emacs_gc_ts_origin_x; +static jfieldID emacs_gc_ts_origin_y; + +/* The constructor and one function. */ +static jmethodID emacs_gc_constructor, emacs_gc_mark_dirty; + +/* The Rect class. */ +static jclass android_rect_class; + +/* Its constructor. */ +static jmethodID android_rect_constructor; + +/* The EmacsService object. */ +jobject emacs_service; + +/* Various methods associated with the EmacsService. */ +struct android_emacs_service service_class; + +/* Various methods associated with the EmacsPixmap class. */ +static struct android_emacs_pixmap pixmap_class; + +/* Various methods associated with the Point class. */ +static struct android_graphics_point point_class; + +/* Various methods associated with the EmacsDrawable class. */ +static struct android_emacs_drawable drawable_class; + +/* Various methods associated with the EmacsWindow class. */ +static struct android_emacs_window window_class; + +/* Various methods associated with the EmacsCursor class. */ +static struct android_emacs_cursor cursor_class; + +/* Various methods associated with the KeyCharacterMap class. */ +static struct android_key_character_map key_character_map_class; + +/* The time at which Emacs was installed, which also supplies the + mtime of asset files. */ +struct timespec emacs_installation_time; + +/* The last event serial used. This is a 32 bit value, but it is + stored in unsigned long to be consistent with X. */ +unsigned int event_serial; + +#ifdef __i386__ + +/* Unused pointer used to control compiler optimizations. */ +void *unused_pointer; + +#endif /* __i386__ */ + +/* Whether or not the default signal mask has been changed. If so, + the signal mask must be restored before calling + android_emacs_init. */ +static bool signal_mask_changed_p; + +/* The signal mask at the time Emacs was started. */ +static sigset_t startup_signal_mask; + + + +/* Event handling functions. Events are stored on a (circular) queue + that is read synchronously. The Android port replaces pselect with + a function android_select, which runs pselect in a separate thread, + but more importantly also waits for events to be available on the + android event queue. */ + +struct android_event_container +{ + /* The next and last events in this queue. */ + struct android_event_container *next, *last; + + /* The event itself. */ + union android_event event; +}; + +struct android_event_queue +{ + /* Mutex protecting the event queue. */ + pthread_mutex_t mutex; + + /* Mutex protecting the select data. */ + pthread_mutex_t select_mutex; + + /* The thread used to run select. */ + pthread_t select_thread; + + /* Condition variables for the reading side. */ + pthread_cond_t read_var; + + /* The number of events in the queue. If this is greater than 1024, + writing will block. */ + int num_events; + + /* Circular queue of events. */ + struct android_event_container events; +}; + +/* Arguments to pselect used by the select thread. */ +static int android_pselect_nfds; +static fd_set *android_pselect_readfds; +static fd_set *android_pselect_writefds; +static fd_set *android_pselect_exceptfds; +static struct timespec *android_pselect_timeout; + +/* Value of pselect. */ +static int android_pselect_rc; + +/* The global event queue. */ +static struct android_event_queue event_queue; + +/* Semaphores used to signal select completion and start. */ +static sem_t android_pselect_sem, android_pselect_start_sem; + +#if __ANDROID_API__ < 16 + +/* Select self-pipe. */ +static int select_pipe[2]; + +#else + +/* Whether or not pselect has been interrupted. */ +static volatile sig_atomic_t android_pselect_interrupted; + +#endif + +/* Set the task name of the current task to NAME, a string at most 16 + characters in length. + + This name is displayed as that of the task (LWP)'s pthread in + GDB. */ + +static void +android_set_task_name (const char *name) +{ + char proc_name[INT_STRLEN_BOUND (long) + + sizeof "/proc/self/task//comm"]; + int fd; + pid_t lwp; + size_t length; + + lwp = gettid (); + sprintf (proc_name, "/proc/self/task/%ld/comm", (long) lwp); + fd = open (proc_name, O_WRONLY | O_TRUNC); + + if (fd < 0) + goto failure; + + length = strlen (name); + + if (write (fd, name, MIN (16, length)) < 0) + goto failure; + + close (fd); + return; + + failure: + __android_log_print (ANDROID_LOG_WARN, __func__, + "Failed to set task name for LWP %ld: %s", + (long) lwp, strerror (errno)); + + /* Close the file descriptor if it is already set. */ + if (fd >= 0) + close (fd); +} + +static void * +android_run_select_thread (void *data) +{ + /* Apparently this is required too. */ + JNI_STACK_ALIGNMENT_PROLOGUE; + + int rc; +#if __ANDROID_API__ < 16 + int nfds; + fd_set readfds; + char byte; +#else + sigset_t signals, waitset; + int sig; +#endif + + /* Set the name of this thread's LWP for debugging purposes. */ + android_set_task_name ("`android_select'"); + +#if __ANDROID_API__ < 16 + /* A completely different implementation is used when building for + Android versions earlier than 16, because pselect with a signal + mask does not work there. Instead of blocking SIGUSR1 and + unblocking it inside pselect, a file descriptor is used instead. + Something is written to the file descriptor every time select is + supposed to return. */ + + while (true) + { + /* Wait for the thread to be released. */ + while (sem_wait (&android_pselect_start_sem) < 0) + ;; + + /* Get the select lock and call pselect. API 8 does not have + working pselect in any sense. Instead, pselect wakes up on + select_pipe[0]. */ + + pthread_mutex_lock (&event_queue.select_mutex); + nfds = android_pselect_nfds; + + if (android_pselect_readfds) + readfds = *android_pselect_readfds; + else + FD_ZERO (&readfds); + + if (nfds < select_pipe[0] + 1) + nfds = select_pipe[0] + 1; + FD_SET (select_pipe[0], &readfds); + + rc = pselect (nfds, &readfds, + android_pselect_writefds, + android_pselect_exceptfds, + android_pselect_timeout, + NULL); + + /* Subtract 1 from rc if readfds contains the select pipe, and + also remove it from that set. */ + + if (rc != -1 && FD_ISSET (select_pipe[0], &readfds)) + { + rc -= 1; + FD_CLR (select_pipe[0], &readfds); + + /* If no file descriptors aside from the select pipe are + ready, then pretend that an error has occurred. */ + if (!rc) + rc = -1; + } + + /* Save the read file descriptor set back again. */ + + if (android_pselect_readfds) + *android_pselect_readfds = readfds; + + android_pselect_rc = rc; + pthread_mutex_unlock (&event_queue.select_mutex); + + /* Signal the main thread that there is now data to read. Hold + the event queue lock during this process to make sure this + does not happen before the main thread begins to wait for the + condition variable. */ + + pthread_mutex_lock (&event_queue.mutex); + pthread_cond_broadcast (&event_queue.read_var); + pthread_mutex_unlock (&event_queue.mutex); + + /* Read a single byte from the select pipe. */ + read (select_pipe[0], &byte, 1); + + /* Signal the Emacs thread that pselect is done. If read_var + was signaled by android_write_event, event_queue.mutex could + still be locked, so this must come before. */ + sem_post (&android_pselect_sem); + } +#else + if (pthread_sigmask (SIG_BLOCK, &signals, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "pthread_sigmask: %s", + strerror (errno)); + + sigfillset (&signals); + sigdelset (&signals, SIGUSR1); + sigemptyset (&waitset); + sigaddset (&waitset, SIGUSR1); + + while (true) + { + /* Wait for the thread to be released. */ + while (sem_wait (&android_pselect_start_sem) < 0) + ;; + + /* Clear the ``pselect interrupted'' flag. This is safe because + right now, SIGUSR1 is blocked. */ + android_pselect_interrupted = 0; + + /* Get the select lock and call pselect. */ + pthread_mutex_lock (&event_queue.select_mutex); + rc = pselect (android_pselect_nfds, + android_pselect_readfds, + android_pselect_writefds, + android_pselect_exceptfds, + android_pselect_timeout, + &signals); + android_pselect_rc = rc; + pthread_mutex_unlock (&event_queue.select_mutex); + + /* Signal the main thread that there is now data to read. Hold + the event queue lock during this process to make sure this + does not happen before the main thread begins to wait for the + condition variable. */ + + pthread_mutex_lock (&event_queue.mutex); + pthread_cond_broadcast (&event_queue.read_var); + pthread_mutex_unlock (&event_queue.mutex); + + /* Check `android_pselect_interrupted' instead of rc and errno. + + This is because `pselect' does not return an rc of -1 upon + being interrupted in some versions of Android, but does set + signal masks correctly. */ + + if (!android_pselect_interrupted) + /* Now, wait for SIGUSR1, unless pselect was interrupted and + the signal was already delivered. The Emacs thread will + always send this signal after read_var is triggered or the + UI thread has sent an event. */ + sigwait (&waitset, &sig); + + /* Signal the Emacs thread that pselect is done. If read_var + was signaled by android_write_event, event_queue.mutex could + still be locked, so this must come before. */ + sem_post (&android_pselect_sem); + } +#endif + + return NULL; +} + +#if __ANDROID_API__ >= 16 + +static void +android_handle_sigusr1 (int sig, siginfo_t *siginfo, void *arg) +{ + /* Notice that pselect has been interrupted. */ + android_pselect_interrupted = 1; +} + +#endif + +/* Semaphore used to indicate completion of a query. + This should ideally be defined further down. */ +static sem_t android_query_sem; + +/* ID of the Emacs thread. */ +static pthread_t main_thread_id; + +/* Set up the global event queue by initializing the mutex and two + condition variables, and the linked list of events. This must be + called before starting the Emacs thread. Also, initialize the + thread used to run pselect. + + These functions must also use the C library malloc and free, + because xmalloc is not thread safe. */ + +static void +android_init_events (void) +{ + struct sigaction sa; + + if (pthread_mutex_init (&event_queue.mutex, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "pthread_mutex_init: %s", + strerror (errno)); + + if (pthread_mutex_init (&event_queue.select_mutex, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "pthread_mutex_init: %s", + strerror (errno)); + + if (pthread_cond_init (&event_queue.read_var, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "pthread_cond_init: %s", + strerror (errno)); + + sem_init (&android_pselect_sem, 0, 0); + sem_init (&android_pselect_start_sem, 0, 0); + sem_init (&android_query_sem, 0, 0); + + event_queue.events.next = &event_queue.events; + event_queue.events.last = &event_queue.events; + + main_thread_id = pthread_self (); + +#if __ANDROID_API__ >= 16 + + /* Before starting the select thread, make sure the disposition for + SIGUSR1 is correct. */ + sigfillset (&sa.sa_mask); + sa.sa_sigaction = android_handle_sigusr1; + sa.sa_flags = SA_SIGINFO; + +#else + + /* Set up the file descriptor used to wake up pselect. */ + if (pipe2 (select_pipe, O_CLOEXEC) < 0) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "pipe2: %s", strerror (errno)); + + /* Make sure the read end will fit in fd_set. */ + if (select_pipe[0] >= FD_SETSIZE) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "read end of select pipe" + " lies outside FD_SETSIZE!"); + +#endif + + if (sigaction (SIGUSR1, &sa, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "sigaction: %s", + strerror (errno)); + + /* Start the select thread. */ + if (pthread_create (&event_queue.select_thread, NULL, + android_run_select_thread, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "pthread_create: %s", + strerror (errno)); +} + +int +android_pending (void) +{ + int i; + + pthread_mutex_lock (&event_queue.mutex); + i = event_queue.num_events; + pthread_mutex_unlock (&event_queue.mutex); + + return i; +} + +/* Wait for events to become available synchronously. Return once an + event arrives. Also, reply to the UI thread whenever it requires a + response. */ + +void +android_wait_event (void) +{ + /* Run queries from the UI thread to the Emacs thread. */ + android_check_query (); + + pthread_mutex_lock (&event_queue.mutex); + + /* Wait for events to appear if there are none available to + read. */ + if (!event_queue.num_events) + pthread_cond_wait (&event_queue.read_var, + &event_queue.mutex); + + pthread_mutex_unlock (&event_queue.mutex); + + /* Check for queries again. If a query is sent after the call to + `android_check_query' above, `read_var' will be signaled. */ + android_check_query (); +} + +void +android_next_event (union android_event *event_return) +{ + struct android_event_container *container; + + pthread_mutex_lock (&event_queue.mutex); + + /* Wait for events to appear if there are none available to + read. */ + if (!event_queue.num_events) + pthread_cond_wait (&event_queue.read_var, + &event_queue.mutex); + + /* Obtain the event from the end of the queue. */ + container = event_queue.events.last; + eassert (container != &event_queue.events); + + /* Remove the event from the queue and copy it to the caller + supplied buffer. */ + container->last->next = container->next; + container->next->last = container->last; + *event_return = container->event; + event_queue.num_events--; + + /* Free the container. */ + free (container); + + /* Unlock the queue. */ + pthread_mutex_unlock (&event_queue.mutex); +} + +bool +android_check_if_event (union android_event *event_return, + bool (*predicate) (union android_event *, + void *), + void *arg) +{ + struct android_event_container *container; + + pthread_mutex_lock (&event_queue.mutex); + + /* Loop over each event. */ + container = event_queue.events.last; + for (; container != &event_queue.events; container = container->last) + { + /* See if the predicate matches. */ + if ((*predicate) (&container->event, arg)) + { + /* Copy out the event and return true. */ + *event_return = container->event; + --event_queue.num_events; + + /* Unlink container. */ + container->last->next = container->next; + container->next->last = container->last; + free (container); + pthread_mutex_unlock (&event_queue.mutex); + return true; + } + } + + pthread_mutex_unlock (&event_queue.mutex); + return false; +} + +void +android_write_event (union android_event *event) +{ + struct android_event_container *container; + + container = malloc (sizeof *container); + + if (!container) + return; + + /* If the event queue hasn't been initialized yet, return false. */ + if (!event_queue.events.next) + return; + + pthread_mutex_lock (&event_queue.mutex); + container->next = event_queue.events.next; + container->last = &event_queue.events; + container->next->last = container; + container->last->next = container; + container->event = *event; + event_queue.num_events++; + pthread_cond_broadcast (&event_queue.read_var); + pthread_mutex_unlock (&event_queue.mutex); + + /* Now set pending_signals to true, and raise SIGIO to interrupt any + ongoing reads if the event is important. */ + pending_signals = true; + + switch (event->type) + { + /* Key press and window action events are considered important, + as they either end up quitting or asking for responses to the + IME. */ + case ANDROID_KEY_PRESS: + case ANDROID_WINDOW_ACTION: + kill (getpid (), SIGIO); + break; + + default: + break; + } +} + + + +/* Whether or not the UI thread has been waiting for a significant + amount of time for a function to run in the main thread, and Emacs + should answer the query ASAP. */ +static bool android_urgent_query; + +int +android_select (int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timespec *timeout) +{ + int nfds_return; +#if __ANDROID_API__ < 16 + static char byte; +#endif + +#ifdef THREADS_ENABLED + if (!pthread_equal (pthread_self (), main_thread_id)) + return pselect (nfds, readfds, writefds, exceptfds, timeout, + NULL); +#endif /* THREADS_ENABLED */ + + /* Since Emacs is reading keyboard input again, signify that queries + from input methods are no longer ``urgent''. */ + + __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE); + + /* Check for and run anything the UI thread wants to run on the main + thread. */ + android_check_query (); + + pthread_mutex_lock (&event_queue.mutex); + + if (event_queue.num_events) + { + /* Zero READFDS, WRITEFDS and EXCEPTFDS, lest the caller + mistakenly interpret this return value as indicating that an + inotify file descriptor is readable, and try to poll an + unready one. */ + + if (readfds) + FD_ZERO (readfds); + + if (writefds) + FD_ZERO (writefds); + + if (exceptfds) + FD_ZERO (exceptfds); + pthread_mutex_unlock (&event_queue.mutex); + return 1; + } + + nfds_return = 0; + + pthread_mutex_lock (&event_queue.select_mutex); + android_pselect_nfds = nfds; + android_pselect_readfds = readfds; + android_pselect_writefds = writefds; + android_pselect_exceptfds = exceptfds; + android_pselect_timeout = timeout; + pthread_mutex_unlock (&event_queue.select_mutex); + + /* Release the select thread. */ + sem_post (&android_pselect_start_sem); + + /* Start waiting for the event queue condition to be set. */ + pthread_cond_wait (&event_queue.read_var, &event_queue.mutex); + +#if __ANDROID_API__ >= 16 + /* Interrupt the select thread now, in case it's still in + pselect. */ + pthread_kill (event_queue.select_thread, SIGUSR1); +#else + /* Interrupt the select thread by writing to the select pipe. */ + if (write (select_pipe[1], &byte, 1) != 1) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "write: %s", strerror (errno)); +#endif + + /* Unlock the event queue mutex. */ + pthread_mutex_unlock (&event_queue.mutex); + + /* Wait for pselect to return in any case. This must be done with + the event queue mutex unlocked. Otherwise, the pselect thread + can hang if it tries to lock the event queue mutex to signal + read_var after the UI thread has already done so. */ + while (sem_wait (&android_pselect_sem) < 0) + ;; + + /* If there are now events in the queue, return 1. */ + + pthread_mutex_lock (&event_queue.mutex); + if (event_queue.num_events) + nfds_return = 1; + pthread_mutex_unlock (&event_queue.mutex); + + /* Add the return value of pselect if it has also found ready file + descriptors. */ + + if (android_pselect_rc >= 0) + nfds_return += android_pselect_rc; + else if (!nfds_return) + /* If pselect was interrupted and nfds_return is 0 (meaning that + no events have been read), indicate that an error has taken + place. */ + nfds_return = android_pselect_rc; + + if ((android_pselect_rc < 0) && nfds_return >= 0) + { + /* Clear the file descriptor sets if events will be delivered + but no file descriptors have become ready to prevent the + caller from misinterpreting a non-zero return value. */ + + if (readfds) + FD_ZERO (readfds); + + if (writefds) + FD_ZERO (writefds); + + if (exceptfds) + FD_ZERO (exceptfds); + } + + /* This is to shut up process.c when pselect gets EINTR. */ + if (nfds_return < 0) + errno = EINTR; + +#ifndef THREADS_ENABLED + /* Now check for and run anything the UI thread wants to run in the + main thread. */ + android_check_query (); +#endif /* THREADS_ENABLED */ + + return nfds_return; +} + + + +static void * +android_run_debug_thread (void *data) +{ + FILE *file; + int fd; + char *line; + size_t n; + + /* Set the name of this thread's LWP for debugging purposes. */ + android_set_task_name ("`android_debug'"); + + fd = (int) (intptr_t) data; + file = fdopen (fd, "r"); + + if (!file) + return NULL; + + line = NULL; + + while (true) + { + if (getline (&line, &n, file) < 0) + { + free (line); + break; + } + + __android_log_print (ANDROID_LOG_INFO, __func__, "%s", line); + } + + fclose (file); + return NULL; +} + + + +/* Intercept USER_FULL_NAME and return something that makes sense if + pw->pw_gecos is NULL. */ + +char * +android_user_full_name (struct passwd *pw) +{ +#ifdef HAVE_STRUCT_PASSWD_PW_GECOS + if (!pw->pw_gecos) + return (char *) "Android user"; + + return pw->pw_gecos; +#else /* !HAVE_STRUCT_PASSWD_PW_GECOS */ + return "Android user"; +#endif /* HAVE_STRUCT_PASSWD_PW_GECOS */ +} + + + +/* Return whether or not the specified file NAME designates a file in + the directory DIR, which should be an absolute file name. NAME + must be in canonical form. */ + +bool +android_is_special_directory (const char *name, const char *dir) +{ + size_t len; + + /* Compare up to strlen (DIR) bytes of NAME with DIR. */ + + len = strlen (dir); + if (strncmp (name, dir, len)) + return false; + + /* Now see if the character of NAME after len is either a directory + separator or a terminating NULL. */ + + name += len; + switch (*name) + { + case '\0': /* NAME is an exact match for DIR. */ + case '/': /* NAME is a constituent of DIR. */ + return true; + } + + /* The file name doesn't match. */ + return false; +} + +#if 0 + +/* URL-encode N bytes of the specified STRING into at most N bytes of + BUFFER; STRING is assumed to be encoded in a `utf-8-emacs' + compatible coding system. Value is the number of bytes encoded + (excluding the trailing null byte placed at the end of the encoded + text) or -1 upon failure. */ + +static ssize_t +android_url_encode (const char *restrict string, size_t length, + char *restrict buffer, size_t n) +{ + int len, character; + size_t num_encoded; + char *end; + char format[1 + 25]; + + /* For each multibyte character... */ + + end = string + length; + num_encoded = 0; + + while (string < end) + { + /* XXX: Android documentation claims that URIs is encoded + according to the ``Unicode'' scheme, but what this means in + reality is that the URI is encoded in UTF-8, and then + each of its bytes are encoded. */ + /* Find the length of the multibyte character at STRING. */ + len = /* multibyte_length (string, end, true, true) */ 1; + + /* 0 means that STRING is not a valid multibyte string. */ + if (!len || string + len > end) + goto failure; + + /* Now fetch the character and increment string. */ + /* character = /\* STRING_CHAR ((unsigned char *) string) *\/; */ + character = *(unsigned char *) string; + string += len; + + /* If CHARACTER is not a letter or an unreserved character, + escape it. */ + + if (!((character >= 'A' + && character <= 'Z') + || (character >= 'a' + && character <= 'z') + || (character >= '0' + && character <= '9') + || character == '_' + || character == '-' + || character == '!' + || character == '.' + || character == '~' + || character == '\'' + || character == '(' + || character == ')' + || character == '*')) + { + len = sprintf (format, "%%%X", (unsigned int) character); + if (len < 0) + goto failure; + + /* See if there is enough space left to hold the encoded + string. */ + + if (n < len) + goto failure; + + n -= len; + num_encoded += len; + + /* Copy the encoded string to STRING. */ + memcpy (buffer, format, n); + buffer += len; + } + else + { + /* No more space within BUFFER. */ + if (!n) + goto failure; + + /* Don't encode this ASCII character; just store it. */ + n--, num_encoded++; + *(buffer++) = character; + } + } + + /* If there's no space for a trailing null byte or more bytes have + been encoded than representable in ssize_t, fail. */ + + if (!n || num_encoded > SSIZE_MAX) + goto failure; + + /* Store the terminating NULL byte. */ + *buffer = '\0'; + return num_encoded; + + failure: + return -1; +} + +/* Return the content URI corresponding to a `/content' file name, + or NULL if it is not a content URI. + + This function is not reentrant. */ + +static const char * +android_get_content_name (const char *filename) +{ + static char buffer[PATH_MAX + 1]; + char *head, *token, *next, *saveptr, *copy, *mark, *mark1; + ssize_t rc; + size_t n, length; + + /* Find the file name described if it starts with `/content'. If + just the directory is described, return content://. */ + + filename = android_is_special_directory (filename, "/content"); + + if (!filename) + return NULL; + + if (!*filename) + return "content://"; + + /* Now copy FILENAME into a buffer and convert it into a content + URI. */ + + copy = xstrdup (filename); + mark = saveptr = NULL; + head = stpcpy (buffer, "content:/"); + + /* Split FILENAME by slashes. */ + + token = strtok_r (copy, "/", &saveptr); + + while (token) + { + /* Compute the number of bytes remaining in buffer excluding a + trailing null byte. */ + n = PATH_MAX - (head - buffer); + + /* Write / to the buffer. Return failure if there is no space + for it. */ + + if (!n) + goto failure; + + *head++ = '/'; + n--; + + /* Find the next token now. */ + next = strtok_r (NULL, "/", &saveptr); + + /* Detect and avoid encoding an encoded URL query affixed to the + end of the last component within the content file name. + + Content URIs can include a query describing parameters that + must be provided to the content provider. They are separated + from the rest of the URI by a single question mark character, + which should not be encoded. + + However, the distinction between the separator and question + marks that appear inside file name components is lost when a + content URI is decoded into a content path. To compensate + for this loss of information, Emacs assumes that the last + question mark is always a URI separator, and suffixes content + file names which contain question marks with a trailing + question mark. */ + + if (!next) + { + /* Find the last question mark character. */ + + mark1 = strchr (token, '?'); + + while (mark1) + { + mark = mark1; + mark1 = strchr (mark + 1, '?'); + } + } + + if (mark) + { + /* First, encode the part leading to the question mark + character. */ + + rc = 0; + if (mark > token) + rc = android_url_encode (token, mark - token, + head, n + 1); + + /* If this fails, bail out. */ + + if (rc < 0) + goto failure; + + /* Copy mark to the file name. */ + + n -= rc, head += rc; + length = strlen (mark); + + if (n < length) + goto failure; + + strcpy (head, mark); + + /* Now break out of the loop, since this is the last + component anyway. */ + break; + } + else + /* Now encode this file name component into the buffer. */ + rc = android_url_encode (token, strlen (token), + head, n + 1); + + if (rc < 0) + goto failure; + + head += rc; + token = next; + } + + /* buffer must have been null terminated by + `android_url_encode'. */ + xfree (copy); + return buffer; + + failure: + xfree (copy); + return NULL; +} + +#endif /* 0 */ + +/* Return the current user's ``home'' directory, which is actually the + app data directory on Android. */ + +const char * +android_get_home_directory (void) +{ + return android_files_dir; +} + +/* Return the name of the file behind a file descriptor FD by reading + /proc/self/fd/. Value is allocated memory holding the file name + upon success, and 0 upon failure. */ + +static char * +android_proc_name (int fd) +{ + char format[sizeof "/proc/self/fd/" + + INT_STRLEN_BOUND (int)]; + static struct allocator allocator = { + /* Fill the allocator with C library malloc functions. xmalloc + and so aren't thread safe. */ + malloc, realloc, free, NULL, + }; + + sprintf (format, "/proc/self/fd/%d", fd); + return careadlinkat (AT_FDCWD, format, NULL, 0, + &allocator, readlinkat); +} + +/* Try to guarantee the existence of the `lib' directory within the + parent directory of the application files directory. + + If `/data/data/org.gnu.emacs/lib' (or + `/data/user/N/org.gnu.emacs/lib') does not exist or is a dangling + symbolic link, create a symlink from it to the library + directory. + + Newer versions of Android don't create this link by default, making + it difficult to locate the directory containing Emacs library + files, particularly from scripts in other programs sharing the same + user ID as Emacs that don't have access to `exec-path'. */ + +static void +android_create_lib_link (void) +{ + char *filename; + char lib_directory[PATH_MAX]; + int fd; + + /* Find the directory containing the files directory. */ + filename = dirname (android_files_dir); + if (!filename) + goto failure; + + /* Now make `lib_directory' the name of the library directory + within. */ + snprintf (lib_directory, PATH_MAX, "%s/lib", filename); + + /* Try to open this directory. */ + fd = open (lib_directory, O_DIRECTORY); + + /* If the directory can be opened normally, close it and return + now. */ + if (fd >= 0) + goto success; + + /* Try to unlink the directory in case it's a dangling symbolic + link. */ + unlink (lib_directory); + + /* Otherwise, try to symlink lib_directory to the actual library + directory. */ + + if (symlink (android_lib_dir, lib_directory)) + /* Print a warning message if creating the link fails. */ + __android_log_print (ANDROID_LOG_WARN, __func__, + "Failed to create symbolic link from" + " application library directory `%s'" + " to its actual location at `%s'", + lib_directory, android_files_dir); + + success: + close (fd); + failure: + return; +} + + + +/* JNI functions called by Java. */ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif + +JNIEXPORT jint JNICALL +NATIVE_NAME (dup) (JNIEnv *env, jobject object, jint fd) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + return dup (fd); +} + +JNIEXPORT jint JNICALL +NATIVE_NAME (close) (JNIEnv *env, jobject object, jint fd) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + return close (fd); +} + +JNIEXPORT jstring JNICALL +NATIVE_NAME (getFingerprint) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + char buffer[sizeof fingerprint * 2 + 1]; + + memset (buffer, 0, sizeof buffer); + hexbuf_digest (buffer, (char *) fingerprint, + sizeof fingerprint); + + return (*env)->NewStringUTF (env, buffer); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, + jobject local_asset_manager, + jobject files_dir, jobject libs_dir, + jobject cache_dir, + jfloat pixel_density_x, + jfloat pixel_density_y, + jfloat scaled_density, + jobject class_path, + jobject emacs_service_object, + jint api_level) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + int pipefd[2]; + pthread_t thread; + const char *java_string; + struct stat statb; + +#ifdef THREADS_ENABLED + /* Save the Java VM. */ + if ((*env)->GetJavaVM (env, &android_jvm)) + emacs_abort (); +#endif /* THREADS_ENABLED */ + + /* Set the Android API level early, as it is used by + `android_vfs_init'. */ + android_api_level = api_level; + + /* This function should only be called from the main thread. */ + android_pixel_density_x = pixel_density_x; + android_pixel_density_y = pixel_density_y; + android_scaled_pixel_density = scaled_density; + + __android_log_print (ANDROID_LOG_INFO, __func__, + "Initializing "PACKAGE_STRING"...\nPlease report bugs to " + PACKAGE_BUGREPORT". Thanks.\n"); + + if (emacs_service_object) + { + /* Create a pipe and duplicate it to stdout and stderr. Next, + make a thread that prints stderr to the system log. + + Notice that this function is called in one of two ways. The + first is when Emacs is being started as a GUI application by + the system, and the second is when Emacs is being started by + libandroid-emacs.so as an ordinary noninteractive Emacs. + + In the second case, stderr is usually connected to a PTY, so + this is unnecessary. */ + + if (pipe2 (pipefd, O_CLOEXEC) < 0) + emacs_abort (); + + if (dup2 (pipefd[1], 2) < 0) + emacs_abort (); + close (pipefd[1]); + + if (pthread_create (&thread, NULL, android_run_debug_thread, + (void *) (intptr_t) pipefd[0])) + emacs_abort (); + } + + /* Now set the path to the site load directory. */ + + java_string = (*env)->GetStringUTFChars (env, (jstring) files_dir, + NULL); + + if (!java_string) + emacs_abort (); + + android_files_dir = strdup ((const char *) java_string); + + if (!android_files_dir) + emacs_abort (); + + (*env)->ReleaseStringUTFChars (env, (jstring) files_dir, + java_string); + + java_string = (*env)->GetStringUTFChars (env, (jstring) libs_dir, + NULL); + + if (!java_string) + emacs_abort (); + + android_lib_dir = strdup ((const char *) java_string); + + if (!android_files_dir) + emacs_abort (); + + (*env)->ReleaseStringUTFChars (env, (jstring) libs_dir, + java_string); + + java_string = (*env)->GetStringUTFChars (env, (jstring) cache_dir, + NULL); + + if (!java_string) + emacs_abort (); + + android_cache_dir = strdup ((const char *) java_string); + + if (!android_files_dir) + emacs_abort (); + + (*env)->ReleaseStringUTFChars (env, (jstring) cache_dir, + java_string); + + if (class_path) + { + java_string = (*env)->GetStringUTFChars (env, (jstring) class_path, + NULL); + + if (!java_string) + emacs_abort (); + + android_class_path = strdup ((const char *) java_string); + + if (!android_class_path) + emacs_abort (); + + (*env)->ReleaseStringUTFChars (env, (jstring) class_path, + java_string); + } + + /* Derive the installation date from the modification time of the + file constitituing the class path. */ + + emacs_installation_time = invalid_timespec (); + + if (class_path) + { + if (!stat (android_class_path, &statb)) + emacs_installation_time = get_stat_mtime (&statb); + } + + /* Calculate the site-lisp path. */ + + android_site_load_path = malloc (PATH_MAX + 1); + + if (!android_site_load_path) + emacs_abort (); + + android_game_path = malloc (PATH_MAX + 1); + + if (!android_game_path) + emacs_abort (); + + snprintf (android_site_load_path, PATH_MAX, "%s/site-lisp", + android_files_dir); + snprintf (android_game_path, PATH_MAX, "%s/scores", android_files_dir); + + __android_log_print (ANDROID_LOG_INFO, __func__, + "Site-lisp directory: %s\n" + "Files directory: %s\n" + "Native code directory: %s\n" + "Game score path: %s\n" + "Class path: %s\n", + android_site_load_path, + android_files_dir, + android_lib_dir, android_game_path, + (android_class_path + ? android_class_path + : "None")); + + if (android_class_path) + /* Set EMACS_CLASS_PATH to the class path where + EmacsNoninteractive can be found. */ + setenv ("EMACS_CLASS_PATH", android_class_path, 1); + + /* Set LD_LIBRARY_PATH to an appropriate value. */ + setenv ("LD_LIBRARY_PATH", android_lib_dir, 1); + + /* EMACS_LD_LIBRARY_PATH records the location of the app library + directory. android-emacs refers to this, since users have valid + reasons for changing LD_LIBRARY_PATH to a value that precludes + the possibility of Java locating libemacs later. */ + setenv ("EMACS_LD_LIBRARY_PATH", android_lib_dir, 1); + + /* If the system is Android 5.0 or later, set LANG to en_US.utf8, + which is understood by the C library. In other instances set it + to C, a meaningless value, for good measure. */ + + if (emacs_service_object) + { + if (api_level >= 21) + setenv ("LANG", "en_US.utf8", 1); + else + setenv ("LANG", "C", 1); + } + + /* Make a reference to the Emacs service. */ + + if (emacs_service_object) + { + emacs_service = (*env)->NewGlobalRef (env, emacs_service_object); + + if (!emacs_service) + emacs_abort (); + + /* If the service is set this Emacs is being initialized as part + of the Emacs application itself. + + Try to create a symlink from where scripts expect Emacs to + place its library files to the directory that actually holds + them; earlier versions of Android used to do this + automatically, but that feature has been removed. */ + + android_create_lib_link (); + } + + /* Set up events. */ + android_init_events (); + + /* Set up the Android virtual filesystem layer. */ + android_vfs_init (env, local_asset_manager); + + /* OK, setup is now complete. The caller may call initEmacs + now. */ +} + +JNIEXPORT jobject JNICALL +NATIVE_NAME (getProcName) (JNIEnv *env, jobject object, jint fd) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + char *buffer; + size_t length; + jbyteArray array; + + buffer = android_proc_name (fd); + if (!buffer) + return NULL; + + /* Return a byte array, as Java strings cannot always encode file + names. */ + length = strlen (buffer); + array = (*env)->NewByteArray (env, length); + if (!array) + goto finish; + + (*env)->SetByteArrayRegion (env, array, 0, length, + (jbyte *) buffer); + + finish: + free (buffer); + return array; +} + +/* Initialize service_class, aborting if something goes wrong. */ + +static void +android_init_emacs_service (void) +{ + jclass old; + + service_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsService"); + eassert (service_class.class); + + old = service_class.class; + service_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!service_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + service_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + service_class.class, \ + name, signature); \ + eassert (service_class.c_name); + + FIND_METHOD (fill_rectangle, "fillRectangle", + "(Lorg/gnu/emacs/EmacsDrawable;" + "Lorg/gnu/emacs/EmacsGC;IIII)V"); + FIND_METHOD (fill_polygon, "fillPolygon", + "(Lorg/gnu/emacs/EmacsDrawable;" + "Lorg/gnu/emacs/EmacsGC;" + "[Landroid/graphics/Point;)V"); + FIND_METHOD (draw_rectangle, "drawRectangle", + "(Lorg/gnu/emacs/EmacsDrawable;" + "Lorg/gnu/emacs/EmacsGC;IIII)V"); + FIND_METHOD (draw_line, "drawLine", + "(Lorg/gnu/emacs/EmacsDrawable;" + "Lorg/gnu/emacs/EmacsGC;IIII)V"); + FIND_METHOD (draw_point, "drawPoint", + "(Lorg/gnu/emacs/EmacsDrawable;" + "Lorg/gnu/emacs/EmacsGC;II)V"); + FIND_METHOD (ring_bell, "ringBell", "(I)V"); + FIND_METHOD (query_tree, "queryTree", + "(Lorg/gnu/emacs/EmacsWindow;)[S"); + FIND_METHOD (get_screen_width, "getScreenWidth", "(Z)I"); + FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I"); + FIND_METHOD (detect_mouse, "detectMouse", "()Z"); + FIND_METHOD (detect_keyboard, "detectKeyboard", "()Z"); + FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;"); + FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;Z)" + "Ljava/lang/String;"); + FIND_METHOD (restart_emacs, "restartEmacs", "()V"); + FIND_METHOD (update_ic, "updateIC", + "(Lorg/gnu/emacs/EmacsWindow;IIII)V"); + FIND_METHOD (reset_ic, "resetIC", + "(Lorg/gnu/emacs/EmacsWindow;I)V"); + FIND_METHOD (open_content_uri, "openContentUri", + "([BZZZ)I"); + FIND_METHOD (check_content_uri, "checkContentUri", + "(Ljava/lang/String;ZZ)Z"); + FIND_METHOD (query_battery, "queryBattery", "()[J"); + FIND_METHOD (update_extracted_text, "updateExtractedText", + "(Lorg/gnu/emacs/EmacsWindow;" + "Landroid/view/inputmethod/ExtractedText;I)V"); + FIND_METHOD (update_cursor_anchor_info, "updateCursorAnchorInfo", + "(Lorg/gnu/emacs/EmacsWindow;FFFF)V"); + FIND_METHOD (get_document_authorities, "getDocumentAuthorities", + "()[Ljava/lang/String;"); + FIND_METHOD (request_directory_access, "requestDirectoryAccess", + "()I"); + FIND_METHOD (get_document_trees, "getDocumentTrees", + "([B)[Ljava/lang/String;"); + FIND_METHOD (document_id_from_name, "documentIdFromName", + "(Ljava/lang/String;Ljava/lang/String;" + "[Ljava/lang/String;)I"); + FIND_METHOD (get_tree_uri, "getTreeUri", + "(Ljava/lang/String;Ljava/lang/String;)" + "Ljava/lang/String;"); + FIND_METHOD (stat_document, "statDocument", + "(Ljava/lang/String;Ljava/lang/String;Z)[J"); + FIND_METHOD (access_document, "accessDocument", + "(Ljava/lang/String;Ljava/lang/String;Z)I"); + FIND_METHOD (open_document_directory, "openDocumentDirectory", + "(Ljava/lang/String;Ljava/lang/String;)" + "Landroid/database/Cursor;"); + FIND_METHOD (read_directory_entry, "readDirectoryEntry", + "(Landroid/database/Cursor;)Lorg/gnu/emacs/" + "EmacsDirectoryEntry;"); + FIND_METHOD (open_document, "openDocument", + "(Ljava/lang/String;Ljava/lang/String;ZZZ)" + "Landroid/os/ParcelFileDescriptor;"); + FIND_METHOD (create_document, "createDocument", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)Ljava/lang/String;"); + FIND_METHOD (create_directory, "createDirectory", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)Ljava/lang/String;"); + FIND_METHOD (delete_document, "deleteDocument", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)I"); + FIND_METHOD (rename_document, "renameDocument", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Ljava/lang/String;)I"); + FIND_METHOD (move_document, "moveDocument", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)Ljava/lang/String;"); + FIND_METHOD (valid_authority, "validAuthority", + "(Ljava/lang/String;)Z"); + FIND_METHOD (external_storage_available, + "externalStorageAvailable", "()Z"); + FIND_METHOD (request_storage_access, + "requestStorageAccess", "()V"); + FIND_METHOD (cancel_notification, + "cancelNotification", "(Ljava/lang/String;)V"); +#undef FIND_METHOD +} + +static void +android_init_emacs_pixmap (void) +{ + jclass old; + + pixmap_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsPixmap"); + eassert (pixmap_class.class); + + old = pixmap_class.class; + pixmap_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!pixmap_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + pixmap_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + pixmap_class.class, \ + name, signature); \ + eassert (pixmap_class.c_name); + + FIND_METHOD (constructor_mutable, "<init>", "(SIII)V"); + +#undef FIND_METHOD +} + +static void +android_init_graphics_point (void) +{ + jclass old; + + point_class.class + = (*android_java_env)->FindClass (android_java_env, + "android/graphics/Point"); + eassert (point_class.class); + + old = point_class.class; + point_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!point_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + point_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + point_class.class, \ + name, signature); \ + eassert (point_class.c_name); + + FIND_METHOD (constructor, "<init>", "(II)V"); +#undef FIND_METHOD +} + +static void +android_init_emacs_drawable (void) +{ + jclass old; + + drawable_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsDrawable"); + eassert (drawable_class.class); + + old = drawable_class.class; + drawable_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!drawable_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + drawable_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + drawable_class.class, \ + name, signature); \ + eassert (drawable_class.c_name); + + FIND_METHOD (get_bitmap, "getBitmap", "()Landroid/graphics/Bitmap;"); +#undef FIND_METHOD +} + +static void +android_init_emacs_window (void) +{ + jclass old; + + window_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsWindow"); + eassert (window_class.class); + + old = window_class.class; + window_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!window_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + window_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + window_class.class, \ + name, signature); \ + eassert (window_class.c_name); + + FIND_METHOD (swap_buffers, "swapBuffers", "()V"); + FIND_METHOD (toggle_on_screen_keyboard, + "toggleOnScreenKeyboard", "(Z)V"); + FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;"); + FIND_METHOD (set_fullscreen, "setFullscreen", "(Z)V"); + FIND_METHOD (change_window_background, "changeWindowBackground", + "(I)V"); + FIND_METHOD (reparent_to, "reparentTo", + "(Lorg/gnu/emacs/EmacsWindow;II)V"); + FIND_METHOD (map_window, "mapWindow", "()V"); + FIND_METHOD (unmap_window, "unmapWindow", "()V"); + FIND_METHOD (resize_window, "resizeWindow", "(II)V"); + FIND_METHOD (move_window, "moveWindow", "(II)V"); + FIND_METHOD (make_input_focus, "makeInputFocus", "(J)V"); + FIND_METHOD (raise, "raise", "()V"); + FIND_METHOD (lower, "lower", "()V"); + FIND_METHOD (reconfigure, "reconfigure", "(Lorg/gnu/emacs/EmacsWindow;I)V"); + FIND_METHOD (get_window_geometry, "getWindowGeometry", + "()[I"); + FIND_METHOD (translate_coordinates, "translateCoordinates", + "(II)[I"); + FIND_METHOD (set_dont_focus_on_map, "setDontFocusOnMap", "(Z)V"); + FIND_METHOD (set_dont_accept_focus, "setDontAcceptFocus", "(Z)V"); + FIND_METHOD (define_cursor, "defineCursor", + "(Lorg/gnu/emacs/EmacsCursor;)V"); + /* In spite of the declaration of this function being located within + EmacsDrawable, the ID of the `damage_rect' method is retrieved + from EmacsWindow, which avoids virtual function dispatch within + android_damage_window. */ + FIND_METHOD (damage_rect, "damageRect", "(IIII)V"); + FIND_METHOD (recreate_activity, "recreateActivity", "()V"); + FIND_METHOD (clear_window, "clearWindow", "()V"); + FIND_METHOD (clear_area, "clearArea", "(IIII)V"); +#undef FIND_METHOD +} + +static void +android_init_emacs_cursor (void) +{ + jclass old; + + cursor_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsCursor"); + eassert (cursor_class.class); + + old = cursor_class.class; + cursor_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!cursor_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + cursor_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + cursor_class.class, \ + name, signature); \ + eassert (cursor_class.c_name); + + FIND_METHOD (constructor, "<init>", "(SI)V"); +#undef FIND_METHOD +} + +static void +android_init_key_character_map (void) +{ + jclass old; + + key_character_map_class.class + = (*android_java_env)->FindClass (android_java_env, + "android/view/KeyCharacterMap"); + eassert (key_character_map_class.class); + + old = key_character_map_class.class; + key_character_map_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!key_character_map_class.class) + emacs_abort (); + + key_character_map_class.get_dead_char + = (*android_java_env)->GetStaticMethodID (android_java_env, + key_character_map_class.class, + "getDeadChar", "(II)I"); + eassert (key_character_map_class.get_dead_char); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, + jobject dump_file_object) +{ + /* android_emacs_init is not main, so GCC is not nice enough to add + the stack alignment prologue. + + Unfortunately for us, dalvik on Android 4.0.x calls native code + with a 4 byte aligned stack, so this prologue must be inserted + before each function exported via JNI. */ + + JNI_STACK_ALIGNMENT_PROLOGUE; + + char **c_argv; + jsize nelements, i; + jobject argument; + const char *c_argument; + char *dump_file; + + android_java_env = env; + + nelements = (*env)->GetArrayLength (env, argv); + c_argv = alloca (sizeof *c_argv * (nelements + 1)); + + for (i = 0; i < nelements; ++i) + { + argument = (*env)->GetObjectArrayElement (env, argv, i); + c_argument = (*env)->GetStringUTFChars (env, (jstring) argument, + NULL); + + if (!c_argument) + emacs_abort (); + + /* Note that c_argument is in ``modified UTF-8 encoding'', but + we don't care as NUL bytes are not being specified inside. */ + c_argv[i] = alloca (strlen (c_argument) + 1); + strcpy (c_argv[i], c_argument); + (*env)->ReleaseStringUTFChars (env, (jstring) argument, c_argument); + } + + c_argv[nelements] = NULL; + + android_init_emacs_service (); + android_init_emacs_pixmap (); + android_init_graphics_point (); + android_init_emacs_drawable (); + android_init_emacs_window (); + android_init_emacs_cursor (); + android_init_key_character_map (); + + /* Set HOME to the app data directory. */ + setenv ("HOME", android_files_dir, 1); + + /* Set TMPDIR to the temporary files directory. */ + setenv ("TMPDIR", android_cache_dir, 1); + + /* And finally set "SHELL" to /system/bin/sh. Otherwise, some + programs will look for /bin/sh, which is problematic. */ + setenv ("SHELL", "/system/bin/sh", 1); + + /* Set the cwd to that directory as well. */ + if (chdir (android_files_dir)) + __android_log_print (ANDROID_LOG_WARN, __func__, + "chdir: %s", strerror (errno)); + + /* Initialize the Android GUI as long as the service object was + set. */ + + if (emacs_service) + android_init_gui = true; + + /* Now see if a dump file has been specified and should be used. */ + dump_file = NULL; + + if (dump_file_object) + { + c_argument + = (*env)->GetStringUTFChars (env, (jstring) dump_file_object, + NULL); + + /* Copy the Java string data once. */ + dump_file = strdup (c_argument); + + /* Release the Java string data. */ + (*env)->ReleaseStringUTFChars (env, (jstring) dump_file_object, + c_argument); + } + + /* Delete local references to objects that are no longer needed. */ + ANDROID_DELETE_LOCAL_REF (argv); + ANDROID_DELETE_LOCAL_REF (dump_file_object); + + /* Restore the signal mask at the time of startup if it was changed + to block unwanted signals from reaching system threads. */ + + if (signal_mask_changed_p) + pthread_sigmask (SIG_SETMASK, &startup_signal_mask, NULL); + + /* Now start Emacs proper. */ + android_emacs_init (nelements, c_argv, dump_file); + + /* android_emacs_init should never return. */ + emacs_abort (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + emacs_abort (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (quit) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + __android_log_print (ANDROID_LOG_VERBOSE, __func__, + "Sending SIGIO and setting Vquit_flag"); + + /* Raise sigio to interrupt anything that could be reading + input. */ + Vquit_flag = Qt; + kill (getpid (), SIGIO); +} + +/* Call shut_down_emacs subsequent to a call to the service's + onDestroy callback. CLOSURE is ignored. */ + +static void +android_shut_down_emacs (void *closure) +{ + __android_log_print (ANDROID_LOG_INFO, __func__, + "The Emacs service is being shut down"); + shut_down_emacs (0, Qnil); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (shutDownEmacs) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + android_run_in_emacs_thread (android_shut_down_emacs, NULL); +} + +/* Carry out garbage collection and clear all image caches on the + Android terminal. Called when the system has depleted most of its + memory and desires that background processes release unused + core. */ + +static void +android_on_low_memory (void *closure) +{ + Fclear_image_cache (Qt, Qnil); + garbage_collect (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (onLowMemory) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + android_run_in_emacs_thread (android_on_low_memory, NULL); +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, + jshort window, jlong time, + jint x, jint y, jint width, + jint height) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xconfigure.type = ANDROID_CONFIGURE_NOTIFY; + event.xconfigure.serial = ++event_serial; + event.xconfigure.window = window; + event.xconfigure.time = time; + event.xconfigure.x = x; + event.xconfigure.y = y; + event.xconfigure.width = width; + event.xconfigure.height = height; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, + jshort window, jlong time, + jint state, jint keycode, + jint unicode_char) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xkey.type = ANDROID_KEY_PRESS; + event.xkey.serial = ++event_serial; + event.xkey.window = window; + event.xkey.time = time; + event.xkey.state = state; + event.xkey.keycode = keycode; + event.xkey.unicode_char = unicode_char; + event.xkey.counter = 0; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, + jshort window, jlong time, + jint state, jint keycode, + jint unicode_char) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xkey.type = ANDROID_KEY_RELEASE; + event.xkey.serial = ++event_serial; + event.xkey.window = window; + event.xkey.time = time; + event.xkey.state = state; + event.xkey.keycode = keycode; + event.xkey.unicode_char = unicode_char; + event.xkey.counter = 0; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object, + jshort window, jlong time) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xfocus.type = ANDROID_FOCUS_IN; + event.xfocus.serial = ++event_serial; + event.xfocus.window = window; + event.xfocus.time = time; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object, + jshort window, jlong time) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xfocus.type = ANDROID_FOCUS_OUT; + event.xfocus.serial = ++event_serial; + event.xfocus.window = window; + event.xfocus.time = time; + + android_write_event (&event); + return ++event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object, + jshort window, jint action) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xaction.type = ANDROID_WINDOW_ACTION; + event.xaction.serial = ++event_serial; + event.xaction.window = window; + event.xaction.action = action; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xcrossing.type = ANDROID_ENTER_NOTIFY; + event.xcrossing.serial = ++event_serial; + event.xcrossing.window = window; + event.xcrossing.x = x; + event.xcrossing.y = y; + event.xcrossing.time = time; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xcrossing.type = ANDROID_LEAVE_NOTIFY; + event.xcrossing.serial = ++event_serial; + event.xcrossing.window = window; + event.xcrossing.x = x; + event.xcrossing.y = y; + event.xcrossing.time = time; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xmotion.type = ANDROID_MOTION_NOTIFY; + event.xmotion.serial = ++event_serial; + event.xmotion.window = window; + event.xmotion.x = x; + event.xmotion.y = y; + event.xmotion.time = time; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint state, + jint button) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xbutton.type = ANDROID_BUTTON_PRESS; + event.xbutton.serial = ++event_serial; + event.xbutton.window = window; + event.xbutton.x = x; + event.xbutton.y = y; + event.xbutton.time = time; + event.xbutton.state = state; + event.xbutton.button = button; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint state, + jint button) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xbutton.type = ANDROID_BUTTON_RELEASE; + event.xbutton.serial = ++event_serial; + event.xbutton.window = window; + event.xbutton.x = x; + event.xbutton.y = y; + event.xbutton.time = time; + event.xbutton.state = state; + event.xbutton.button = button; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint pointer_id, + jint flags) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.touch.type = ANDROID_TOUCH_DOWN; + event.touch.serial = ++event_serial; + event.touch.window = window; + event.touch.x = x; + event.touch.y = y; + event.touch.time = time; + event.touch.pointer_id = pointer_id; + event.touch.flags = flags; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint pointer_id, + jint flags) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.touch.type = ANDROID_TOUCH_UP; + event.touch.serial = ++event_serial; + event.touch.window = window; + event.touch.x = x; + event.touch.y = y; + event.touch.time = time; + event.touch.pointer_id = pointer_id; + event.touch.flags = flags; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint pointer_id, + jint flags) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.touch.type = ANDROID_TOUCH_MOVE; + event.touch.serial = ++event_serial; + event.touch.window = window; + event.touch.x = x; + event.touch.y = y; + event.touch.time = time; + event.touch.pointer_id = pointer_id; + event.touch.flags = flags; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint state, + jfloat x_delta, jfloat y_delta) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.wheel.type = ANDROID_WHEEL; + event.wheel.serial = ++event_serial; + event.wheel.window = window; + event.wheel.x = x; + event.wheel.y = y; + event.wheel.time = time; + event.wheel.state = state; + event.wheel.x_delta = x_delta; + event.wheel.y_delta = y_delta; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, + jshort window) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.iconified.type = ANDROID_ICONIFIED; + event.iconified.serial = ++event_serial; + event.iconified.window = window; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, + jshort window) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.iconified.type = ANDROID_DEICONIFIED; + event.iconified.serial = ++event_serial; + event.iconified.window = window; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, + jshort window, jint menu_event_id, + jint menu_event_serial) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.menu.type = ANDROID_CONTEXT_MENU; + event.menu.serial = ++event_serial; + event.menu.window = window; + event.menu.menu_event_id = menu_event_id; + event.menu.menu_event_serial = menu_event_serial; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jint width, jint height) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.xexpose.type = ANDROID_EXPOSE; + event.xexpose.serial = ++event_serial; + event.xexpose.window = window; + event.xexpose.x = x; + event.xexpose.y = y; + event.xexpose.width = width; + event.xexpose.height = height; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendDndDrag) (JNIEnv *env, jobject object, + jshort window, jint x, jint y) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.dnd.type = ANDROID_DND_DRAG_EVENT; + event.dnd.serial = ++event_serial; + event.dnd.window = window; + event.dnd.x = x; + event.dnd.y = y; + event.dnd.uri_or_string = NULL; + event.dnd.length = 0; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendDndUri) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jstring string) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + const jchar *characters; + jsize length; + uint16_t *buffer; + + event.dnd.type = ANDROID_DND_URI_EVENT; + event.dnd.serial = ++event_serial; + event.dnd.window = window; + event.dnd.x = x; + event.dnd.y = y; + + length = (*env)->GetStringLength (env, string); + buffer = malloc (length * sizeof *buffer); + characters = (*env)->GetStringChars (env, string, NULL); + + if (!characters) + /* The JVM has run out of memory; return and let the out of memory + error take its course. */ + return 0; + + memcpy (buffer, characters, length * sizeof *buffer); + (*env)->ReleaseStringChars (env, string, characters); + + event.dnd.uri_or_string = buffer; + event.dnd.length = length; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendDndText) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jstring string) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + const jchar *characters; + jsize length; + uint16_t *buffer; + + event.dnd.type = ANDROID_DND_TEXT_EVENT; + event.dnd.serial = ++event_serial; + event.dnd.window = window; + event.dnd.x = x; + event.dnd.y = y; + + length = (*env)->GetStringLength (env, string); + buffer = malloc (length * sizeof *buffer); + characters = (*env)->GetStringChars (env, string, NULL); + + if (!characters) + /* The JVM has run out of memory; return and let the out of memory + error take its course. */ + return 0; + + memcpy (buffer, characters, length * sizeof *buffer); + (*env)->ReleaseStringChars (env, string, characters); + + event.dnd.uri_or_string = buffer; + event.dnd.length = length; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendNotificationDeleted) (JNIEnv *env, jobject object, + jstring tag) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + const char *characters; + + event.notification.type = ANDROID_NOTIFICATION_DELETED; + event.notification.serial = ++event_serial; + event.notification.window = ANDROID_NONE; + + /* TAG is guaranteed to be an ASCII string, of which the JNI character + encoding is a superset. */ + characters = (*env)->GetStringUTFChars (env, tag, NULL); + if (!characters) + return 0; + + event.notification.tag = strdup (characters); + (*env)->ReleaseStringUTFChars (env, tag, characters); + if (!event.notification.tag) + return 0; + + event.notification.action = NULL; + event.notification.length = 0; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jlong JNICALL +NATIVE_NAME (sendNotificationAction) (JNIEnv *env, jobject object, + jstring tag, jstring action) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + const void *characters; + jsize length; + uint16_t *buffer; + + event.notification.type = ANDROID_NOTIFICATION_ACTION; + event.notification.serial = ++event_serial; + event.notification.window = ANDROID_NONE; + + /* TAG is guaranteed to be an ASCII string, of which the JNI character + encoding is a superset. */ + characters = (*env)->GetStringUTFChars (env, tag, NULL); + if (!characters) + return 0; + + event.notification.tag = strdup (characters); + (*env)->ReleaseStringUTFChars (env, tag, characters); + if (!event.notification.tag) + return 0; + + length = (*env)->GetStringLength (env, action); + buffer = malloc (length * sizeof *buffer); + characters = (*env)->GetStringChars (env, action, NULL); + + if (!characters) + { + /* The JVM has run out of memory; return and let the out of memory + error take its course. */ + xfree (event.notification.tag); + return 0; + } + + memcpy (buffer, characters, length * sizeof *buffer); + (*env)->ReleaseStringChars (env, action, characters); + + event.notification.action = buffer; + event.notification.length = length; + + android_write_event (&event); + return event_serial; +} + +JNIEXPORT jboolean JNICALL +NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env, + jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + /* Yes, android_pass_multimedia_buttons_to_system is being + read from the UI thread. */ + return !android_pass_multimedia_buttons_to_system; +} + +JNIEXPORT jboolean JNICALL +NATIVE_NAME (shouldForwardCtrlSpace) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + return !android_intercept_control_space; +} + +JNIEXPORT void JNICALL +NATIVE_NAME (blitRect) (JNIEnv *env, jobject object, + jobject src, jobject dest, + jint x1, jint y1, jint x2, jint y2) +{ + AndroidBitmapInfo src_info, dest_info; + unsigned char *src_data_1, *dest_data_1; + void *src_data, *dest_data; + + /* N.B. that X2 and Y2 represent the pixel past the edge of the + rectangle; thus, the width is x2 - x1 and the height is y2 - + y1. */ + + memset (&src_info, 0, sizeof src_info); + memset (&dest_info, 0, sizeof dest_info); + AndroidBitmap_getInfo (env, src, &src_info); + AndroidBitmap_getInfo (env, dest, &dest_info); + + /* If the stride is 0 after a call to `getInfo', assume it + failed. */ + + if (!src_info.stride || !dest_info.stride) + return; + + /* If formats differ, abort. */ + eassert (src_info.format == dest_info.format + && src_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888); + + /* Lock the image data. */ + src_data = NULL; + AndroidBitmap_lockPixels (env, src, &src_data); + + if (!src_data) + return; + + dest_data = NULL; + AndroidBitmap_lockPixels (env, dest, &dest_data); + + if (!dest_data) + goto fail1; + + /* Now clip the rectangle to the bounds of the source and + destination bitmap. */ + + x1 = MAX (x1, 0); + y1 = MAX (y1, 0); + x2 = MAX (x2, 0); + y2 = MAX (y2, 0); + + + if (x1 >= src_info.width + || x1 >= dest_info.width) + x1 = MIN (dest_info.width - 1, src_info.width - 1); + + if (x2 > src_info.width + || x2 > dest_info.width) + x2 = MIN (src_info.width, dest_info.width); + + if (y1 >= src_info.height + || y1 >= dest_info.height) + y1 = MIN (dest_info.height - 1, src_info.height - 1); + + if (y2 > src_info.height + || y2 > dest_info.height) + y2 = MIN (src_info.height, dest_info.height); + + if (x1 >= x2 || y1 >= y2) + goto fail2; + + /* Determine the address of the first line to copy. */ + + src_data_1 = src_data; + dest_data_1 = dest_data; + src_data_1 += x1 * 4; + src_data_1 += y1 * src_info.stride; + dest_data_1 += x1 * 4; + dest_data_1 += y1 * dest_info.stride; + + /* Start copying each line. */ + + while (y1 != y2) + { + memcpy (dest_data_1, src_data_1, (x2 - x1) * 4); + src_data_1 += src_info.stride; + dest_data_1 += dest_info.stride; + y1++; + } + + /* Complete the copy and unlock the bitmap. */ + + fail2: + AndroidBitmap_unlockPixels (env, dest); + fail1: + AndroidBitmap_unlockPixels (env, src); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (notifyPixelsChanged) (JNIEnv *env, jobject object, + jobject bitmap) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + void *data; + + /* Lock and unlock the bitmap. This calls + SkBitmap->notifyPixelsChanged. */ + + if (AndroidBitmap_lockPixels (env, bitmap, &data) < 0) + /* The return value is less than 0 if an error occurs. + Good luck finding this in the documentation. */ + return; + + AndroidBitmap_unlockPixels (env, bitmap); +} + +/* Forward declarations of deadlock prevention functions. */ + +static void android_begin_query (void); +static void android_end_query (void); +static void android_answer_query_spin (void); + +JNIEXPORT void JNICALL +NATIVE_NAME (beginSynchronous) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + android_begin_query (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (endSynchronous) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + android_end_query (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (answerQuerySpin) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + android_answer_query_spin (); +} + + + +/* System thread setup. Android doesn't always block signals Emacs is + interested in from being received by the UI or render threads, + which can lead to problems when those signals then interrupt one of + those threads. */ + +JNIEXPORT void JNICALL +NATIVE_NAME (setupSystemThread) (void) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + sigset_t sigset; + + /* Block everything except for SIGSEGV and SIGBUS; those two are + used by the runtime. */ + + sigfillset (&sigset); + sigdelset (&sigset, SIGSEGV); + sigdelset (&sigset, SIGBUS); + + /* Save the signal mask that was previously used. It will be + restored in `initEmacs'. */ + + if (pthread_sigmask (SIG_BLOCK, &sigset, &startup_signal_mask)) + __android_log_print (ANDROID_LOG_WARN, __func__, + "pthread_sigmask: %s", strerror (errno)); + else + signal_mask_changed_p = true; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#else +#pragma GCC diagnostic pop +#endif + + + +/* Java functions called by C. + + Because all C code runs in the native function initEmacs, ALL LOCAL + REFERENCES WILL PERSIST! + + This means that every local reference must be explicitly destroyed + with DeleteLocalRef. A helper macro is provided to do this. */ + +struct android_handle_entry +{ + /* The type. */ + enum android_handle_type type; + + /* The handle. */ + jobject handle; +}; + +/* Table of handles MAX_HANDLE long. */ +struct android_handle_entry android_handles[USHRT_MAX]; + +/* The largest handle ID currently known, but subject to + wraparound. */ +static android_handle max_handle; + +/* Allocate a new, unused, handle identifier. If Emacs is out of + identifiers, return 0. */ + +static android_handle +android_alloc_id (void) +{ + android_handle handle; + + /* 0 is never a valid handle ID. */ + + if (!max_handle) + max_handle++; + + /* See if the handle is already occupied. */ + + if (android_handles[max_handle].handle) + { + /* Look for a fresh unoccupied handle. */ + + handle = max_handle; + max_handle++; + + while (handle != max_handle) + { + ++max_handle; + + /* Make sure the handle is valid. */ + if (!max_handle) + ++max_handle; + + if (!android_handles[max_handle].handle) + return max_handle++; + } + + return ANDROID_NONE; + } + + return max_handle++; +} + +/* Destroy the specified handle and mark it as free on the Java side + as well. */ + +static void +android_destroy_handle (android_handle handle) +{ + static jclass old, class; + static jmethodID method; + + if (!android_handles[handle].handle) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Trying to destroy free handle!"); + emacs_abort (); + } + + if (!class) + { + class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsHandleObject"); + eassert (class != NULL); + + method + = (*android_java_env)->GetMethodID (android_java_env, class, + "destroyHandle", "()V"); + eassert (method != NULL); + + old = class; + class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) class); + android_exception_check_1 (old); + ANDROID_DELETE_LOCAL_REF (old); + } + + (*android_java_env)->CallVoidMethod (android_java_env, + android_handles[handle].handle, + method); + + /* Just clear any exception thrown. If destroying the handle + fails from an out-of-memory error, then Emacs loses some + resources, but that is not as big deal as signaling. */ + (*android_java_env)->ExceptionClear (android_java_env); + + /* Delete the global reference regardless of any error. */ + (*android_java_env)->DeleteGlobalRef (android_java_env, + android_handles[handle].handle); + android_handles[handle].handle = NULL; +} + +jobject +android_resolve_handle (android_handle handle, + enum android_handle_type type) +{ + if (!handle) + /* ANDROID_NONE. */ + return NULL; + + /* CheckJNI will normally ensure that the handle exists and is + the right type, but with a less informative error message. + Don't waste cycles doing our own checking here. */ + +#ifdef ENABLE_CHECKING + + if (!android_handles[handle].handle) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Trying to resolve free handle!"); + emacs_abort (); + } + + if (android_handles[handle].type != type) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Handle has wrong type!"); + emacs_abort (); + } + +#endif /* ENABLE_CHECKING */ + + return android_handles[handle].handle; +} + +static jobject +android_resolve_handle2 (android_handle handle, + enum android_handle_type type, + enum android_handle_type type2) +{ + if (!handle) + return NULL; + + /* CheckJNI will normally ensure that the handle exists and is + the right type, but with a less informative error message. + Don't waste cycles doing our own checking here. */ + +#ifdef ENABLE_CHECKING + + if (!android_handles[handle].handle) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Trying to resolve free handle!"); + emacs_abort (); + } + + if (android_handles[handle].type != type + && android_handles[handle].type != type2) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Handle has wrong type!"); + emacs_abort (); + } + +#endif /* ENABLE_CHECKING */ + + return android_handles[handle].handle; +} + +void +android_change_window_attributes (android_window handle, + enum android_window_value_mask value_mask, + struct android_set_window_attributes *attrs) +{ + jmethodID method; + jobject window; + jint pixel; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + if (value_mask & ANDROID_CW_BACK_PIXEL) + { + method = window_class.change_window_background; + pixel = (jint) attrs->background_pixel; + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + method, pixel); + android_exception_check (); + } +} + +/* Create a new window with the given width, height and + attributes. */ + +android_window +android_create_window (android_window parent, int x, int y, + int width, int height, + enum android_window_value_mask value_mask, + struct android_set_window_attributes *attrs) +{ + static jclass class; + static jmethodID constructor; + jobject object, parent_object, old; + android_window window; + android_handle prev_max_handle; + bool override_redirect; + + parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW); + + prev_max_handle = max_handle; + window = android_alloc_id (); + + if (!window) + error ("Out of window handles!"); + + if (!class) + { + class = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsWindow"); + eassert (class != NULL); + + constructor + = (*android_java_env)->GetMethodID (android_java_env, class, "<init>", + "(SLorg/gnu/emacs/EmacsWindow;" + "IIIIZ)V"); + eassert (constructor != NULL); + + old = class; + class = (*android_java_env)->NewGlobalRef (android_java_env, class); + android_exception_check_1 (old); + ANDROID_DELETE_LOCAL_REF (old); + } + + /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window + creation time. */ + override_redirect = ((value_mask + & ANDROID_CW_OVERRIDE_REDIRECT) + && attrs->override_redirect); + + object = (*android_java_env)->NewObject (android_java_env, class, + constructor, (jshort) window, + parent_object, (jint) x, (jint) y, + (jint) width, (jint) height, + (jboolean) override_redirect); + if (!object) + { + (*android_java_env)->ExceptionClear (android_java_env); + + max_handle = prev_max_handle; + memory_full (0); + } + + android_handles[window].type = ANDROID_HANDLE_WINDOW; + android_handles[window].handle + = (*android_java_env)->NewGlobalRef (android_java_env, + object); + (*android_java_env)->ExceptionClear (android_java_env); + ANDROID_DELETE_LOCAL_REF (object); + + if (!android_handles[window].handle) + memory_full (0); + + android_change_window_attributes (window, value_mask, attrs); + return window; +} + +void +android_set_window_background (android_window window, unsigned long pixel) +{ + struct android_set_window_attributes attrs; + + attrs.background_pixel = pixel; + android_change_window_attributes (window, ANDROID_CW_BACK_PIXEL, + &attrs); +} + +void +android_destroy_window (android_window window) +{ + if (android_handles[window].type != ANDROID_HANDLE_WINDOW) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Trying to destroy something not a window!"); + emacs_abort (); + } + + android_destroy_handle (window); +} + +static void +android_init_android_rect_class (void) +{ + jclass old; + + if (android_rect_class) + /* Already initialized. */ + return; + + android_rect_class + = (*android_java_env)->FindClass (android_java_env, + "android/graphics/Rect"); + eassert (android_rect_class); + + android_rect_constructor + = (*android_java_env)->GetMethodID (android_java_env, android_rect_class, + "<init>", "(IIII)V"); + eassert (emacs_gc_constructor); + + old = android_rect_class; + android_rect_class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) android_rect_class); + android_exception_check_1 (old); + ANDROID_DELETE_LOCAL_REF (old); +} + +static void +android_init_emacs_gc_class (void) +{ + jclass old; + + if (emacs_gc_class) + /* Already initialized. */ + return; + + emacs_gc_class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsGC"); + eassert (emacs_gc_class); + + emacs_gc_constructor + = (*android_java_env)->GetMethodID (android_java_env, + emacs_gc_class, + "<init>", "(S)V"); + eassert (emacs_gc_constructor); + + emacs_gc_mark_dirty + = (*android_java_env)->GetMethodID (android_java_env, + emacs_gc_class, + "markDirty", "(Z)V"); + eassert (emacs_gc_mark_dirty); + + old = emacs_gc_class; + emacs_gc_class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) emacs_gc_class); + android_exception_check_1 (old); + ANDROID_DELETE_LOCAL_REF (old); + + emacs_gc_foreground + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "foreground", "I"); + emacs_gc_background + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "background", "I"); + emacs_gc_function + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "function", "I"); + emacs_gc_clip_rects + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "clip_rects", + "[Landroid/graphics/Rect;"); + emacs_gc_clip_x_origin + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "clip_x_origin", "I"); + emacs_gc_clip_y_origin + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "clip_y_origin", "I"); + emacs_gc_stipple + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "stipple", + "Lorg/gnu/emacs/EmacsPixmap;"); + emacs_gc_clip_mask + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "clip_mask", + "Lorg/gnu/emacs/EmacsPixmap;"); + emacs_gc_fill_style + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "fill_style", "I"); + emacs_gc_ts_origin_x + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "ts_origin_x", "I"); + emacs_gc_ts_origin_y + = (*android_java_env)->GetFieldID (android_java_env, + emacs_gc_class, + "ts_origin_y", "I"); +} + +struct android_gc * +android_create_gc (enum android_gc_value_mask mask, + struct android_gc_values *values) +{ + struct android_gc *gc; + android_handle prev_max_handle; + jobject object; + + android_init_emacs_gc_class (); + + gc = xmalloc (sizeof *gc); + prev_max_handle = max_handle; + gc->gcontext = android_alloc_id (); + gc->foreground = 0; + gc->background = 0xffffff; + gc->clip_rects = NULL; + + /* This means to not apply any clipping. */ + gc->num_clip_rects = -1; + + /* Apply the other default values. */ + gc->function = ANDROID_GC_COPY; + gc->fill_style = ANDROID_FILL_SOLID; + gc->clip_x_origin = 0; + gc->clip_y_origin = 0; + gc->clip_mask = ANDROID_NONE; + gc->stipple = ANDROID_NONE; + gc->ts_x_origin = 0; + gc->ts_y_origin = 0; + + if (!gc->gcontext) + { + xfree (gc); + error ("Out of GContext handles!"); + } + + object = (*android_java_env)->NewObject (android_java_env, + emacs_gc_class, + emacs_gc_constructor, + (jshort) gc->gcontext); + + if (!object) + { + (*android_java_env)->ExceptionClear (android_java_env); + + max_handle = prev_max_handle; + memory_full (0); + } + + android_handles[gc->gcontext].type = ANDROID_HANDLE_GCONTEXT; + android_handles[gc->gcontext].handle + = (*android_java_env)->NewGlobalRef (android_java_env, object); + (*android_java_env)->ExceptionClear (android_java_env); + ANDROID_DELETE_LOCAL_REF (object); + + if (!android_handles[gc->gcontext].handle) + memory_full (0); + + android_change_gc (gc, mask, values); + return gc; +} + +void +android_free_gc (struct android_gc *gc) +{ + android_destroy_handle (gc->gcontext); + + xfree (gc->clip_rects); + xfree (gc); +} + +void +android_change_gc (struct android_gc *gc, + enum android_gc_value_mask mask, + struct android_gc_values *values) +{ + jobject what, gcontext; + jboolean clip_changed; + + clip_changed = false; + + android_init_emacs_gc_class (); + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + if (mask & ANDROID_GC_FOREGROUND) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_foreground, + values->foreground); + gc->foreground = values->foreground; + } + + if (mask & ANDROID_GC_BACKGROUND) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_background, + values->background); + gc->background = values->background; + } + + if (mask & ANDROID_GC_FUNCTION) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_function, + values->function); + gc->function = values->function; + } + + if (mask & ANDROID_GC_CLIP_X_ORIGIN) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_clip_x_origin, + values->clip_x_origin); + gc->clip_x_origin = values->clip_x_origin; + clip_changed = true; + } + + if (mask & ANDROID_GC_CLIP_Y_ORIGIN) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_clip_y_origin, + values->clip_y_origin); + gc->clip_y_origin = values->clip_y_origin; + clip_changed = true; + } + + if (mask & ANDROID_GC_CLIP_MASK) + { + what = android_resolve_handle (values->clip_mask, + ANDROID_HANDLE_PIXMAP); + (*android_java_env)->SetObjectField (android_java_env, + gcontext, + emacs_gc_clip_mask, + what); + gc->clip_mask = values->clip_mask; + + /* Changing GCClipMask also clears the clip rectangles. */ + (*android_java_env)->SetObjectField (android_java_env, + gcontext, + emacs_gc_clip_rects, + NULL); + + xfree (gc->clip_rects); + gc->clip_rects = NULL; + gc->num_clip_rects = -1; + clip_changed = true; + } + + if (mask & ANDROID_GC_STIPPLE) + { + what = android_resolve_handle (values->stipple, + ANDROID_HANDLE_PIXMAP); + (*android_java_env)->SetObjectField (android_java_env, + gcontext, + emacs_gc_stipple, + what); + gc->stipple = values->stipple; + } + + if (mask & ANDROID_GC_FILL_STYLE) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_fill_style, + values->fill_style); + gc->fill_style = values->fill_style; + } + + if (mask & ANDROID_GC_TILE_STIP_X_ORIGIN) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_ts_origin_x, + values->ts_x_origin); + gc->ts_x_origin = values->ts_x_origin; + } + + if (mask & ANDROID_GC_TILE_STIP_Y_ORIGIN) + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_ts_origin_y, + values->ts_y_origin); + gc->ts_y_origin = values->ts_y_origin; + } + + if (mask) + { + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + gcontext, + emacs_gc_class, + emacs_gc_mark_dirty, + (jboolean) clip_changed); + android_exception_check (); + } +} + +void +android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin, + int clip_y_origin, + struct android_rectangle *clip_rects, + int n_clip_rects) +{ + jobjectArray array; + jobject rect, gcontext; + int i; + + android_init_android_rect_class (); + android_init_emacs_gc_class (); + + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + array = (*android_java_env)->NewObjectArray (android_java_env, + n_clip_rects, + android_rect_class, + NULL); + android_exception_check (); + + for (i = 0; i < n_clip_rects; ++i) + { + rect = (*android_java_env)->NewObject (android_java_env, + android_rect_class, + android_rect_constructor, + (jint) clip_rects[i].x, + (jint) clip_rects[i].y, + (jint) (clip_rects[i].x + + clip_rects[i].width), + (jint) (clip_rects[i].y + + clip_rects[i].height)); + + /* The meaning of this call is to check whether or not an + allocation error happened, and to delete ARRAY and signal an + out-of-memory error if that is the case. */ + android_exception_check_1 (array); + + (*android_java_env)->SetObjectArrayElement (android_java_env, + array, i, rect); + ANDROID_DELETE_LOCAL_REF (rect); + } + + (*android_java_env)->SetObjectField (android_java_env, + gcontext, + emacs_gc_clip_rects, + (jobject) array); + ANDROID_DELETE_LOCAL_REF (array); + + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_clip_x_origin, + clip_x_origin); + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_clip_y_origin, + clip_y_origin); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + gcontext, + emacs_gc_class, + emacs_gc_mark_dirty, + (jboolean) true); + android_exception_check (); + + /* Cache the clip rectangles on the C side for + sfntfont-android.c. */ + if (gc->clip_rects) + xfree (gc->clip_rects); + + /* If gc->num_clip_rects is 0, then no drawing will be performed at + all. */ + gc->clip_rects = xmalloc (sizeof *gc->clip_rects + * n_clip_rects); + gc->num_clip_rects = n_clip_rects; + memcpy (gc->clip_rects, clip_rects, + n_clip_rects * sizeof *gc->clip_rects); +} + +void +android_reparent_window (android_window w, android_window parent_handle, + int x, int y) +{ + jobject window, parent; + jmethodID method; + + window = android_resolve_handle (w, ANDROID_HANDLE_WINDOW); + parent = android_resolve_handle (parent_handle, + ANDROID_HANDLE_WINDOW); + + method = window_class.reparent_to; + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window, + window_class.class, method, + parent, (jint) x, (jint) y); + android_exception_check (); +} + +void +android_clear_window (android_window handle) +{ + jobject window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + window_class.clear_window); + android_exception_check (); +} + +void +android_map_window (android_window handle) +{ + jobject window; + jmethodID map_window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + map_window = window_class.map_window; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + map_window); + android_exception_check (); +} + +void +android_unmap_window (android_window handle) +{ + jobject window; + jmethodID unmap_window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + unmap_window = window_class.unmap_window; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + unmap_window); + android_exception_check (); +} + +void +android_resize_window (android_window handle, unsigned int width, + unsigned int height) +{ + jobject window; + jmethodID resize_window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + resize_window = window_class.resize_window; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + resize_window, + (jint) width, + (jint) height); + android_exception_check (); +} + +void +android_move_window (android_window handle, int x, int y) +{ + jobject window; + jmethodID move_window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + move_window = window_class.move_window; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + move_window, + (jint) x, (jint) y); + android_exception_check (); +} + +void +android_swap_buffers (struct android_swap_info *swap_info, + int num_windows) +{ + jobject window; + int i; + + for (i = 0; i < num_windows; ++i) + { + window = android_resolve_handle (swap_info[i].swap_window, + ANDROID_HANDLE_WINDOW); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + window_class.swap_buffers); + android_exception_check (); + } +} + +void +android_get_gc_values (struct android_gc *gc, + enum android_gc_value_mask mask, + struct android_gc_values *values) +{ + if (mask & ANDROID_GC_FOREGROUND) + /* GCs never have 32 bit colors, so we don't have to worry about + sign extension here. */ + values->foreground = gc->foreground; + + if (mask & ANDROID_GC_BACKGROUND) + values->background = gc->background; + + if (mask & ANDROID_GC_FUNCTION) + values->function = gc->function; + + if (mask & ANDROID_GC_CLIP_X_ORIGIN) + values->clip_x_origin = gc->clip_x_origin; + + if (mask & ANDROID_GC_CLIP_Y_ORIGIN) + values->clip_y_origin = gc->clip_y_origin; + + if (mask & ANDROID_GC_FILL_STYLE) + values->fill_style = gc->fill_style; + + if (mask & ANDROID_GC_TILE_STIP_X_ORIGIN) + values->ts_x_origin = gc->ts_x_origin; + + if (mask & ANDROID_GC_TILE_STIP_Y_ORIGIN) + values->ts_y_origin = gc->ts_y_origin; + + /* Fields involving handles are not used by Emacs, and thus not + implemented */ +} + +void +android_set_foreground (struct android_gc *gc, unsigned long foreground) +{ + struct android_gc_values gcv; + + gcv.foreground = foreground; + android_change_gc (gc, ANDROID_GC_FOREGROUND, &gcv); +} + +void +android_fill_rectangle (android_drawable handle, struct android_gc *gc, + int x, int y, unsigned int width, + unsigned int height) +{ + jobject drawable, gcontext; + + drawable = android_resolve_handle2 (handle, + ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.fill_rectangle, + drawable, + gcontext, + (jint) x, (jint) y, + (jint) width, + (jint) height); +} + +android_pixmap +android_create_pixmap_from_bitmap_data (char *data, unsigned int width, + unsigned int height, + unsigned long foreground, + unsigned long background, + unsigned int depth) +{ + android_pixmap pixmap; + jobject object; + AndroidBitmapInfo info; + unsigned int *depth_24; + unsigned char *depth_8; + void *bitmap_data; + unsigned int x, y; + unsigned int r, g, b; + + /* Create a pixmap with the right dimensions and depth. */ + pixmap = android_create_pixmap (width, height, depth); + + /* Lock the bitmap data. */ + bitmap_data = android_lock_bitmap (pixmap, &info, &object); + + /* Merely return if locking the bitmap fails. */ + if (!bitmap_data) + return pixmap; + + eassert (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 + || info.format == ANDROID_BITMAP_FORMAT_A_8); + + /* Begin copying each line. */ + + switch (info.format) + { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + + /* Swizzle the pixels into ABGR format. Android uses Skia's + ``native color type'', which is ABGR. This is despite the + format being named ``ARGB'', and more confusingly + `ANDROID_BITMAP_FORMAT_RGBA_8888' in bitmap.h. */ + + r = background & 0x00ff0000; + g = background & 0x0000ff00; + b = background & 0x000000ff; + background = (r >> 16) | g | (b << 16) | 0xff000000; + r = foreground & 0x00ff0000; + g = foreground & 0x0000ff00; + b = foreground & 0x000000ff; + foreground = (r >> 16) | g | (b << 16) | 0xff000000; + + for (y = 0; y < height; ++y) + { + depth_24 = (void *) ((char *) bitmap_data + y * info.stride); + + for (x = 0; x < width; ++x) + depth_24[x] = ((data[x / 8] & (1 << (x % 8))) + ? foreground : background); + + data += (width + 7) / 8; + } + + break; + + case ANDROID_BITMAP_FORMAT_A_8: + + /* 8-bit pixmaps are created, but in spite of that they are + employed only to represent bitmaps. */ + + foreground = (foreground ? 255 : 0); + background = (background ? 255 : 0); + + for (y = 0; y < height; ++y) + { + depth_8 = (void *) ((char *) bitmap_data + y * info.stride); + + for (x = 0; x < width; ++x) + depth_8[x] = ((data[x / 8] & (1 << (x % 8))) + ? foreground : background); + + data += (width + 7) / 8; + } + + break; + + default: + emacs_abort (); + } + + /* Unlock the bitmap itself. */ + AndroidBitmap_unlockPixels (android_java_env, object); + ANDROID_DELETE_LOCAL_REF (object); + + /* Return the pixmap. */ + return pixmap; +} + +void +android_set_clip_mask (struct android_gc *gc, android_pixmap pixmap) +{ + struct android_gc_values gcv; + + gcv.clip_mask = pixmap; + android_change_gc (gc, ANDROID_GC_CLIP_MASK, &gcv); +} + +void +android_set_fill_style (struct android_gc *gc, + enum android_fill_style fill_style) +{ + struct android_gc_values gcv; + + gcv.fill_style = fill_style; + android_change_gc (gc, ANDROID_GC_FILL_STYLE, &gcv); +} + + + +/* Pixmap bit blit implementation. This exists as `Canvas.drawBitmap' + seems to have trouble with copying bitmap data from one bitmap back + to itself on Android 8.0. */ + +/* Function called to actually perform the copy. */ + +typedef void (*android_blit_func) (int, int, int, int, int, int, + struct android_gc *, + unsigned char *, AndroidBitmapInfo *, + unsigned char *, AndroidBitmapInfo *, + unsigned char *, AndroidBitmapInfo *); + + + +#ifdef __aarch64__ + +/* Copy N pixels from SRC to DST, using MASK as a depth 1 clip + mask. */ + +static void +android_neon_mask_line (unsigned int *src, unsigned int *dst, + unsigned char *mask, int n) +{ + uint32x4_t src_low, src_high, dst_low, dst_high; + int16x8_t vmask; + int32x4_t ext_mask_low, ext_mask_high, low, high; + int rem, i; + + /* Calculate the remainder. */ + rem = n & 7, n &= ~7; + + /* Process eight pixels at a time. */ + + if (n) + { + again: + /* Load the low and high four pixels from the source. */ + src_low = vld1q_u32 (src); + src_high = vld1q_u32 (src + 4); + + /* Do the same with the destination. */ + dst_low = vld1q_u32 (dst); + dst_high = vld1q_u32 (dst + 4); + + /* Load and sign extend the mask. */ + vmask = vmovl_s8 (vld1_u8 (mask)); + ext_mask_low = vmovl_s16 (vget_low_s16 (vmask)); + ext_mask_high = vmovl_s16 (vget_high_s16 (vmask)); + + /* Reinterpret the mask. */ + low = vreinterpretq_u32_s32 (ext_mask_low); + high = vreinterpretq_u32_s32 (ext_mask_high); + + /* Apply the mask. */ + dst_low = vbicq_u32 (dst_low, low); + src_low = vandq_u32 (src_low, low); + dst_high = vbicq_u32 (dst_high, high); + src_high = vandq_u32 (src_high, high); + + /* Write the result after combining both masked vectors. */ + vst1q_u32 (dst, vorrq_u32 (dst_low, src_low)); + vst1q_u32 (dst + 4, vorrq_u32 (dst_high, src_high)); + + /* Adjust src, dst and mask. */ + dst += 8; + src += 8; + mask += 8; + + /* See if this loop should continue. */ + n -= 8; + if (n > 0) + goto again; + } + + /* Process the remaining pixels. */ + + for (i = 0; i < rem; ++i) + { + /* Sign extend the mask. */ + n = ((signed char *) mask)[i]; + + /* Combine src and dst. */ + dst[i] = ((src[i] & n) | (dst[i] & ~n)); + } +} + +#endif /* __aarch64__ */ + + + +/* Copy a rectangle SRC_X, SRC_Y, WIDTH and HEIGHT from SRC, described + by SRC_INFO, to DST_X and DST_Y in DST, as described by DST_INFO. + + If MASK is set, mask the source data using MASK_INFO, translating + it by GC->clip_x_origin and GC->clip_y_origin. MASK must be a + pixmap of depth 1. + + N.B. that currently only copies between bitmaps of depth 24 are + implemented. */ + +static void +android_blit_copy (int src_x, int src_y, int width, int height, + int dst_x, int dst_y, struct android_gc *gc, + unsigned char *src, AndroidBitmapInfo *src_info, + unsigned char *dst, AndroidBitmapInfo *dst_info, + unsigned char *mask, AndroidBitmapInfo *mask_info) +{ + uintptr_t start, end; + int mask_offset; + size_t pixel, offset, offset1; + unsigned char *src_current, *dst_current; + unsigned char *mask_current; + int overflow, temp, i; +#ifndef __aarch64__ + int j; +#endif /* __aarch64__ */ + bool backwards; + unsigned int *long_src, *long_dst; + + /* Assert that the specified coordinates are within bounds. */ + eassert (src_x >= 0 && src_y >= 0 + && dst_x >= 0 && dst_y >= 0); + eassert (src_x + width <= src_info->width); + eassert (src_y + height <= src_info->height); + eassert (dst_x + width <= dst_info->width); + eassert (dst_y + height <= dst_info->height); + + /* Now check that each bitmap has the correct format. */ + eassert (src_info->format == dst_info->format + && src_info->format == ANDROID_BITMAP_FORMAT_RGBA_8888); + pixel = sizeof (unsigned int); + + /* Android doesn't have A1 bitmaps, so A8 is used to represent + packed bitmaps of depth 1. */ + eassert (!mask || mask_info->format == ANDROID_BITMAP_FORMAT_A_8); + + /* Calculate the address of the first pixel of the first row to be + copied in both src and dst. Compare them to determine the + direction in which the copy is to take place. */ + + overflow = ckd_mul (&start, src_y, src_info->stride); + overflow |= ckd_mul (&end, src_x, pixel); + overflow |= ckd_add (&start, end, start); + overflow |= ckd_add (&start, (uintptr_t) src, start); + + if (overflow) + return; + + src_current = (unsigned char *) start; + + overflow = ckd_mul (&start, dst_y, dst_info->stride); + overflow |= ckd_mul (&end, dst_x, pixel); + overflow |= ckd_add (&start, end, start); + overflow |= ckd_add (&start, (uintptr_t) dst, start); + + if (overflow) + return; + + dst_current = (unsigned char *) start; + backwards = false; + + /* Now see if copying should proceed from the bottom up. */ + + if (src == dst && dst_current >= src_current) + { + backwards = true; + + /* Walk src and dst from bottom to top, in order to avoid + overlap. Calculate the coordinate of the last pixel of the + last row in both src and dst. */ + + overflow = ckd_mul (&start, src_y + height - 1, + src_info->stride); + + if (mask) + /* If a mask is set, put the pointers before the end of the + row. */ + overflow |= ckd_mul (&end, src_x + width - 1, pixel); + else + end = src_x * pixel; + + overflow |= ckd_add (&start, start, end); + overflow |= ckd_add (&start, (uintptr_t) src, start); + + if (overflow) + return; + + src_current = (unsigned char *) start; + + overflow = ckd_mul (&start, dst_y + height - 1, + dst_info->stride); + + if (mask) + /* If a mask is set, put the pointers before the end of the + row. */ + overflow |= ckd_mul (&end, dst_x + width - 1, pixel); + else + end = dst_x * pixel; + + overflow |= ckd_add (&start, start, end); + overflow |= ckd_add (&start, (uintptr_t) dst, start); + + if (overflow) + return; + + dst_current = (unsigned char *) start; + } + + if (!mask) + { + /* Change the direction of the copy depending on how SRC and DST + overlap. */ + + for (i = 0; i < height; ++i) + { + memmove (dst_current, src_current, + width * pixel); + + if (backwards) + { + /* Proceed to the last row. */ + src_current -= src_info->stride; + dst_current -= dst_info->stride; + } + else + { + /* Proceed to the next row. */ + src_current += src_info->stride; + dst_current += dst_info->stride; + } + } + } + else + { + /* Adjust the source and destination Y. The start is MAX + (dst_y, gc->clip_y_origin); the difference between that value + and dst_y is the offset to apply to src_y. */ + + temp = dst_y; + dst_y = MAX (dst_y, gc->clip_y_origin); + src_y += dst_y - temp; + height -= dst_y - temp; + + /* Verify that the bounds are correct. */ + eassert (dst_y + height + <= gc->clip_y_origin + mask_info->height); + eassert (dst_y >= gc->clip_y_origin); + + /* There is a mask. For each scan line... */ + + if (backwards) + { + /* Calculate the number of pixels at the end of the + mask. */ + + mask_offset = dst_x + width; + mask_offset -= mask_info->width + gc->clip_x_origin; + + if (mask_offset < 0) + mask_offset = 0; + + /* Calculate the last column of the mask that will be + consulted. */ + + temp = dst_x - gc->clip_x_origin; + temp += MIN (mask_info->width - temp, + width - mask_offset); + + if (temp < 0) + return; + + /* Now calculate the last row of the mask that will be + consulted. */ + i = dst_y - gc->clip_y_origin + height; + + /* Turn both into offsets. */ + + if (ckd_mul (&offset, temp, pixel) + || ckd_mul (&offset1, i, mask_info->stride) + || ckd_add (&offset, offset, offset1) + || ckd_add (&start, (uintptr_t) mask, offset)) + return; + + if (height <= 0) + return; + + mask = mask_current = (unsigned char *) start; + + while (height--) + { + /* Skip backwards past the end of the mask. */ + + long_src = (unsigned int *) (src_current - mask_offset * pixel); + long_dst = (unsigned int *) (dst_current - mask_offset * pixel); + mask = mask_current; + + /* For each pixel covered by the mask... */ + temp = MIN (mask_info->width - temp, width - mask_offset); + while (temp--) + { + /* Copy the destination it to the source, masked by + the mask. */ + + /* Sign extend the mask. */ + i = *(signed char *) mask--; + + /* Apply the mask. */ + *long_dst = ((*long_src & i) | (*long_dst & ~i)); + + long_dst--; + long_src--; + } + + /* Return to the last row. */ + src_current -= src_info->stride; + dst_current -= dst_info->stride; + mask_current -= mask_info->stride; + } + } + else + { + /* Calculate the first column of the mask that will be + consulted. */ + + mask_offset = dst_x - gc->clip_x_origin; + + /* Adjust the mask by that much. */ + + if (mask_offset > 0) + mask += mask_offset; + else + { + /* Offset src and dst by the mask offset. */ + src_current += -mask_offset * pixel; + dst_current += -mask_offset * pixel; + width += mask_offset; + } + + /* Make sure it's not out of bounds. */ + + eassert (dst_y - gc->clip_y_origin >= 0); + if ((dst_y - gc->clip_y_origin) + height > mask_info->height + || width <= 0) + return; + + /* Now move mask to the position of the first row. */ + + mask += ((dst_y - gc->clip_y_origin) + * mask_info->stride); + + /* Determine how many bytes need to be copied. */ + + if (mask_offset > 0) + temp = MIN (mask_info->width - mask_offset, width); + else + temp = MIN (mask_info->width, width); + + if (temp <= 0 || height <= 0) + return; + + /* Copy bytes according to the mask. */ + + while (height--) + { + long_src = (unsigned int *) src_current; + long_dst = (unsigned int *) dst_current; + mask_current = mask; + +#ifndef __aarch64__ + for (j = 0; j < temp; ++j) + { + /* Sign extend the mask. */ + i = *(signed char *) mask_current++; + + /* Apply the mask. */ + *long_dst = ((*long_src & i) | (*long_dst & ~i)); + long_dst++; + long_src++; + } +#else /* __aarch64__ */ + android_neon_mask_line (long_src, long_dst, mask, temp); +#endif /* __aarch64__ */ + + src_current += src_info->stride; + dst_current += dst_info->stride; + mask += mask_info->stride; + } + } + } +} + + +/* Xor a rectangle SRC_X, SRC_Y, WIDTH and HEIGHT from SRC, described + by SRC_INFO, to DST_X and DST_Y in DST, as described by DST_INFO. + + Ignore the alpha channel when computing the exclusive-or of the + destination pixel. + + If MASK is set, mask the source data using MASK_INFO, translating + it by GC->clip_x_origin and GC->clip_y_origin. MASK must be a + pixmap of depth 1. + + N.B. that currently only copies between bitmaps of depth 24 are + implemented. */ + +static void +android_blit_xor (int src_x, int src_y, int width, int height, + int dst_x, int dst_y, struct android_gc *gc, + unsigned char *src, AndroidBitmapInfo *src_info, + unsigned char *dst, AndroidBitmapInfo *dst_info, + unsigned char *mask, AndroidBitmapInfo *mask_info) +{ +#if 0 + uintptr_t start, end; + int mask_offset; + size_t pixel, offset, offset1; + unsigned char *src_current, *dst_current; + unsigned char *mask_current; + int overflow, temp, i; + bool backwards; + unsigned int *long_src, *long_dst; +#endif /* 0 */ + + /* Note that this alu hasn't been tested -- it probably does not + work! */ + emacs_abort (); + +#if 0 + /* Assert that the specified coordinates are within bounds. */ + eassert (src_x >= 0 && src_y >= 0 + && dst_x >= 0 && dst_y >= 0); + eassert (src_x + width <= src_info->width); + eassert (src_y + height <= src_info->height); + eassert (dst_x + width <= dst_info->width); + eassert (dst_y + height <= dst_info->height); + + /* Now check that each bitmap has the correct format. */ + eassert (src_info->format == dst_info->format + && src_info->format == ANDROID_BITMAP_FORMAT_RGBA_8888); + pixel = sizeof (unsigned int); + + /* Android doesn't have A1 bitmaps, so A8 is used to represent + packed bitmaps of depth 1. */ + eassert (!mask || mask_info->format == ANDROID_BITMAP_FORMAT_A_8); + + /* Calculate the address of the first pixel of the first row to be + copied in both src and dst. Compare them to determine the + direction in which the copy is to take place. */ + + overflow = ckd_mul (&start, src_y, src_info->stride); + overflow |= ckd_mul (&end, src_x, pixel); + overflow |= ckd_add (&start, (uintptr_t) src, start); + + if (overflow) + return; + + src_current = (unsigned char *) start; + + overflow = ckd_mul (&start, dst_y, src_info->stride); + overflow |= ckd_mul (&end, dst_x, pixel); + overflow |= ckd_add (&start, (uintptr_t) dst, start); + + if (overflow) + return; + + dst_current = (unsigned char *) start; + backwards = false; + + /* Now see if copying should proceed from the bottom up. */ + + if (src == dst && dst_current >= src_current) + { + backwards = true; + + /* Walk src and dst from bottom to top, in order to avoid + overlap. Calculate the coordinate of the last pixel of the + last row in both src and dst. */ + + overflow = ckd_mul (&start, src_y + height - 1, + src_info->stride); + if (mask) /* If a mask is set, put the pointers before the end + of the row. */ + overflow |= ckd_mul (&end, src_x + width - 1, pixel); + else + overflow |= ckd_mul (&end, src_x, pixel); + overflow |= ckd_add (&start, start, end); + overflow |= ckd_add (&start, (uintptr_t) src, start); + + if (overflow) + return; + + src_current = (unsigned char *) start; + + overflow = ckd_mul (&start, dst_y + height - 1, + dst_info->stride); + if (mask) /* If a mask is set, put the pointers before the end + of the row. */ + overflow |= ckd_mul (&end, dst_x + width - 1, pixel); + else + overflow |= ckd_mul (&end, dst_x, pixel); + overflow |= ckd_add (&start, start, end); + overflow |= ckd_add (&start, (uintptr_t) dst, start); + + if (overflow) + return; + + dst_current = (unsigned char *) start; + } + + if (!mask) + { + /* Change the direction of the copy depending on how SRC and DST + overlap. */ + + for (i = 0; i < height; ++i) + { + if (backwards) + { + for (i = width - 1; i <= 0; --i) + (((unsigned int *) dst_current)[i]) + /* Keep the alpha channel intact. */ + ^= (((unsigned int *) src_current)[i]) & 0xffffff; + + /* Proceed to the last row. */ + src_current -= src_info->stride; + dst_current -= dst_info->stride; + } + else + { + for (i = 0; i < width; ++i) + (((unsigned int *) dst_current)[i]) + /* Keep the alpha channel intact. */ + ^= (((unsigned int *) src_current)[i]) & 0xffffff; + + /* Proceed to the next row. */ + src_current += src_info->stride; + dst_current += dst_info->stride; + } + } + } + else + { + /* Adjust the source and destination Y. The start is MAX + (dst_y, gc->clip_y_origin); the difference between that value + and dst_y is the offset to apply to src_y. */ + + temp = dst_y; + dst_y = MAX (dst_y, gc->clip_y_origin); + src_y += dst_y - temp; + height -= dst_y - temp; + + /* Verify that the bounds are correct. */ + eassert (dst_y + height + <= gc->clip_y_origin + mask_info->height); + eassert (dst_y >= gc->clip_y_origin); + + /* There is a mask. For each scan line... */ + + if (backwards) + { + /* Calculate the number of pixels at the end of the + mask. */ + + mask_offset = dst_x + width; + mask_offset -= mask_info->width + gc->clip_x_origin; + + if (mask_info < 0) + mask_info = 0; + + /* Calculate the last column of the mask that will be + consulted. */ + + temp = dst_x - gc->clip_x_origin; + temp += MIN (mask_info->width - temp, + width - mask_offset); + + if (temp < 0) + return; + + /* Now calculate the last row of the mask that will be + consulted. */ + i = dst_y - gc->clip_y_origin + height; + + /* Turn both into offsets. */ + + if (ckd_mul (&offset, temp, pixel) + || ckd_mul (&offset1, i, mask_info->stride) + || ckd_add (&offset, offset, offset1) + || ckd_add (&start, (uintptr_t) mask, offset)) + return; + + mask = mask_current = (unsigned char *) start; + + for (i = 0; i < height; ++i) + { + /* Skip backwards past the end of the mask. */ + + long_src = (unsigned int *) (src_current - mask_offset * pixel); + long_dst = (unsigned int *) (dst_current - mask_offset * pixel); + mask = mask_current; + + /* For each pixel covered by the mask... */ + temp = MIN (mask_info->width - temp, width - mask_offset); + while (temp--) + /* XOR the source to the destination, masked by the + mask. */ + *long_dst-- ^= ((*(long_src--) & (0u - (*(mask--) & 1))) + & 0xffffff); + + /* Return to the last row. */ + src_current -= src_info->stride; + dst_current -= dst_info->stride; + mask_current -= mask_info->stride; + } + } + else + { + /* Calculate the first column of the mask that will be + consulted. */ + + mask_offset = dst_x - gc->clip_x_origin; + + /* Adjust the mask by that much. */ + + if (mask_offset > 0) + mask += mask_offset; + else + { + /* Offset src and dst by the mask offset. */ + src_current += -mask_offset * pixel; + dst_current += -mask_offset * pixel; + width -= mask_offset; + } + + /* Now move mask to the position of the first row. */ + + mask += gc->clip_y_origin * mask_info->stride; + + for (i = 0; i < height; ++i) + { + long_src = (unsigned int *) src_current; + long_dst = (unsigned int *) dst_current; + mask_current = mask; + + if (mask_offset > 0) + { + /* Copy bytes according to the mask. */ + temp = MIN (mask_info->width - mask_offset, width); + while (temp--) + *long_dst++ ^= ((*(long_src++) + & (0u - (*(mask_current++) & 1))) + & 0xffffff); + } + else + { + /* Copy bytes according to the mask. */ + temp = MIN (mask_info->width, width); + while (temp--) + *long_dst++ = ((*(long_src++) + & (0u - (*(mask_current++) & 1))) + & 0xffffff); + } + + src_current += src_info->stride; + dst_current += dst_info->stride; + mask += mask_info->stride; + } + } + } +#endif /* 0 */ +} + +void +android_copy_area (android_drawable src, android_drawable dest, + struct android_gc *gc, int src_x, int src_y, + unsigned int width, unsigned int height, + int dest_x, int dest_y) +{ + jobject src_object, dest_object, mask; + android_blit_func do_blit; + AndroidBitmapInfo src_info, dest_info, mask_info; + void *src_data, *dest_data, *mask_data; + int n_clip_rects, i; + bool flag; + struct android_rectangle bounds, rect, temp, *clip_rectangles; + + /* Perform the copy. Loop over each clip rectangle, unless none are + set. Also, obtain bitmaps for src and dst, and possibly the mask + as well if it is present. */ + + src_data = android_lock_bitmap (src, &src_info, &src_object); + if (!src_data) + return; + + mask_data = mask = NULL; + + if (src != dest) + { + dest_data = android_lock_bitmap (dest, &dest_info, &dest_object); + if (!dest_data) + goto fail; + } + else + { + dest_data = src_data; + dest_info = src_info; + } + + /* Obtain the bitmap for the mask if necessary. */ + + if (gc->clip_mask) + { + mask_data = android_lock_bitmap (gc->clip_mask, + &mask_info, &mask); + if (!mask_data) + goto fail1; + } + + /* Calculate the number of clip rectangles. */ + n_clip_rects = gc->num_clip_rects; + + /* If n_clip_rects is -1, then no clipping is in effect. Set rect + to the bounds of the destination. */ + + flag = n_clip_rects == -1; + if (flag) + { + n_clip_rects = 1; + clip_rectangles = ▭ + } + else if (!n_clip_rects) + goto fail2; + else + clip_rectangles = gc->clip_rects; + + /* Set rect to the bounds of the destination. */ + + rect.x = 0; + rect.y = 0; + rect.width = dest_info.width; + rect.height = dest_info.height; + + if (mask_data) + { + /* Clip width and height to that of the mask. */ + + if (src_x + width > mask_info.width) + width = mask_info.width - src_x; + + if (src_y + height > mask_info.height) + height = mask_info.height - src_y; + } + + /* Clip width and height to that of the source. */ + + if (src_x + width > src_info.width) + width = src_info.width - src_x; + + if (src_y + height > src_info.height) + height = src_info.height - src_y; + + /* Return if the copy is outside the source. */ + + if (width <= 0 || height <= 0) + goto fail2; + + /* Look up the right function for the alu. */ + + switch (gc->function) + { + case ANDROID_GC_COPY: + do_blit = android_blit_copy; + break; + + case ANDROID_GC_XOR: + do_blit = android_blit_xor; + break; + + default: + emacs_abort (); + } + + /* Load the bounds of the destination rectangle. */ + bounds.x = dest_x; + bounds.y = dest_y; + bounds.width = width; + bounds.height = height; + + /* For each clip rectangle... */ + for (i = 0; i < n_clip_rects; ++i) + { + /* Calculate its intersection with the destination + rectangle. */ + + if (!gui_intersect_rectangles (&clip_rectangles[i], &bounds, + &temp)) + continue; + + /* And that of the destination itself. */ + + if (!flag && !gui_intersect_rectangles (&temp, &rect, &temp)) + continue; + + /* Now perform the copy. */ + (*do_blit) (src_x + temp.x - dest_x, /* temp.x relative to src_x */ + src_y + temp.y - dest_y, /* temp.y relative to src_y */ + temp.width, /* Width of area to copy. */ + temp.height, /* Height of area to copy. */ + temp.x, temp.y, /* Coordinates to copy to. */ + gc, /* GC. */ + src_data, &src_info, /* Source drawable. */ + dest_data, &dest_info, /* Destination drawable. */ + mask_data, &mask_info); /* Mask drawable. */ + } + + /* Now damage the destination drawable accordingly, should it be a + window. */ + + if (android_handles[dest].type == ANDROID_HANDLE_WINDOW) + android_damage_window (dest, &bounds); + + fail2: + if (mask) + { + AndroidBitmap_unlockPixels (android_java_env, mask); + ANDROID_DELETE_LOCAL_REF (mask); + } + fail1: + if (src != dest) + { + AndroidBitmap_unlockPixels (android_java_env, dest_object); + ANDROID_DELETE_LOCAL_REF (dest_object); + } + fail: + AndroidBitmap_unlockPixels (android_java_env, src_object); + ANDROID_DELETE_LOCAL_REF (src_object); +} + + + +void +android_free_pixmap (android_pixmap pixmap) +{ + android_destroy_handle (pixmap); +} + +void +android_set_background (struct android_gc *gc, unsigned long background) +{ + struct android_gc_values gcv; + + gcv.background = background; + android_change_gc (gc, ANDROID_GC_BACKGROUND, &gcv); +} + +void +android_fill_polygon (android_drawable drawable, struct android_gc *gc, + struct android_point *points, int npoints, + enum android_shape shape, enum android_coord_mode mode) +{ + jobjectArray array; + jobject point, drawable_object, gcontext; + int i; + + drawable_object = android_resolve_handle2 (drawable, + ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + array = (*android_java_env)->NewObjectArray (android_java_env, + npoints, + point_class.class, + NULL); + android_exception_check (); + + for (i = 0; i < npoints; ++i) + { + point = (*android_java_env)->NewObject (android_java_env, + point_class.class, + point_class.constructor, + (jint) points[i].x, + (jint) points[i].y); + android_exception_check_1 (array); + + (*android_java_env)->SetObjectArrayElement (android_java_env, + array, i, point); + ANDROID_DELETE_LOCAL_REF (point); + } + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.fill_polygon, + drawable_object, + gcontext, array); + android_exception_check_1 (array); + ANDROID_DELETE_LOCAL_REF (array); +} + +void +android_draw_rectangle (android_drawable handle, struct android_gc *gc, + int x, int y, unsigned int width, unsigned int height) +{ + jobject drawable, gcontext; + + drawable = android_resolve_handle2 (handle, + ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.draw_rectangle, + drawable, gcontext, + (jint) x, (jint) y, + (jint) width, (jint) height); + + /* In lieu of android_exception_check, clear all exceptions after + calling this frequently called graphics operation. */ + (*android_java_env)->ExceptionClear (android_java_env); +} + +void +android_draw_point (android_drawable handle, struct android_gc *gc, + int x, int y) +{ + jobject drawable, gcontext; + + drawable = android_resolve_handle2 (handle, + ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.draw_point, + drawable, gcontext, + (jint) x, (jint) y); + + /* In lieu of android_exception_check, clear all exceptions after + calling this frequently called graphics operation. */ + (*android_java_env)->ExceptionClear (android_java_env); +} + +void +android_draw_line (android_drawable handle, struct android_gc *gc, + int x, int y, int x2, int y2) +{ + jobject drawable, gcontext; + + drawable = android_resolve_handle2 (handle, + ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + gcontext = android_resolve_handle (gc->gcontext, + ANDROID_HANDLE_GCONTEXT); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.draw_line, + drawable, gcontext, + (jint) x, (jint) y, + (jint) x2, (jint) y2); + + /* In lieu of android_exception_check, clear all exceptions after + calling this frequently called graphics operation. */ + (*android_java_env)->ExceptionClear (android_java_env); +} + +android_pixmap +android_create_pixmap (unsigned int width, unsigned int height, + int depth) +{ + android_handle prev_max_handle; + jobject object; + android_pixmap pixmap; + + /* First, allocate the pixmap handle. */ + prev_max_handle = max_handle; + pixmap = android_alloc_id (); + + if (!pixmap) + error ("Out of pixmap handles!"); + + object = (*android_java_env)->NewObject (android_java_env, + pixmap_class.class, + pixmap_class.constructor_mutable, + (jshort) pixmap, + (jint) width, (jint) height, + (jint) depth); + + if (!object) + { + (*android_java_env)->ExceptionClear (android_java_env); + max_handle = prev_max_handle; + memory_full (0); + } + + android_handles[pixmap].type = ANDROID_HANDLE_PIXMAP; + android_handles[pixmap].handle + = (*android_java_env)->NewGlobalRef (android_java_env, object); + (*android_java_env)->ExceptionClear (android_java_env); + ANDROID_DELETE_LOCAL_REF (object); + + if (!android_handles[pixmap].handle) + memory_full (0); + + return pixmap; +} + +void +android_set_ts_origin (struct android_gc *gc, int x, int y) +{ + struct android_gc_values gcv; + + gcv.ts_x_origin = x; + gcv.ts_y_origin = y; + android_change_gc (gc, (ANDROID_GC_TILE_STIP_X_ORIGIN + | ANDROID_GC_TILE_STIP_Y_ORIGIN), + &gcv); +} + +void +android_clear_area (android_window handle, int x, int y, + unsigned int width, unsigned int height) +{ + jobject window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + window_class.clear_area, + (jint) x, (jint) y, + (jint) width, (jint) height); +} + +android_pixmap +android_create_bitmap_from_data (char *bits, unsigned int width, + unsigned int height) +{ + return android_create_pixmap_from_bitmap_data (bits, 1, 0, + width, height, 1); +} + +struct android_image * +android_create_image (unsigned int depth, enum android_image_format format, + char *data, unsigned int width, unsigned int height) +{ + struct android_image *image; + + image = xmalloc (sizeof *image); + + /* Fill in the fields required by image.c. N.B. that + android_destroy_image ostensibly will free data, but image.c + mostly sets and frees data itself. */ + image->width = width; + image->height = height; + image->data = data; + image->depth = depth; + image->format = format; + + /* Now fill in the image dimensions. There are only two depths + supported by this function. */ + + if (depth == 1) + { + image->bytes_per_line = (width + 7) / 8; + image->bits_per_pixel = 1; + } + else if (depth == 24) + { + image->bytes_per_line = width * 4; + image->bits_per_pixel = 32; + } + else + emacs_abort (); + + return image; +} + +void +android_destroy_image (struct android_image *ximg) +{ + /* If XIMG->data is NULL, then it has already been freed by + image.c. */ + + if (ximg->data) + xfree (ximg->data); + xfree (ximg); +} + +void +android_put_pixel (struct android_image *ximg, int x, int y, + unsigned long pixel) +{ + char *byte, *word; + unsigned int r, g, b; + unsigned int pixel_int; + + /* Ignore out-of-bounds accesses. */ + + if (x >= ximg->width || y >= ximg->height || x < 0 || y < 0) + return; + + switch (ximg->depth) + { + case 1: + byte = ximg->data + y * ximg->bytes_per_line + x / 8; + + if (pixel) + *byte |= (1 << x % 8); + else + *byte &= ~(1 << x % 8); + break; + + case 24: + /* Unaligned accesses are problematic on Android devices. */ + word = ximg->data + y * ximg->bytes_per_line + x * 4; + + /* Swizzle the pixel into ABGR format. Android uses Skia's + ``native color type'', which is ABGR. This is despite the + format being named ``ARGB'', and more confusingly + `ANDROID_BITMAP_FORMAT_RGBA_8888' in bitmap.h. */ + r = pixel & 0x00ff0000; + g = pixel & 0x0000ff00; + b = pixel & 0x000000ff; + pixel = (r >> 16) | g | (b << 16) | 0xff000000; + + pixel_int = pixel; + memcpy (word, &pixel_int, sizeof pixel_int); + break; + } +} + +unsigned long +android_get_pixel (struct android_image *ximg, int x, int y) +{ + char *byte, *word; + unsigned int pixel, r, g, b; + + if (x >= ximg->width || y >= ximg->height + || x < 0 || y < 0) + return 0; + + switch (ximg->depth) + { + case 1: + byte = ximg->data + y * ximg->bytes_per_line + x / 8; + return (*byte & (1 << x % 8)) ? 1 : 0; + + case 24: + word = ximg->data + y * ximg->bytes_per_line + x * 4; + memcpy (&pixel, word, sizeof pixel); + + /* Convert the pixel back to RGB. */ + b = pixel & 0x00ff0000; + g = pixel & 0x0000ff00; + r = pixel & 0x000000ff; + pixel = ((r << 16) | g | (b >> 16)) & ~0xff000000; + + return pixel; + } + + emacs_abort (); +} + +struct android_image * +android_get_image (android_drawable handle, + enum android_image_format format) +{ + jobject drawable, bitmap; + AndroidBitmapInfo bitmap_info; + size_t byte_size; + void *data; + struct android_image *image; + unsigned char *data1, *data2; + int i, x; + + drawable = android_resolve_handle2 (handle, ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + + /* Look up the drawable and get the bitmap corresponding to it. + Then, lock the bitmap's bits. */ + bitmap = (*android_java_env)->CallObjectMethod (android_java_env, + drawable, + drawable_class.get_bitmap); + android_exception_check (); + + /* Clear the bitmap info structure. */ + memset (&bitmap_info, 0, sizeof bitmap_info); + + /* The NDK doc seems to imply this function can fail but doesn't say + what value it gives when it does! */ + AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info); + + if (!bitmap_info.stride) + { + ANDROID_DELETE_LOCAL_REF (bitmap); + memory_full (0); + } + + /* Compute how big the image data will be. Fail if it would be too + big. */ + + if (bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8) + { + if (ckd_mul (&byte_size, + (size_t) bitmap_info.stride, + (size_t) bitmap_info.height)) + { + ANDROID_DELETE_LOCAL_REF (bitmap); + memory_full (0); + } + } + else + /* This A8 image will be packed into A1 later on. */ + byte_size = (bitmap_info.width + 7) / 8; + + /* Lock the image data. Once again, the NDK documentation says the + call can fail, but does not say how to determine whether or not + it has failed, nor how the address is aligned. */ + data = NULL; + AndroidBitmap_lockPixels (android_java_env, bitmap, &data); + + if (!data) + { + /* Take a NULL pointer to mean that AndroidBitmap_lockPixels + failed. */ + ANDROID_DELETE_LOCAL_REF (bitmap); + memory_full (0); + } + + /* Copy the data into a new struct android_image. */ + image = xmalloc (sizeof *image); + image->width = bitmap_info.width; + image->height = bitmap_info.height; + image->data = malloc (byte_size); + + if (!image->data) + { + ANDROID_DELETE_LOCAL_REF (bitmap); + xfree (image); + memory_full (byte_size); + } + + /* Use the format of the bitmap to determine the image depth. */ + switch (bitmap_info.format) + { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + image->depth = 24; + image->bits_per_pixel = 32; + break; + + /* A8 images are used by Emacs to represent bitmaps. They have + to be packed manually. */ + case ANDROID_BITMAP_FORMAT_A_8: + image->depth = 1; + image->bits_per_pixel = 1; + break; + + /* Other formats are currently not supported. */ + default: + emacs_abort (); + } + + image->format = format; + + if (image->depth == 24) + { + image->bytes_per_line = bitmap_info.stride; + + /* Copy the bitmap data over. */ + memcpy (image->data, data, byte_size); + } + else + { + /* Pack the A8 image data into bits manually. */ + image->bytes_per_line = (image->width + 7) / 8; + + data1 = (unsigned char *) image->data; + data2 = data; + + for (i = 0; i < image->height; ++i) + { + for (x = 0; x < image->width; ++x) + /* Some bits in data1 might be initialized at this point, + but they will all be set properly later. */ + data1[x / 8] = (data2[x] + ? (data1[x / 8] | (1 << (x % 8))) + : (data1[x / 8] & ~(1 << (x % 8)))); + + data1 += image->bytes_per_line; + data2 += bitmap_info.stride; + } + } + + /* Unlock the bitmap pixels. */ + AndroidBitmap_unlockPixels (android_java_env, bitmap); + + /* Delete the bitmap reference. */ + ANDROID_DELETE_LOCAL_REF (bitmap); + return image; +} + +void +android_put_image (android_pixmap handle, struct android_image *image) +{ + jobject drawable, bitmap; + AndroidBitmapInfo bitmap_info; + void *data; + unsigned char *data_1, *data_2; + int i, x; + + drawable = android_resolve_handle (handle, ANDROID_HANDLE_PIXMAP); + + /* Look up the drawable and get the bitmap corresponding to it. + Then, lock the bitmap's bits. */ + bitmap = (*android_java_env)->CallObjectMethod (android_java_env, + drawable, + drawable_class.get_bitmap); + android_exception_check (); + + /* Clear the bitmap info structure. */ + memset (&bitmap_info, 0, sizeof bitmap_info); + + /* The NDK doc seems to imply this function can fail but doesn't say + what value it gives when it does! */ + AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info); + + if (!bitmap_info.stride) + { + ANDROID_DELETE_LOCAL_REF (bitmap); + memory_full (0); + } + + if (bitmap_info.width != image->width + || bitmap_info.height != image->height) + /* This is not yet supported. */ + emacs_abort (); + + /* Make sure the bitmap formats are compatible with each other. */ + + if ((image->depth == 24 + && bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) + || (image->depth == 1 + && bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8)) + emacs_abort (); + + /* Lock the image data. Once again, the NDK documentation says the + call can fail, but does not say how to determine whether or not + it has failed, nor how the address is aligned. */ + data = NULL; + AndroidBitmap_lockPixels (android_java_env, bitmap, &data); + + if (!data) + { + /* Take a NULL pointer to mean that AndroidBitmap_lockPixels + failed. */ + ANDROID_DELETE_LOCAL_REF (bitmap); + memory_full (0); + } + + data_1 = data; + data_2 = (unsigned char *) image->data; + + /* Copy the bitmap data over scanline-by-scanline. */ + for (i = 0; i < image->height; ++i) + { + if (image->depth != 1) + memcpy (data_1, data_2, + image->width * (image->bits_per_pixel / 8)); + else + { + /* Android internally uses a 1 byte-per-pixel format for + ALPHA_8 images. Expand the image from the 1 + bit-per-pixel X format correctly. */ + + for (x = 0; x < image->width; ++x) + data_1[x] = (data_2[x / 8] & (1 << x % 8)) ? 0xff : 0; + } + + data_1 += bitmap_info.stride; + data_2 += image->bytes_per_line; + } + + /* Unlock the bitmap pixels. */ + AndroidBitmap_unlockPixels (android_java_env, bitmap); + + /* Delete the bitmap reference. */ + ANDROID_DELETE_LOCAL_REF (bitmap); +} + +void +android_bell (void) +{ + jint duration; + + /* Restrict android_keyboard_bell_duration to values between 10 and + 1000. */ + duration = MIN (1000, MAX (0, android_keyboard_bell_duration)); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.ring_bell, + duration); + android_exception_check (); +} + +void +android_set_input_focus (android_window handle, unsigned long time) +{ + jobject window; + jmethodID make_input_focus; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + make_input_focus = window_class.make_input_focus; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + make_input_focus, + (jlong) time); + android_exception_check (); +} + +void +android_raise_window (android_window handle) +{ + jobject window; + jmethodID raise; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + raise = window_class.raise; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + raise); + android_exception_check (); +} + +void +android_lower_window (android_window handle) +{ + jobject window; + jmethodID lower; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + lower = window_class.lower; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + lower); + android_exception_check (); +} + +void +android_reconfigure_wm_window (android_window handle, + enum android_wc_value_mask value_mask, + struct android_window_changes *values) +{ + jobject sibling, window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + if (!(value_mask & ANDROID_CW_STACK_MODE)) + return; + + /* If value_mask & ANDROID_CW_SIBLING, place HANDLE above or below + values->sibling pursuant to values->stack_mode; else, reposition + it at the top or the bottom of its parent. */ + + sibling = NULL; + + if (value_mask & ANDROID_CW_SIBLING) + sibling = android_resolve_handle (values->sibling, + ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + window_class.reconfigure, + sibling, + (jint) values->stack_mode); + android_exception_check (); +} + +int +android_query_tree (android_window handle, android_window *root_return, + android_window *parent_return, + android_window **children_return, + unsigned int *nchildren_return) +{ + jobject window, array; + jsize nelements, i; + android_window *children; + jshort *shorts; + jmethodID method; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + /* window can be NULL, so this is a service method. */ + method = service_class.query_tree; + array + = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env, + emacs_service, + service_class.class, + method, window); + android_exception_check (); + + /* The first element of the array is the parent window. The rest + are the children. */ + nelements = (*android_java_env)->GetArrayLength (android_java_env, + array); + eassert (nelements); + + /* Now fill in the children. */ + children = xnmalloc (nelements - 1, sizeof *children); + + shorts + = (*android_java_env)->GetShortArrayElements (android_java_env, array, + NULL); + android_exception_check_nonnull (shorts, array); + + for (i = 1; i < nelements; ++i) + /* Subtract one from the index into children, since the parent is + not included. */ + children[i - 1] = shorts[i]; + + /* Finally, return the parent and other values. */ + *root_return = 0; + *parent_return = shorts[0]; + *children_return = children; + *nchildren_return = nelements - 1; + + /* Release the array contents. */ + (*android_java_env)->ReleaseShortArrayElements (android_java_env, array, + shorts, JNI_ABORT); + + ANDROID_DELETE_LOCAL_REF (array); + return 1; +} + +void +android_get_geometry (android_window handle, + android_window *root_return, + int *x_return, int *y_return, + unsigned int *width_return, + unsigned int *height_return, + unsigned int *border_width_return) +{ + jobject window; + jarray window_geometry; + jmethodID get_geometry; + jint *ints; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + get_geometry = window_class.get_window_geometry; + + window_geometry + = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env, + window, + window_class.class, + get_geometry); + android_exception_check (); + + /* window_geometry is an array containing x, y, width and + height. border_width is always 0 on Android. */ + eassert ((*android_java_env)->GetArrayLength (android_java_env, + window_geometry) + == 4); + + *root_return = 0; + *border_width_return = 0; + + ints + = (*android_java_env)->GetIntArrayElements (android_java_env, + window_geometry, + NULL); + android_exception_check_nonnull (ints, window_geometry); + + *x_return = ints[0]; + *y_return = ints[1]; + *width_return = ints[2]; + *height_return = ints[3]; + + (*android_java_env)->ReleaseIntArrayElements (android_java_env, + window_geometry, + ints, JNI_ABORT); + + /* Now free the local reference. */ + ANDROID_DELETE_LOCAL_REF (window_geometry); +} + +void +android_move_resize_window (android_window window, int x, int y, + unsigned int width, unsigned int height) +{ + android_move_window (window, x, y); + android_resize_window (window, width, height); +} + +void +android_map_raised (android_window window) +{ + android_raise_window (window); + android_map_window (window); +} + +void +android_translate_coordinates (android_window src, int x, + int y, int *root_x, int *root_y) +{ + jobject window; + jarray coordinates; + jmethodID method; + jint *ints; + + window = android_resolve_handle (src, ANDROID_HANDLE_WINDOW); + method = window_class.translate_coordinates; + coordinates + = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env, + window, + window_class.class, + method, (jint) x, + (jint) y); + android_exception_check (); + + /* The array must contain two elements: X, Y translated to the root + window. */ + eassert ((*android_java_env)->GetArrayLength (android_java_env, + coordinates) + == 2); + + /* Obtain the coordinates from the array. */ + ints = (*android_java_env)->GetIntArrayElements (android_java_env, + coordinates, NULL); + android_exception_check_nonnull (ints, coordinates); + + *root_x = ints[0]; + *root_y = ints[1]; + + /* Release the coordinates. */ + (*android_java_env)->ReleaseIntArrayElements (android_java_env, + coordinates, ints, + JNI_ABORT); + + /* And free the local reference. */ + ANDROID_DELETE_LOCAL_REF (coordinates); +} + +/* Return the character produced by combining the diacritic character + DCHAR with the key-producing character C in *VALUE. Value is 1 if + there is no character for this combination, 0 otherwise. */ + +static int +android_get_dead_char (unsigned int dchar, unsigned int c, + unsigned int *value) +{ + jmethodID method; + jclass class; + jint result; + + /* Call getDeadChar. */ + class = key_character_map_class.class; + method = key_character_map_class.get_dead_char; + result = (*android_java_env)->CallStaticIntMethod (android_java_env, + class, method, + (jint) dchar, + (jint) c); + + if (result) + { + *value = result; + return 0; + } + + return 1; +} + +/* Return a Unicode string in BUFFER_RETURN, a buffer of size + WCHARS_BUFFER, from the key press event EVENT, much like + XmbLookupString. If EVENT represents a key press without a + corresponding Unicode character, return its keysym in *KEYSYM_RETURN. + Return the action taken in *STATUS_RETURN. + + COMPOSE_STATUS, if non-NULL, should point to a structure for + temporary information to be stored in during dead key + composition. */ + +int +android_wc_lookup_string (android_key_pressed_event *event, + wchar_t *buffer_return, int wchars_buffer, + int *keysym_return, + enum android_lookup_status *status_return, + struct android_compose_status *compose_status) +{ + enum android_lookup_status status; + int rc; + jobject window, string; + const jchar *characters; + jsize size; + size_t i; + JNIEnv *env; + unsigned int unicode_char; + + env = android_java_env; + status = ANDROID_LOOKUP_NONE; + rc = 0; + + /* See if an actual lookup has to be made. Note that while + BUFFER_RETURN is wchar_t, the returned characters are always in + UCS. */ + + if (event->unicode_char != (uint32_t) -1) + { + if (event->unicode_char) + { + /* KeyCharacterMap.COMBINING_ACCENT. */ + if ((event->unicode_char & 0x80000000) && compose_status) + goto dead_key; + + /* Remove combining accent bits. */ + unicode_char = event->unicode_char & ~0x80000000; + + if (wchars_buffer < 1) + { + *status_return = ANDROID_BUFFER_OVERFLOW; + return 0; + } + else + { + /* If COMPOSE_STATUS holds a diacritic mark unicode_char + ought to be combined with, and this combination is + valid, return the result alone with no keysym. */ + + if (compose_status + && compose_status->chars_matched + && !android_get_dead_char (compose_status->accent, + unicode_char, + &unicode_char)) + { + buffer_return[0] = unicode_char; + *status_return = ANDROID_LOOKUP_CHARS; + compose_status->chars_matched = 0; + return 1; + } + else if (compose_status && compose_status->chars_matched) + { + /* If the combination is valid the compose status must + be reset and no character returned. */ + compose_status->chars_matched = 0; + status = ANDROID_LOOKUP_NONE; + return 0; + } + + buffer_return[0] = unicode_char; + status = ANDROID_LOOKUP_CHARS; + rc = 1; + } + } + + *keysym_return = event->keycode; + + if (status == ANDROID_LOOKUP_CHARS) + status = ANDROID_LOOKUP_BOTH; + else + { + status = ANDROID_LOOKUP_KEYSYM; + rc = 0; + } + + /* Terminate any ongoing character composition after a key is + registered. */ + if (compose_status + /* Provided that a modifier key is not the key being + depressed. */ + && !ANDROID_IS_MODIFIER_KEY (event->keycode)) + compose_status->chars_matched = 0; + *status_return = status; + return rc; + } + + /* Now look up the window. */ + rc = 0; + + if (!android_handles[event->window].handle + || (android_handles[event->window].type + != ANDROID_HANDLE_WINDOW)) + status = ANDROID_LOOKUP_NONE; + else + { + window = android_handles[event->window].handle; + string + = (*env)->CallNonvirtualObjectMethod (env, window, + window_class.class, + window_class.lookup_string, + (jint) event->serial); + android_exception_check (); + + if (!string) + status = ANDROID_LOOKUP_NONE; + else + { + /* Now return this input method string. */ + characters = (*env)->GetStringChars (env, string, NULL); + android_exception_check_nonnull ((void *) characters, string); + + /* Establish the size of the the string. */ + size = (*env)->GetStringLength (env, string); + + /* Copy over the string data. */ + for (i = 0; i < MIN ((unsigned int) wchars_buffer, size); ++i) + buffer_return[i] = characters[i]; + + if (i < size) + status = ANDROID_BUFFER_OVERFLOW; + else + status = ANDROID_LOOKUP_CHARS; + + /* Return the number of characters that should have been + written. */ + + if (size > INT_MAX) + rc = INT_MAX; + else + rc = size; + + (*env)->ReleaseStringChars (env, string, characters); + ANDROID_DELETE_LOCAL_REF (string); + } + } + + *status_return = status; + return rc; + + dead_key: + /* event->unicode_char is a dead key, which are diacritic marks that + should not be directly inserted but instead be combined with a + subsequent character before insertion. */ + *status_return = ANDROID_LOOKUP_NONE; + compose_status->chars_matched = 1; + compose_status->accent = event->unicode_char & ~0x80000000; + return 0; +} + + + +/* Low level drawing primitives. */ + +/* Lock the bitmap corresponding to the drawable DRAWABLE. Return the + bitmap data upon success, and store the bitmap object in + BITMAP_RETURN. Value is NULL upon failure. + + The caller must take care to unlock the bitmap data afterwards. */ + +unsigned char * +android_lock_bitmap (android_drawable drawable, + AndroidBitmapInfo *bitmap_info, + jobject *bitmap_return) +{ + jobject object, bitmap; + void *data; + + object = android_resolve_handle2 (drawable, ANDROID_HANDLE_WINDOW, + ANDROID_HANDLE_PIXMAP); + + /* Look up the drawable and get the bitmap corresponding to it. + Then, lock the bitmap's bits. */ + bitmap = (*android_java_env)->CallObjectMethod (android_java_env, + object, + drawable_class.get_bitmap); + if (!bitmap) + { + /* Report any exception signaled. */ + android_exception_check (); + + /* If no exception was signaled, then NULL was returned as the + bitmap does not presently exist due to window reconfiguration + on the main thread. */ + return NULL; + } + + memset (bitmap_info, 0, sizeof *bitmap_info); + + /* Get the bitmap info. */ + AndroidBitmap_getInfo (android_java_env, bitmap, bitmap_info); + + if (!bitmap_info->stride) + { + ANDROID_DELETE_LOCAL_REF (bitmap); + return NULL; + } + + /* Now lock the image data. */ + data = NULL; + AndroidBitmap_lockPixels (android_java_env, bitmap, &data); + + if (!data) + { + ANDROID_DELETE_LOCAL_REF (bitmap); + return NULL; + } + + /* Give the bitmap to the caller. */ + *bitmap_return = bitmap; + + /* The bitmap data is now locked. */ + return data; +} + +/* Damage the window HANDLE by the given damage rectangle. */ + +void +android_damage_window (android_drawable handle, + struct android_rectangle *damage) +{ + jobject drawable; + + drawable = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + /* Post the damage to the drawable. */ + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + drawable, + window_class.class, + window_class.damage_rect, + (jint) damage->x, + (jint) damage->y, + (jint) (damage->x + + damage->width), + (jint) (damage->y + + damage->height)); + android_exception_check (); +} + + + +/* Other misc system routines. */ + +int +android_get_screen_width (void) +{ + int rc; + jmethodID method; + + method = service_class.get_screen_width; + rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env, + emacs_service, + service_class.class, + method, + (jboolean) false); + android_exception_check (); + return rc; +} + +int +android_get_screen_height (void) +{ + int rc; + jmethodID method; + + method = service_class.get_screen_height; + rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env, + emacs_service, + service_class.class, + method, + (jboolean) false); + android_exception_check (); + return rc; +} + +int +android_get_mm_width (void) +{ + int rc; + jmethodID method; + + method = service_class.get_screen_width; + rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env, + emacs_service, + service_class.class, + method, + (jboolean) true); + android_exception_check (); + return rc; +} + +int +android_get_mm_height (void) +{ + int rc; + jmethodID method; + + method = service_class.get_screen_height; + rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env, + emacs_service, + service_class.class, + method, + (jboolean) true); + android_exception_check (); + return rc; +} + +bool +android_detect_mouse (void) +{ + bool rc; + jmethodID method; + + method = service_class.detect_mouse; + rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env, + emacs_service, + service_class.class, + method); + android_exception_check (); + return rc; +} + +bool +android_detect_keyboard (void) +{ + bool rc; + jmethodID method; + + method = service_class.detect_keyboard; + rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env, + emacs_service, + service_class.class, + method); + android_exception_check (); + return rc; +} + +void +android_set_dont_focus_on_map (android_window handle, + bool no_focus_on_map) +{ + jmethodID method; + jobject window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + method = window_class.set_dont_focus_on_map; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window, + window_class.class, + method, + (jboolean) no_focus_on_map); + android_exception_check (); +} + +void +android_set_dont_accept_focus (android_window handle, + bool no_accept_focus) +{ + jmethodID method; + jobject window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + method = window_class.set_dont_accept_focus; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window, + window_class.class, + method, + (jboolean) no_accept_focus); + android_exception_check (); +} + +void +android_get_keysym_name (int keysym, char *name_return, size_t size) +{ + jobject string; + const char *buffer; + jmethodID method; + + /* These keysyms are special editor actions sent by the input + method. */ + + switch (keysym) + { + case 65536 + 1: + strncpy (name_return, "select-all", size - 1); + name_return[size] = '\0'; + return; + + case 65536 + 2: + strncpy (name_return, "start-selecting-text", size - 1); + name_return[size] = '\0'; + return; + + case 65536 + 3: + strncpy (name_return, "stop-selecting-text", size - 1); + name_return[size] = '\0'; + return; + } + + method = service_class.name_keysym; + string + = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env, + emacs_service, + service_class.class, + method, + (jint) keysym); + android_exception_check (); + + if (!string) + { + strncpy (name_return, "stop-selecting-text", size - 1); + name_return[size] = '\0'; + return; + } + + buffer = (*android_java_env)->GetStringUTFChars (android_java_env, + (jstring) string, + NULL); + android_exception_check_nonnull ((void *) buffer, string); + strncpy (name_return, buffer, size - 1); + name_return[size] = '\0'; + + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + (jstring) string, + buffer); + ANDROID_DELETE_LOCAL_REF (string); +} + +/* Display the on screen keyboard on window WINDOW, or hide it if SHOW + is false. Ask the system to bring up or hide the on-screen + keyboard on behalf of WINDOW. The request may be rejected by the + system, especially when the window does not have the input + focus. */ + +void +android_toggle_on_screen_keyboard (android_window window, bool show) +{ + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = window_class.toggle_on_screen_keyboard; + + /* Now display the on screen keyboard. */ + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, object, + window_class.class, + method, (jboolean) show); + + /* Check for out of memory errors. */ + android_exception_check (); +} + + + +#if defined __clang_major__ && __clang_major__ < 5 +# define HAS_BUILTIN_TRAP 0 +#elif 3 < __GNUC__ + (3 < __GNUC_MINOR__ + (4 <= __GNUC_PATCHLEVEL__)) +# define HAS_BUILTIN_TRAP 1 +#elif defined __has_builtin +# define HAS_BUILTIN_TRAP __has_builtin (__builtin_trap) +#else /* !__has_builtin */ +# define HAS_BUILTIN_TRAP 0 +#endif /* defined __clang_major__ && __clang_major__ < 5 */ + +/* emacs_abort implementation for Android. This logs a stack + trace. */ + +void +emacs_abort (void) +{ +#ifndef HAS_BUILTIN_TRAP + volatile char *foo; +#endif /* !HAS_BUILTIN_TRAP */ + + __android_log_print (ANDROID_LOG_FATAL, __func__, + "emacs_abort called, please review the following" + " stack trace"); + +#ifndef HAS_BUILTIN_TRAP + /* Induce a NULL pointer dereference to make debuggerd generate a + tombstone. */ + foo = NULL; + *foo = '\0'; +#else /* HAS_BUILTIN_TRAP */ + /* Crash through __builtin_trap instead. This appears to more + uniformly elicit crash reports from debuggerd. */ + __builtin_trap (); +#endif /* !HAS_BUILTIN_TRAP */ + + abort (); +} + + + +/* Return whether or not TEXT, a string without multibyte + characters, has no bytes with the 8th bit set. */ + +static bool +android_check_string (Lisp_Object text) +{ + ptrdiff_t i; + + for (i = 0; i < SBYTES (text); ++i) + { + if (SREF (text, i) & 128) + return false; + } + + return true; +} + +/* Verify that the specified NULL-terminated STRING is a valid JNI + ``UTF-8'' string. Return 0 if so, 1 otherwise. + + Do not perform GC, enabling NAME to be a direct reference to string + data. + + The native coding system used by the JVM to store strings derives + from UTF-8, but deviates from it in two aspects in an attempt to + better represent the UCS-16 based Java String format, and to let + strings contain NULL characters while remaining valid C strings: + NULL bytes are encoded as two-byte sequences, and Unicode surrogate + pairs encoded as two-byte sequences are preferred to four-byte + sequences when encoding characters above the BMP. */ + +int +android_verify_jni_string (const char *name) +{ + const unsigned char *chars; + + chars = (unsigned char *) name; + while (*chars) + { + /* Switch on the high 4 bits. */ + + switch (*chars++ >> 4) + { + case 0 ... 7: + /* The 8th bit is clean, so this is a regular C + character. */ + break; + + case 8 ... 0xb: + /* Invalid starting byte! */ + return 1; + + case 0xf: + /* The start of a four byte sequence. These aren't allowed + in Java. */ + return 1; + + case 0xe: + /* The start of a three byte sequence. Verify that its + continued. */ + + if ((*chars++ & 0xc0) != 0x80) + return 1; + + FALLTHROUGH; + + case 0xc ... 0xd: + /* The start of a two byte sequence. Verify that the + next byte exists and has its high bit set. */ + + if ((*chars++ & 0xc0) != 0x80) + return 1; + + break; + } + } + + return 0; +} + +/* Given a Lisp string TEXT, return a local reference to an equivalent + Java string. Each argument following TEXT should be NULL or a + local reference that will be freed if creating the string fails, + whereupon memory_full will also be signaled. */ + +jstring +android_build_string (Lisp_Object text, ...) +{ + Lisp_Object encoded; + jstring string; + size_t nchars; + jchar *characters; + va_list ap; + jobject object; + + USE_SAFE_ALLOCA; + + /* Directly encode TEXT if it contains no non-ASCII characters, or + is multibyte and a valid Modified UTF-8 string. This is okay + because the Java extended UTF format is compatible with + ASCII. */ + + if ((SBYTES (text) == SCHARS (text) + && android_check_string (text)) + /* If TEXT is a multibyte string, then it's using Emacs's + internal UTF-8 coding system, a significant subset of which + is compatible with JNI. */ + || (STRING_MULTIBYTE (text) + && !android_verify_jni_string (SSDATA (text)))) + { + string = (*android_java_env)->NewStringUTF (android_java_env, + SSDATA (text)); + + if ((*android_java_env)->ExceptionCheck (android_java_env)) + goto error; + + SAFE_FREE (); + return string; + } + + encoded = code_convert_string_norecord (text, Qutf_16le, + true); + nchars = (SBYTES (encoded) / sizeof (jchar)); + + /* Encode the string as UTF-16 prior to creating the string. + Copy the string to a separate buffer in order to preserve + alignment. */ + + characters = SAFE_ALLOCA (SBYTES (encoded)); + memcpy (characters, SDATA (encoded), SBYTES (encoded)); + + /* Create the string. */ + string + = (*android_java_env)->NewString (android_java_env, + characters, nchars); + + if ((*android_java_env)->ExceptionCheck (android_java_env)) + goto error; + + SAFE_FREE (); + return string; + + error: + /* An exception arose while creating the string. When this + transpires, an assumption is made that the error was induced by + running out of memory. Delete each of the local references + within AP. */ + + va_start (ap, text); + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + /* Now remove each and every local reference provided after + OBJECT. */ + + while ((object = va_arg (ap, jobject))) + ANDROID_DELETE_LOCAL_REF (object); + + va_end (ap); + memory_full (0); +} + +/* Do the same, except TEXT is constant string data in ASCII or + UTF-8 containing no characters outside the Basic Multilingual + Plane. */ + +jstring +android_build_jstring (const char *text) +{ + jstring string; + + /* Note that Java expects this string to be in ``modified UTF + encoding'', which is actually UTF-8, except with NUL + encoded as a two-byte sequence, and surrogate pairs encoded + in the three-byte extended encoding. The only consequence + of passing an actual UTF-8 string is that NUL bytes and + characters requiring surrogate pairs cannot be represented, + which is not really of consequence. */ + + string = (*android_java_env)->NewStringUTF (android_java_env, + text); + android_exception_check (); + + return string; +} + + + +/* Exception checking functions. Most JNI functions which allocate + memory return NULL upon failure; they also set the JNI + environment's pending exception to an OutOfMemoryError. + + These functions check for such errors and call memory_full wherever + appropriate. Three variants are provided: one which releases no + local references, one which releases a single local reference + before calling memory_full, and one which releases two local + references. + + Typically, you use these functions by calling them immediately + after a JNI function which allocates memory, passing it any local + references that are already valid but should be deleted after + leaving the current scope. For example, to allocate foo, make + global_foo its global reference, and then release foo, you write: + + jobject foo, global_foo; + + foo = (*android_java_env)->New...; + android_exception_check (); + + global_foo = (*android_java_env)->NewGlobalRef (..., foo); + android_exception_check_1 (foo); + ANDROID_DELETE_LOCAL_REF (foo); + + where the first android_exception_check ensures that foo has been + allocated correctly, while the call to android_exception_check_1, + and the call to ANDROID_DELETE_LOCAL_REF afterwards, together + ensure the same of global_foo, and also that foo is released both + if global_foo cannot be allocated, and after the global reference + is created. */ + +#if __GNUC__ >= 3 +#define likely(cond) __builtin_expect (cond, 1) +#else /* __GNUC__ < 3 */ +#define likely(cond) (cond) +#endif /* __GNUC__ >= 3 */ + +/* Check for JNI exceptions and call memory_full in that + situation. */ + +void +android_exception_check (void) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); +} + +/* Check for JNI exceptions. If there is one such exception, clear + it, then delete the local reference to OBJECT and call memory_full. + OBJECT can be NULL, which is a valid local reference to the Java + null object. */ + +void +android_exception_check_1 (jobject object) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + if (object) + ANDROID_DELETE_LOCAL_REF (object); + + memory_full (0); +} + +/* Like android_exception_check_1, except it takes more than one local + reference argument. */ + +void +android_exception_check_2 (jobject object, jobject object1) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + if (object) + ANDROID_DELETE_LOCAL_REF (object); + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + memory_full (0); +} + +/* Like android_exception_check_2, except it takes more than two local + reference arguments. */ + +void +android_exception_check_3 (jobject object, jobject object1, + jobject object2) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + if (object) + ANDROID_DELETE_LOCAL_REF (object); + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + if (object2) + ANDROID_DELETE_LOCAL_REF (object2); + + memory_full (0); +} + +/* Like android_exception_check_3, except it takes more than three + local reference arguments. */ + +void +android_exception_check_4 (jobject object, jobject object1, + jobject object2, jobject object3) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + if (object) + ANDROID_DELETE_LOCAL_REF (object); + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + if (object2) + ANDROID_DELETE_LOCAL_REF (object2); + + if (object3) + ANDROID_DELETE_LOCAL_REF (object3); + + memory_full (0); +} + +/* Like android_exception_check_4, except it takes more than four local + reference arguments. */ + +void +android_exception_check_5 (jobject object, jobject object1, + jobject object2, jobject object3, + jobject object4) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + if (object) + ANDROID_DELETE_LOCAL_REF (object); + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + if (object2) + ANDROID_DELETE_LOCAL_REF (object2); + + if (object3) + ANDROID_DELETE_LOCAL_REF (object3); + + if (object4) + ANDROID_DELETE_LOCAL_REF (object4); + + memory_full (0); +} + + +/* Like android_exception_check_5, except it takes more than five local + reference arguments. */ + +void +android_exception_check_6 (jobject object, jobject object1, + jobject object2, jobject object3, + jobject object4, jobject object5) +{ + if (likely (!(*android_java_env)->ExceptionCheck (android_java_env))) + return; + + __android_log_print (ANDROID_LOG_WARN, __func__, + "Possible out of memory error. " + " The Java exception follows: "); + /* Describe exactly what went wrong. */ + (*android_java_env)->ExceptionDescribe (android_java_env); + (*android_java_env)->ExceptionClear (android_java_env); + + if (object) + ANDROID_DELETE_LOCAL_REF (object); + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + if (object2) + ANDROID_DELETE_LOCAL_REF (object2); + + if (object3) + ANDROID_DELETE_LOCAL_REF (object3); + + if (object4) + ANDROID_DELETE_LOCAL_REF (object4); + + if (object5) + ANDROID_DELETE_LOCAL_REF (object5); + + memory_full (0); +} + +/* Check for JNI problems based on the value of OBJECT. + + Signal out of memory if OBJECT is NULL. OBJECT1 means the + same as in `android_exception_check_1'. + + This function is useful when checking for errors from JNI + functions that do not set exceptions on failure, such as + `GetIntArrayElements'. */ + +void +android_exception_check_nonnull (void *object, jobject object1) +{ + if (likely (object != NULL)) + return; + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + memory_full (0); +} + +/* Check for JNI problems based on the value of OBJECT. + + Signal out of memory if OBJECT is NULL. OBJECT1 and OBJECT2 mean + the same as in `android_exception_check_2'. */ + +void +android_exception_check_nonnull_1 (void *object, jobject object1, + jobject object2) +{ + if (likely (object != NULL)) + return; + + if (object1) + ANDROID_DELETE_LOCAL_REF (object1); + + if (object2) + ANDROID_DELETE_LOCAL_REF (object2); + + memory_full (0); +} + + + +/* Native image transforms. */ + +/* Transform the coordinates X and Y by the specified affine + transformation MATRIX. Place the result in *XOUT and *YOUT. */ + +static void +android_transform_coordinates (int x, int y, + struct android_transform *transform, + float *xout, float *yout) +{ + /* Apply the specified affine transformation. + A transform looks like: + + M1 M2 M3 X + M4 M5 M6 * Y + + = + + M1*X + M2*Y + M3*1 = X1 + M4*X + M5*Y + M6*1 = Y1 + + (In most transforms, there is another row at the bottom for + mathematical reasons. Since Z1 is always 1.0, the row is simply + implied to be 0 0 1, because 0 * x + 0 * y + 1 * 1 = 1.0. See + the definition of matrix3x3 in image.c for some more explanations + about this.) */ + + *xout = transform->m1 * x + transform->m2 * y + transform->m3; + *yout = transform->m4 * x + transform->m5 * y + transform->m6; +} + +/* Return the interpolation of the four pixels TL, TR, BL, and BR, + according to the weights DISTX and DISTY. */ + +static unsigned int +android_four_corners_bilinear (unsigned int tl, unsigned int tr, + unsigned int bl, unsigned int br, + int distx, int disty) +{ + int distxy, distxiy, distixy, distixiy; + uint32_t f, r; + + distxy = distx * disty; + distxiy = (distx << 8) - distxy; + distixy = (disty << 8) - distxy; + distixiy = (256 * 256 - (disty << 8) + - (distx << 8) + distxy); + + /* Red */ + r = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy + + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); + + /* Green */ + f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy + + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); + r |= f & 0xff000000; + + /* Now do the upper two components. */ + tl >>= 16; + tr >>= 16; + bl >>= 16; + br >>= 16; + r >>= 16; + + /* Blue */ + f = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy + + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); + r |= f & 0x00ff0000; + + /* Alpha */ + f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy + + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); + r |= f & 0xff000000; + + return r; +} + +/* Return the interpolation of the four pixels closest to at X, Y in + IMAGE, according to weights in both axes computed from X and Y. + IMAGE must be depth 24, or the behavior is undefined. */ + +static unsigned int +android_fetch_pixel_bilinear (struct android_image *image, + float x, float y) +{ + int x1, y1, x2, y2; + float distx, disty; + unsigned int top_left, top_right; + unsigned int bottom_left, bottom_right; + char *word; + + /* Compute the four closest corners to X and Y. */ + x1 = (int) x; + x2 = x1 + 1; + y1 = (int) y; + y2 = y1 + 1; + + /* Make sure all four corners are within range. */ + x1 = MAX (0, MIN (image->width - 1, x1)); + y1 = MAX (0, MIN (image->height - 1, y1)); + x2 = MAX (0, MIN (image->width - 1, x2)); + y2 = MAX (0, MIN (image->height - 1, y2)); + + /* Compute the X and Y biases. These are numbers between 0f and + 1f. */ + distx = x - x1; + disty = y - y1; + + /* Fetch the four closest pixels. */ + word = image->data + y1 * image->bytes_per_line + x1 * 4; + memcpy (&top_left, word, sizeof top_left); + word = image->data + y1 * image->bytes_per_line + x2 * 4; + memcpy (&top_right, word, sizeof top_right); + word = image->data + y2 * image->bytes_per_line + x1 * 4; + memcpy (&bottom_left, word, sizeof bottom_left); + word = image->data + y2 * image->bytes_per_line + x2 * 4; + memcpy (&bottom_right, word, sizeof bottom_right); + + /* Do the interpolation. */ + return android_four_corners_bilinear (top_left, top_right, bottom_left, + bottom_right, distx * 256, + disty * 256); +} + +/* Transform the depth 24 image IMAGE by the 3x2 affine transformation + matrix MATRIX utilizing a bilinear filter. Place the result in + OUT. The matrix maps from the coordinate space of OUT to + IMAGE. */ + +void +android_project_image_bilinear (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + int x, y; + unsigned int pixel; + float xout, yout; + char *word; + + /* Loop through each pixel in OUT. Transform it by TRANSFORM, then + interpolate it to IMAGE, and place the result back in OUT. */ + + for (y = 0; y < out->height; ++y) + { + for (x = 0; x < out->width; ++x) + { + /* Transform the coordinates by TRANSFORM. */ + android_transform_coordinates (x, y, transform, + &xout, &yout); + + /* Interpolate back to IMAGE. */ + pixel = android_fetch_pixel_bilinear (image, xout, yout); + + /* Put the pixel back in OUT. */ + word = out->data + y * out->bytes_per_line + x * 4; + memcpy (word, &pixel, sizeof pixel); + } + } +} + +/* Return the interpolation of X, Y to IMAGE, a depth 24 image. */ + +static unsigned int +android_fetch_pixel_nearest_24 (struct android_image *image, float x, + float y) +{ + int x1, y1; + char *word; + unsigned int pixel; + + x1 = MAX (0, MIN (image->width - 1, (int) roundf (x))); + y1 = MAX (0, MIN (image->height - 1, (int) roundf (y))); + + word = image->data + y1 * image->bytes_per_line + x1 * 4; + memcpy (&pixel, word, sizeof pixel); + + return pixel; +} + +/* Return the interpolation of X, Y to IMAGE, a depth 1 image. */ + +static unsigned int +android_fetch_pixel_nearest_1 (struct android_image *image, float x, + float y) +{ + int x1, y1; + char *byte; + + x1 = MAX (0, MIN (image->width - 1, (int) roundf (x))); + y1 = MAX (0, MIN (image->height - 1, (int) roundf (y))); + + byte = image->data + y1 * image->bytes_per_line; + return (byte[x1 / 8] & (1 << x1 % 8)) ? 1 : 0; +} + +/* Transform the depth 24 or 1 image IMAGE by the 3x2 affine + transformation matrix MATRIX. Place the result in OUT. The matrix + maps from the coordinate space of OUT to IMAGE. Use a + nearest-neighbor filter. */ + +void +android_project_image_nearest (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + int x, y; + unsigned int pixel; + float xout, yout; + char *word, *byte; + + if (image->depth == 1) + { + for (y = 0; y < out->height; ++y) + { + for (x = 0; x < out->width; ++x) + { + /* Transform the coordinates by TRANSFORM. */ + android_transform_coordinates (x, y, transform, + &xout, &yout); + + /* Interpolate back to IMAGE. */ + pixel = android_fetch_pixel_nearest_1 (image, xout, yout); + + /* Put the pixel back in OUT. */ + byte = out->data + y * out->bytes_per_line + x / 8; + + if (pixel) + *byte |= (1 << x % 8); + else + *byte &= ~(1 << x % 8); + } + } + + return; + } + + for (y = 0; y < out->height; ++y) + { + for (x = 0; x < out->width; ++x) + { + /* Transform the coordinates by TRANSFORM. */ + android_transform_coordinates (x, y, transform, + &xout, &yout); + + /* Interpolate back to IMAGE. */ + pixel = android_fetch_pixel_nearest_24 (image, xout, yout); + + /* Put the pixel back in OUT. */ + word = out->data + y * out->bytes_per_line + x * 4; + memcpy (word, &pixel, sizeof pixel); + } + } +} + + + +/* Other miscellaneous functions. */ + +/* Ask the system to start browsing the specified URL. Upon failure, + return a string describing the error. Else, value is nil. URL + should be encoded unless SEND. + + If SEND, open the URL with applications that can ``send'' or + ``share'' the URL (through mail, for example.) */ + +Lisp_Object +android_browse_url (Lisp_Object url, Lisp_Object send) +{ + jobject value, string; + Lisp_Object tem; + const char *buffer; + + string = android_build_string (url, NULL); + value + = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env, + emacs_service, + service_class.class, + service_class.browse_url, + string, + (jboolean) !NILP (send)); + android_exception_check (); + + ANDROID_DELETE_LOCAL_REF (string); + + /* If no string was returned, return Qnil. */ + if (!value) + return Qnil; + + buffer = (*android_java_env)->GetStringUTFChars (android_java_env, + (jstring) value, + NULL); + android_exception_check_1 (value); + + /* Otherwise, build the string describing the error. */ + tem = build_string_from_utf8 (buffer); + + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + (jstring) value, + buffer); + + /* And return it. */ + ANDROID_DELETE_LOCAL_REF (value); + return tem; +} + +/* Tell the system to restart Emacs in a short amount of time, and + then kill Emacs. Never return. This is used to implement + `restart-emacs'. */ + +_Noreturn void +android_restart_emacs (void) +{ + /* Try to call the Java side function. Normally, this should call + System.exit to terminate this process. */ + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.restart_emacs); + + /* Exit anyway, in case EmacsService did not do so. */ + exit (0); +} + +/* Return a number from 1 to 34 describing the version of Android + Emacs is running on. + + This is different from __ANDROID_API__, as that describes the + minimum version of Android this build of Emacs will run on, and in + turn which APIs Emacs can safely use. */ + +int +(android_get_current_api_level) (void) +{ + return android_api_level; +} + +/* Query the status of the battery, and place it in *STATUS. + Value is 1 upon failure, else 0. */ + +int +android_query_battery (struct android_battery_state *status) +{ + jlongArray array; + jlong *longs; + jmethodID method; + + method = service_class.query_battery; + array + = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env, + emacs_service, + service_class.class, + method); + android_exception_check (); + + /* A NULL return with no exception means that battery information + could not be obtained. */ + + if (!array) + return 1; + + longs = (*android_java_env)->GetLongArrayElements (android_java_env, + array, NULL); + android_exception_check_nonnull (longs, array); + + status->capacity = longs[0]; + status->charge_counter = longs[1]; + status->current_average = longs[2]; + status->current_now = longs[3]; + status->remaining = longs[4]; + status->status = longs[5]; + status->plugged = longs[6]; + status->temperature = longs[7]; + + (*android_java_env)->ReleaseLongArrayElements (android_java_env, + array, longs, + JNI_ABORT); + ANDROID_DELETE_LOCAL_REF (array); + + return 0; +} + +/* Display a file panel and grant Emacs access to the SAF directory + within it. Value is 1 upon failure and 0 upon success (which only + indicates that the panel has been displayed successfully; the panel + may still be dismissed without a file being selected.) */ + +int +android_request_directory_access (void) +{ + jint rc; + jmethodID method; + + method = service_class.request_directory_access; + rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env, + emacs_service, + service_class.class, + method); + android_exception_check (); + + return rc; +} + +/* Return whether Emacs is entitled to access external storage. + + On Android 5.1 and earlier, such permissions as are declared within + an application's manifest are granted during installation and are + irrevocable. + + On Android 6.0 through Android 10.0, the right to read external + storage is a regular permission granted from the Permissions + panel. + + On Android 11.0 and later, that right must be granted through an + independent ``Special App Access'' settings panel. */ + +bool +android_external_storage_available_p (void) +{ + jboolean rc; + jmethodID method; + + if (android_api_level <= 22) /* LOLLIPOP_MR1 */ + return true; + + method = service_class.external_storage_available; + rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env, + emacs_service, + service_class.class, + method); + android_exception_check (); + + return rc; +} + +/* Display a dialog from which the aforementioned rights can be + granted. */ + +void +android_request_storage_access (void) +{ + jmethodID method; + + if (android_api_level <= 22) /* LOLLIPOP_MR1 */ + return; + + method = service_class.request_storage_access; + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + method); + android_exception_check (); +} + +/* Recreate the activity to which WINDOW is attached to debug graphics + code executed in response to window attachment. */ + +void +android_recreate_activity (android_window window) +{ + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = window_class.recreate_activity; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, object, + window_class.class, + method); + android_exception_check (); +} + + + +/* The thread from which a query against a thread is currently being + made, if any. Value is 0 if no query is in progress, 1 if a query + is being made from the UI thread to the main thread, and 2 if a + query is being made the other way around. */ +static char android_servicing_query; + +/* Function that is waiting to be run in the Emacs thread. */ +static void (*android_query_function) (void *); + +/* Context for that function. */ +static void *android_query_context; + +/* Deadlock protection. The UI thread and the Emacs thread must + sometimes make synchronous queries to each other, which are + normally answered inside each thread's respective event loop. + Deadlocks can happen when both threads simultaneously make such + synchronous queries and block waiting for each others responses. + + The Emacs thread can be interrupted to service any queries made by + the UI thread, but is not possible the other way around. + + To avoid such deadlocks, an atomic counter is provided. This + counter is set to two every time a query starts from the main + thread, and is set to zero every time one ends. If the UI thread + tries to make a query and sees that the counter is two, it simply + returns so that its event loop can proceed to perform and respond + to the query. If the Emacs thread sees that the counter is one, + then it stops to service all queries being made by the input + method, then proceeds to make its query with the counter set to + 2. + + The memory synchronization is simple: all writes to + `android_query_context' and `android_query_function' are depended + on by writes to the atomic counter. Loads of the new value from + the counter are then guaranteed to make those writes visible. The + separate flag `android_urgent_query' does not depend on anything + itself; however, the input signal handler executes a memory fence + to ensure that all query related writes become visible. */ + +/* Run any function that the UI thread has asked to run, and then + signal its completion. */ + +void +android_check_query (void) +{ + void (*proc) (void *); + void *closure; + + if (!__atomic_load_n (&android_servicing_query, __ATOMIC_ACQUIRE)) + return; + + /* First, load the procedure and closure. */ + closure = android_query_context; + proc = android_query_function; + + if (!proc) + return; + + proc (closure); + + /* Finish the query. */ + android_query_context = NULL; + android_query_function = NULL; + __atomic_store_n (&android_servicing_query, 0, __ATOMIC_RELEASE); + __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE); + + /* Signal completion. */ + sem_post (&android_query_sem); +} + +/* Run any function that the UI thread has asked to run, if the UI + thread has been waiting for more than two seconds. + + Call this from `process_pending_signals' to ensure that the UI + thread always receives an answer within a reasonable amount of + time. */ + +void +android_check_query_urgent (void) +{ + void (*proc) (void *); + void *closure; + + if (!__atomic_load_n (&android_urgent_query, __ATOMIC_ACQUIRE)) + return; + + __android_log_print (ANDROID_LOG_VERBOSE, __func__, + "Responding to urgent query..."); + + if (!__atomic_load_n (&android_servicing_query, __ATOMIC_ACQUIRE)) + return; + + /* First, load the procedure and closure. */ + closure = android_query_context; + proc = android_query_function; + + if (!proc) + return; + + proc (closure); + + /* Finish the query. Don't clear `android_urgent_query'; instead, + do that the next time Emacs enters the keyboard loop. */ + + android_query_context = NULL; + android_query_function = NULL; + __atomic_store_n (&android_servicing_query, 0, __ATOMIC_RELEASE); + + /* Signal completion. */ + sem_post (&android_query_sem); +} + +/* Run the function that the UI thread has asked to run, and then + signal its completion. Do not change `android_servicing_query' + after it completes. */ + +static void +android_answer_query (void) +{ + void (*proc) (void *); + void *closure; + + eassert (__atomic_load_n (&android_servicing_query, + __ATOMIC_ACQUIRE) + == 1); + + /* First, load the procedure and closure. */ + closure = android_query_context; + proc = android_query_function; + + if (!proc) + return; + + proc (closure); + + /* Finish the query. */ + android_query_context = NULL; + android_query_function = NULL; + __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE); + + /* Signal completion. */ + sem_post (&android_query_sem); +} + +/* Like `android_answer_query'. However, the query may not have + begun; spin until it has. */ + +static void +android_answer_query_spin (void) +{ + int n; + + while (!(n = __atomic_load_n (&android_servicing_query, + __ATOMIC_ACQUIRE))) + eassert (!n); + + /* Note that this function is supposed to be called before + `android_begin_query' starts, so clear the service flag. */ + android_check_query (); +} + +/* Notice that the Emacs thread will start blocking waiting for a + response from the UI thread. Process any pending queries from the + UI thread. + + This function may be called from Java. */ + +static void +android_begin_query (void) +{ + char old; + + /* Load the previous value of `android_servicing_query' and then set + it to 2. */ + + old = __atomic_exchange_n (&android_servicing_query, + 2, __ATOMIC_ACQ_REL); + + /* See if a query was previously in progress. */ + if (old == 1) + { + /* Answer the query that is currently being made. */ + eassert (android_query_function != NULL); + android_answer_query (); + } + + /* `android_servicing_query' is now 2. */ +} + +/* Notice that a query has stopped. This function may be called from + Java. */ + +static void +android_end_query (void) +{ + __atomic_store_n (&android_servicing_query, 0, __ATOMIC_RELEASE); + __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE); +} + +/* Synchronously ask the Emacs thread to run the specified PROC with + the given CLOSURE. Return if this fails, or once PROC is run. + + PROC may be run from inside maybe_quit. + + It is not okay to run Lisp code which signals or performs non + trivial tasks inside PROC. + + Return 1 if the Emacs thread is currently waiting for the UI thread + to respond and PROC could not be run, or 0 otherwise. */ + +int +android_run_in_emacs_thread (void (*proc) (void *), void *closure) +{ + union android_event event; + char old; + int rc; + struct timespec timeout; + + event.xaction.type = ANDROID_WINDOW_ACTION; + event.xaction.serial = ++event_serial; + event.xaction.window = 0; + event.xaction.action = 0; + + /* Set android_query_function and android_query_context. */ + android_query_context = closure; + android_query_function = proc; + + /* Don't allow deadlocks to happen; make sure the Emacs thread is + not waiting for something to be done (in that case, + `android_query_context' is 2.) */ + + old = 0; + if (!__atomic_compare_exchange_n (&android_servicing_query, &old, + 1, false, __ATOMIC_ACQ_REL, + __ATOMIC_ACQUIRE)) + { + android_query_context = NULL; + android_query_function = NULL; + + /* The two variables above may still be non-NULL from the POV of + the main thread, as no happens-before constraint is placed on + those stores wrt a future load from `android_servicing_query'. */ + + return 1; + } + + /* Send a dummy event. `android_check_query' will be called inside + wait_reading_process_output after the event arrives. + + Otherwise, android_select will call android_check_thread the next + time it is entered. */ + android_write_event (&event); + + /* Start waiting for the function to be executed. First, wait two + seconds for the query to execute normally. */ + + timeout.tv_sec = 2; + timeout.tv_nsec = 0; + timeout = timespec_add (current_timespec (), timeout); + + /* See if an urgent query was recently answered without entering the + keyboard loop in between. When that happens, raise SIGIO to + continue processing queries as soon as possible. */ + + if (__atomic_load_n (&android_urgent_query, __ATOMIC_ACQUIRE)) + kill (getpid (), SIGIO); + + again: + rc = sem_timedwait (&android_query_sem, &timeout); + + if (rc < 0) + { + if (errno == EINTR) + goto again; + + eassert (errno == ETIMEDOUT); + + __android_log_print (ANDROID_LOG_VERBOSE, __func__, + "Timed out waiting for response" + " from main thread..."); + + /* The query timed out. At this point, set + `android_urgent_query' to true. */ + __atomic_store_n (&android_urgent_query, true, + __ATOMIC_RELEASE); + + kill_again: + + /* And raise SIGIO. Now that the query is considered urgent, + the main thread will reply while reading async input. + + Normally, the main thread waits for the keyboard loop to be + entered before responding, in order to avoid responding with + inaccurate results taken during command executioon. */ + kill (getpid (), SIGIO); + + /* Wait for the query to complete. `android_urgent_query' is + only cleared by either `android_select' or + `android_check_query', so there's no need to worry about the + flag being cleared before the query is processed. + + Send SIGIO again periodically until the query is answered, on + the off chance that SIGIO arrived too late to preempt a + system call, but too early for it to return EINTR. */ + + timeout.tv_sec = 4; + timeout.tv_nsec = 0; + timeout = timespec_add (current_timespec (), timeout); + + while (sem_timedwait (&android_query_sem, &timeout) < 0) + { + /* If waiting timed out, send SIGIO to the main thread + again. */ + + if (errno == ETIMEDOUT) + goto kill_again; + + /* Otherwise, continue waiting. */ + eassert (errno == EINTR); + } + } + + /* At this point, `android_servicing_query' should either be zero if + the query was answered or two if the main thread has started a + query. */ + + eassert (!__atomic_load_n (&android_servicing_query, + __ATOMIC_ACQUIRE) + || (__atomic_load_n (&android_servicing_query, + __ATOMIC_ACQUIRE) == 2)); + + return 0; +} + + + +/* Input method related functions. */ + +/* Change WINDOW's active selection to the characters between + SELECTION_START and SELECTION_END. + + Also, update the composing region to COMPOSING_REGION_START and + COMPOSING_REGION_END. + + If any value cannot fit in jint, then the behavior of the input + method is undefined. */ + +void +android_update_ic (android_window window, ptrdiff_t selection_start, + ptrdiff_t selection_end, ptrdiff_t composing_region_start, + ptrdiff_t composing_region_end) +{ + jobject object; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.update_ic, + object, + (jint) selection_start, + (jint) selection_end, + (jint) composing_region_start, + (jint) composing_region_end); + android_exception_check (); +} + +/* Reinitialize any ongoing input method connection on WINDOW. + + Any input method that is connected to WINDOW will invalidate its + cache of the buffer contents. + + MODE controls certain aspects of the input method's behavior: + + - If MODE is ANDROID_IC_MODE_NULL, the input method will be + deactivated, and an ASCII only keyboard will be displayed + instead. + + - If MODE is ANDROID_IC_MODE_ACTION, the input method will + edit text normally, but send ``return'' as a key event. + This is useful inside the mini buffer. + + - If MODE is ANDROID_IC_MODE_TEXT, the input method is free + to behave however it wants. */ + +void +android_reset_ic (android_window window, enum android_ic_mode mode) +{ + jobject object; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.reset_ic, + object, (jint) mode); + android_exception_check (); +} + +/* Make updates to extracted text known to the input method on + WINDOW. TEXT should be a local reference to the new + extracted text. TOKEN should be the token specified by the + input method. */ + +void +android_update_extracted_text (android_window window, void *text, + int token) +{ + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = service_class.update_extracted_text; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + method, object, + /* N.B. that text is + not jobject, + because that type + is not available + in + androidgui.h. */ + (jobject) text, + (jint) token); + android_exception_check_1 (text); +} + +/* Report the position of the cursor to the input method connection on + WINDOW. + + X is the horizontal position of the end of the insertion marker. Y + is the top of the insertion marker. Y_BASELINE is the baseline of + the row containing the insertion marker, and Y_BOTTOM is the bottom + of the insertion marker. */ + +void +android_update_cursor_anchor_info (android_window window, float x, + float y, float y_baseline, + float y_bottom) +{ + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = service_class.update_cursor_anchor_info; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + method, + object, + (jfloat) x, + (jfloat) y, + (jfloat) y_baseline, + (jfloat) y_bottom); + android_exception_check (); +} + + + +/* Window decoration management functions. */ + +/* Make the specified WINDOW fullscreen, i.e. obscure all of the + system navigation and status bars. If not FULLSCREEN, make it + maximized instead. + + Value is 1 if the system does not support this, else 0. */ + +int +android_set_fullscreen (android_window window, bool fullscreen) +{ + jobject object; + + /* Android 4.0 and earlier don't support fullscreen windows. */ + + if (android_api_level < 16) + return 1; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + object, + window_class.class, + window_class.set_fullscreen, + (jboolean) fullscreen); + android_exception_check (); + return 0; +} + + + +/* Window cursor support. */ + +android_cursor +android_create_font_cursor (enum android_cursor_shape shape) +{ + android_cursor id; + short prev_max_handle; + jobject object; + + /* First, allocate the cursor handle. */ + prev_max_handle = max_handle; + id = android_alloc_id (); + + if (!id) + error ("Out of cursor handles!"); + + /* Next, create the cursor. */ + object = (*android_java_env)->NewObject (android_java_env, + cursor_class.class, + cursor_class.constructor, + (jshort) id, + (jint) shape); + if (!object) + { + (*android_java_env)->ExceptionClear (android_java_env); + max_handle = prev_max_handle; + memory_full (0); + } + + android_handles[id].type = ANDROID_HANDLE_CURSOR; + android_handles[id].handle + = (*android_java_env)->NewGlobalRef (android_java_env, object); + (*android_java_env)->ExceptionClear (android_java_env); + ANDROID_DELETE_LOCAL_REF (object); + + if (!android_handles[id].handle) + memory_full (0); + + return id; +} + +void +android_define_cursor (android_window window, android_cursor cursor) +{ + jobject window1, cursor1; + jmethodID method; + + window1 = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + cursor1 = android_resolve_handle (cursor, ANDROID_HANDLE_CURSOR); + method = window_class.define_cursor; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window1, + window_class.class, + method, cursor1); + android_exception_check (); +} + +void +android_free_cursor (android_cursor cursor) +{ + if (android_handles[cursor].type != ANDROID_HANDLE_CURSOR) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Trying to destroy something not a CURSOR!"); + emacs_abort (); + } + + android_destroy_handle (cursor); +} + + + +/* Process execution. + + Newer Android systems use SELinux to restrict user programs from + executing programs installed in the application data directory for + security reasons. Emacs uses a `loader' binary installed in the + application data directory to manually load executables and replace + the `execve' system call. */ + +enum + { + /* Maximum number of arguments available. */ + MAXARGS = 1024, + }; + +/* Rewrite the command line given in *ARGV to utilize the `exec1' + bootstrap binary if necessary. + + Value is 0 upon success, else 1. Set errno upon failure. + + ARGV holds a pointer to a NULL-terminated array of arguments given + to `emacs_spawn'. */ + +int +android_rewrite_spawn_argv (const char ***argv) +{ + static const char *new_args[MAXARGS]; + static char exec1_name[PATH_MAX], loader_name[PATH_MAX]; + size_t i, nargs; + + /* This isn't required on Android 9 or earlier. */ + + if (android_api_level < 29 || !android_use_exec_loader) + return 0; + + /* Get argv[0]; this should never be NULL. + Then, verify that it exists and is executable. */ + + eassert (**argv); + if (access (**argv, R_OK | X_OK)) + return 1; + + /* Count the number of arguments in *argv. */ + + nargs = 0; + while ((*argv)[nargs]) + ++nargs; + + /* nargs now holds the number of arguments in argv. If it's larger + than MAXARGS, return failure. */ + + if (nargs + 2 > MAXARGS) + { + errno = E2BIG; + return 1; + } + + /* Fill in the name of `libexec1.so'. */ + snprintf (exec1_name, PATH_MAX, "%s/libexec1.so", + android_lib_dir); + + /* And libloader.so. */ + snprintf (loader_name, PATH_MAX, "%s/libloader.so", + android_lib_dir); + + /* Now fill in the first two arguments. */ + new_args[0] = exec1_name; + new_args[1] = loader_name; + + /* And insert the rest, including the trailing NULL. */ + for (i = 0; i < nargs + 1; ++i) + new_args[i + 2] = (*argv)[i]; + + /* Replace argv. */ + *argv = new_args; + + /* Return success. */ + return 0; +} + + + +#else /* ANDROID_STUBIFY */ + +/* X emulation functions for Android. */ + +struct android_gc * +android_create_gc (enum android_gc_value_mask mask, + struct android_gc_values *values) +{ + /* This function should never be called when building stubs. */ + emacs_abort (); +} + +void +android_free_gc (struct android_gc *gc) +{ + /* This function should never be called when building stubs. */ + emacs_abort (); +} + +struct android_image * +android_create_image (unsigned int depth, enum android_image_format format, + char *data, unsigned int width, unsigned int height) +{ + emacs_abort (); +} + +void +android_destroy_image (struct android_image *ximg) +{ + emacs_abort (); +} + +void +android_put_pixel (struct android_image *ximg, int x, int y, + unsigned long pixel) +{ + emacs_abort (); +} + +unsigned long +android_get_pixel (struct android_image *ximg, int x, int y) +{ + emacs_abort (); +} + +struct android_image * +android_get_image (android_drawable drawable, + enum android_image_format format) +{ + emacs_abort (); +} + +void +android_put_image (android_pixmap pixmap, + struct android_image *image) +{ + emacs_abort (); +} + +void +android_project_image_bilinear (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + emacs_abort (); +} + +void +android_project_image_nearest (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + emacs_abort (); +} + +#endif /* !ANDROID_STUBIFY */ |