emacs/src/android.c

7375 lines
192 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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;
jmethodID set_wm_name;
};
struct android_emacs_cursor
{
jclass class;
jmethodID constructor;
};
struct android_key_character_map
{
jclass class;
jmethodID get_dead_char;
};
struct android_emacs_handle
{
jclass class;
jmethodID destroy_handle;
jfieldID handle;
};
/* 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. */
static 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, emacs_gc_line_style;
static jfieldID emacs_gc_line_width, emacs_gc_dash_offset;
static jfieldID emacs_gc_dashes;
/* 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;
/* Various methods and fields associated with the EmacsHandleObject
class. */
static struct android_emacs_handle handle_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 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;)[J");
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",
"(Ljava/lang/String;ZZZ)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",
"(Ljava/lang/String;)[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");
FIND_METHOD (relinquish_uri_rights,
"relinquishUriRights", "(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>", "(III)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");
FIND_METHOD (set_wm_name, "setWmName", "(Ljava/lang/String;)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>", "(I)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);
}
static void
android_init_emacs_handle (void)
{
jclass old;
handle_class.class
= (*android_java_env)->FindClass (android_java_env,
"org/gnu/emacs/EmacsHandleObject");
eassert (handle_class.class);
old = handle_class.class;
handle_class.class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) old);
ANDROID_DELETE_LOCAL_REF (old);
if (!handle_class.class)
emacs_abort ();
#define FIND_METHOD(c_name, name, signature) \
handle_class.c_name \
= (*android_java_env)->GetMethodID (android_java_env, \
handle_class.class, \
name, signature); \
eassert (handle_class.c_name);
FIND_METHOD (destroy_handle, "destroyHandle", "()V");
#undef FIND_METHOD
handle_class.handle
= (*android_java_env)->GetFieldID (android_java_env,
handle_class.class,
"handle", "J");
eassert (handle_class.handle);
}
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 ();
android_init_emacs_handle ();
/* 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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,
jlong 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 jint JNICALL
NATIVE_NAME (getQuitKeycode) (JNIEnv *env, jobject object)
{
/* Likewise. */
return (jint) android_quit_keycode;
}
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. */
/* 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 (!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, (jobject) 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, (jobject) 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);
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 ();
}
}
/* Return a reference to the local reference HANDLE suitable for
indefinite retention and save its value into HANDLE, deleting HANDLE,
or signal an error if such a reference cannot be allocated. */
static android_handle
android_globalize_reference (jobject handle)
{
jobject global;
/* Though Android 8.0 and later can support an unlimited number of
active local references, they remain inappropriate in threading
configurations for being local to the current thread. */
global = (*android_java_env)->NewGlobalRef (android_java_env,
handle);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (handle);
if (__builtin_expect (global == NULL, 0))
error ("JNI global reference reserves exhausted");
/* Save the value of this handle into HANDLE. */
(*android_java_env)->SetLongField (android_java_env, global,
handle_class.handle,
(jlong) global);
verify (sizeof (jlong) >= sizeof (intptr_t));
return (intptr_t) global;
}
/* 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;
bool override_redirect;
parent_object = android_resolve_handle (parent);
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>",
"(Lorg/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, parent_object,
(jint) x, (jint) y,
(jint) width, (jint) height,
(jboolean) override_redirect);
android_exception_check ();
window = android_globalize_reference (object);
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)
{
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>", "()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");
emacs_gc_line_style
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"line_style", "I");
emacs_gc_line_width
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"line_width", "I");
emacs_gc_dash_offset
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"dash_offset", "I");
emacs_gc_dashes
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"dashes", "[I");
}
struct android_gc *
android_create_gc (enum android_gc_value_mask mask,
struct android_gc_values *values)
{
struct android_gc *gc;
jobject object;
android_init_emacs_gc_class ();
gc = xmalloc (sizeof *gc);
gc->gcontext = 0;
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;
gc->line_style = ANDROID_LINE_SOLID;
gc->line_width = 0;
gc->dash_offset = 0;
gc->dashes = NULL;
gc->n_segments = 0;
object = (*android_java_env)->NewObject (android_java_env,
emacs_gc_class,
emacs_gc_constructor);
android_exception_check ();
gc->gcontext = android_globalize_reference (object);
android_change_gc (gc, mask, values);
return gc;
}
void
android_free_gc (struct android_gc *gc)
{
android_destroy_handle (gc->gcontext);
xfree (gc->dashes);
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, array;
jboolean clip_changed;
clip_changed = false;
android_init_emacs_gc_class ();
gcontext = android_resolve_handle (gc->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_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_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_GC_LINE_STYLE)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_line_style,
values->line_style);
gc->line_style = values->line_style;
}
if (mask & ANDROID_GC_LINE_WIDTH)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_line_width,
values->line_width);
gc->line_width = values->line_width;
}
if (mask & ANDROID_GC_DASH_OFFSET)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_dash_offset,
values->dash_offset);
gc->dash_offset = values->dash_offset;
}
if (mask & ANDROID_GC_DASH_LIST)
{
/* Compare the new dash pattern with the old. */
if (gc->dashes && gc->n_segments == 1
&& gc->dashes[0] == values->dash)
/* If they be identical, nothing needs to change. */
mask &= ~ANDROID_GC_DASH_LIST;
else
{
if (gc->n_segments != 1)
gc->dashes = xrealloc (gc->dashes, sizeof *gc->dashes);
gc->n_segments = 1;
gc->dashes[0] = values->dash;
array = (*android_java_env)->NewIntArray (android_java_env, 1);
android_exception_check ();
(*android_java_env)->SetIntArrayRegion (android_java_env,
array, 0, 1,
(jint *) &values->dash);
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_dashes,
array);
ANDROID_DELETE_LOCAL_REF (array);
}
}
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);
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_set_dashes (struct android_gc *gc, int dash_offset,
int *dash_list, int n)
{
int i;
jobject array, gcontext;
gcontext = android_resolve_handle (gc->gcontext);
if (n == gc->n_segments
&& (!gc->dashes || !memcmp (gc->dashes, dash_list,
sizeof *dash_list * n)))
/* No change in the dash list. */
goto set_offset;
if (!n)
{
/* Reset the dash list to its initial empty state. */
xfree (gc->dashes);
gc->dashes = NULL;
array = NULL;
}
else
{
/* If the size of the array has not changed, it can be reused. */
if (n != gc->n_segments)
{
gc->dashes = xrealloc (gc->dashes, sizeof *gc->dashes * n);
array = (*android_java_env)->NewIntArray (android_java_env, n);
android_exception_check ();
}
else
array = (*android_java_env)->GetObjectField (android_java_env,
gcontext,
emacs_gc_dashes);
/* Copy the list of segments into both arrays. */
for (i = 0; i < n; ++i)
gc->dashes[i] = dash_list[i];
verify (sizeof (int) == sizeof (jint));
(*android_java_env)->SetIntArrayRegion (android_java_env,
array, 0, n,
(jint *) dash_list);
}
/* Replace the dash array in the GContext object if required. */
if (n != gc->n_segments)
{
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_dashes,
array);
ANDROID_DELETE_LOCAL_REF (array);
}
gc->n_segments = n;
set_offset:
/* And the offset. */
if (dash_offset != gc->dash_offset)
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_dash_offset,
dash_offset);
gc->dash_offset = dash_offset;
}
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);
parent = android_resolve_handle (parent_handle);
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_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);
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);
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);
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);
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_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. In addition, the size of GCClipMask and GCDashList is
not static, precluding their retrieval. */
}
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_handle (handle);
gcontext = android_resolve_handle (gc->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;
}
}
}
}
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 = &rect;
}
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_INVERT: */
/* do_blit = android_blit_invert; */
/* A GC with its operation set to ANDROID_GC_INVERT is never given
to CopyArea. */
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_java_env)->IsInstanceOf (android_java_env,
(jobject) dest,
window_class.class))
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_handle (drawable);
gcontext = android_resolve_handle (gc->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_handle (handle);
gcontext = android_resolve_handle (gc->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_handle (handle);
gcontext = android_resolve_handle (gc->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_handle (handle);
gcontext = android_resolve_handle (gc->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)
{
jobject object;
object = (*android_java_env)->NewObject (android_java_env,
pixmap_class.class,
pixmap_class.constructor_mutable,
(jint) width, (jint) height,
(jint) depth);
android_exception_check ();
return android_globalize_reference (object);
}
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_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, width, height,
1, 0, 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_handle (handle);
/* 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);
/* 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);
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);
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);
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);
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_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;
jlong *longs;
jmethodID method;
window = android_resolve_handle (handle);
/* 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);
longs
= (*android_java_env)->GetLongArrayElements (android_java_env, array,
NULL);
android_exception_check_nonnull (longs, array);
for (i = 1; i < nelements; ++i)
/* Subtract one from the index into children, since the parent is
not included. */
children[i - 1] = longs[i];
/* Finally, return the parent and other values. */
*root_return = 0;
*parent_return = longs[0];
*children_return = children;
*nchildren_return = nelements - 1;
/* Release the array contents. */
(*android_java_env)->ReleaseLongArrayElements (android_java_env, array,
longs, 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);
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);
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;
window = android_resolve_handle (event->window);
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_handle (drawable);
/* 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);
/* 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);
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);
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 ();
}
/* Set the WM name of HANDLE to STRING, a Java string. This name
provides the task description of activities that receive HANDLE. */
void
android_set_wm_name (android_window handle, jstring name)
{
jmethodID method;
jobject window;
window = android_resolve_handle (handle);
method = window_class.set_wm_name;
if (android_get_current_api_level () < 21)
return;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
window_class.class, method,
name);
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);
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 ();
}
/* emacs_abort implementation for Android. This logs a stack
trace. */
void
emacs_abort (void)
{
volatile char *foo;
__android_log_print (ANDROID_LOG_FATAL, __func__,
"emacs_abort called, please review the following"
" stack trace");
/* Induce a NULL pointer dereference to make debuggerd generate a
tombstone. */
foo = NULL;
*foo = '\0';
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. */
#define likely(cond) __builtin_expect (cond, 1)
/* 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);
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_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_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);
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);
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_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)
{
jobject object;
/* Next, create the cursor. */
object = (*android_java_env)->NewObject (android_java_env,
cursor_class.class,
cursor_class.constructor,
(jint) shape);
android_exception_check ();
return android_globalize_reference (object);
}
void
android_define_cursor (android_window window, android_cursor cursor)
{
jobject window1, cursor1;
jmethodID method;
window1 = android_resolve_handle (window);
cursor1 = android_resolve_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)
{
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 */