/* Android initialization for GNU Emacs.
Copyright (C) 2023 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 . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Old NDK versions lack MIN and MAX. */
#include
#include
#include
#include "android.h"
#include "androidgui.h"
#include "lisp.h"
#include "blockinput.h"
#include "coding.h"
#include "epaths.h"
/* Whether or not Emacs is running inside the application process and
Android windowing should be enabled. */
bool android_init_gui;
#ifndef ANDROID_STUBIFY
#if __ANDROID_API__ >= 9
#include
#include
#else
#include "android-asset.h"
#endif
#include
#include
#include
#include
#include
#define ANDROID_THROW(env, class, msg) \
((*(env))->ThrowNew ((env), (*(env))->FindClass ((env), class), msg))
#define ANDROID_MAX_ASSET_FD 65535
struct android_fd_table_entry
{
/* Various flags associated with this table. */
short flags;
/* The stat buffer associated with this entry. */
struct stat statb;
};
enum android_fd_table_entry_flags
{
ANDROID_FD_TABLE_ENTRY_IS_VALID = 1,
};
struct android_emacs_service
{
jclass class;
jmethodID fill_rectangle;
jmethodID fill_polygon;
jmethodID draw_rectangle;
jmethodID draw_line;
jmethodID draw_point;
jmethodID copy_area;
jmethodID clear_window;
jmethodID clear_area;
jmethodID ring_bell;
jmethodID query_tree;
jmethodID get_screen_width;
jmethodID get_screen_height;
jmethodID detect_mouse;
jmethodID name_keysym;
jmethodID browse_url;
jmethodID restart_emacs;
jmethodID update_ic;
jmethodID reset_ic;
jmethodID open_content_uri;
jmethodID check_content_uri;
jmethodID query_battery;
jmethodID display_toast;
jmethodID update_extracted_text;
jmethodID update_cursor_anchor_info;
};
struct android_emacs_pixmap
{
jclass class;
jmethodID constructor;
jmethodID constructor_mutable;
};
struct android_graphics_point
{
jclass class;
jmethodID constructor;
};
struct android_emacs_drawable
{
jclass class;
jmethodID get_bitmap;
jmethodID damage_rect;
};
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 get_window_geometry;
jmethodID translate_coordinates;
jmethodID set_dont_accept_focus;
jmethodID set_dont_focus_on_map;
jmethodID define_cursor;
};
struct android_emacs_cursor
{
jclass class;
jmethodID constructor;
};
/* The API level of the current device. */
static int android_api_level;
/* The asset manager being used. */
static AAssetManager *asset_manager;
/* Whether or not Emacs has been initialized. */
static int emacs_initialized;
/* The directory used to store site-lisp. */
char *android_site_load_path;
/* The directory used to store native libraries. */
char *android_lib_dir;
/* The directory used to store game files. */
char *android_game_path;
/* The directory used to store temporary files. */
char *android_cache_dir;
/* The list of archive files within which the Java virtual macine
looks for class files. */
char *android_class_path;
/* The display's pixel densities. */
double android_pixel_density_x, android_pixel_density_y;
/* The Android application data directory. */
static char *android_files_dir;
/* Array of structures used to hold asset information corresponding to
a file descriptor. This assumes Emacs does not do funny things
with dup. It currently does not. */
static struct android_fd_table_entry android_table[ANDROID_MAX_ASSET_FD];
/* The Java environment being used for the main thread. */
JNIEnv *android_java_env;
/* The EmacsGC class. */
static jclass emacs_gc_class;
/* Various fields. */
static jfieldID emacs_gc_foreground, emacs_gc_background;
static jfieldID emacs_gc_function, emacs_gc_clip_rects;
static jfieldID emacs_gc_clip_x_origin, emacs_gc_clip_y_origin;
static jfieldID emacs_gc_stipple, emacs_gc_clip_mask;
static jfieldID emacs_gc_fill_style, emacs_gc_ts_origin_x;
static jfieldID emacs_gc_ts_origin_y;
/* The constructor and one function. */
static jmethodID emacs_gc_constructor, emacs_gc_mark_dirty;
/* The Rect class. */
static jclass android_rect_class;
/* Its constructor. */
static jmethodID android_rect_constructor;
/* The EmacsService object. */
static jobject emacs_service;
/* Various methods associated with the EmacsService. */
static 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;
/* 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__ */
/* 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 *volatile 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. */
volatile int num_events;
/* Circular queue of events. */
struct android_event_container events;
};
/* Arguments to pselect used by the select thread. */
static volatile int android_pselect_nfds;
static fd_set *volatile android_pselect_readfds;
static fd_set *volatile android_pselect_writefds;
static fd_set *volatile android_pselect_exceptfds;
static struct timespec *volatile 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
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, writefds;
char byte;
#else
sigset_t signals, waitset;
int sig;
#endif
#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, &writefds,
android_pselect_exceptfds,
android_pselect_timeout,
NULL);
/* Subtract 1 from rc if writefds contains the select pipe. */
if (FD_ISSET (select_pipe[0], &writefds))
rc -= 1;
/* Save the writefds back again. */
if (android_pselect_writefds)
*android_pselect_writefds = writefds;
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;
/* 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;
#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. */
void
android_wait_event (void)
{
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);
}
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:
raise (SIGIO);
break;
default:
break;
}
}
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
/* 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)
{
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 (android_pselect_rc >= 0)
nfds_return += android_pselect_rc;
if (!nfds_return && android_pselect_rc < 0)
nfds_return = android_pselect_rc;
/* This is to shut up process.c when pselect gets EINTR. */
if (nfds_return < 0)
errno = EINTR;
/* Now check for and run anything the UI thread wants to run in the
main thread. */
android_check_query ();
return nfds_return;
}
static void *
android_run_debug_thread (void *data)
{
FILE *file;
int fd;
char *line;
size_t n;
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;
}
/* Asset directory handling functions. ``directory-tree'' is a file in
the root of the assets directory describing its contents.
See lib-src/asset-directory-tool for more details. */
/* The Android directory tree. */
static const char *directory_tree;
/* The size of the directory tree. */
static size_t directory_tree_size;
/* Read an unaligned (32-bit) long from the address POINTER. */
static unsigned int
android_extract_long (char *pointer)
{
unsigned int number;
memcpy (&number, pointer, sizeof number);
return number;
}
/* Scan to the file FILE in the asset directory tree. Return a
pointer to the end of that file (immediately before any children)
in the directory tree, or NULL if that file does not exist.
If returning non-NULL, also return the offset to the end of the
last subdirectory or file in *LIMIT_RETURN. LIMIT_RETURN may be
NULL.
FILE must have less than 11 levels of nesting. If it ends with a
trailing slash, then NULL will be returned if it is not actually a
directory. */
static const char *
android_scan_directory_tree (char *file, size_t *limit_return)
{
char *token, *saveptr, *copy, *copy1, *start, *max, *limit;
size_t token_length, ntokens, i;
char *tokens[10];
USE_SAFE_ALLOCA;
/* Skip past the 5 byte header. */
start = (char *) directory_tree + 5;
/* Figure out the current limit. */
limit = (char *) directory_tree + directory_tree_size;
/* Now, split `file' into tokens, with the delimiter being the file
name separator. Look for the file and seek past it. */
ntokens = 0;
saveptr = NULL;
copy = copy1 = xstrdup (file);
memset (tokens, 0, sizeof tokens);
while ((token = strtok_r (copy, "/", &saveptr)))
{
copy = NULL;
/* Make sure ntokens is within bounds. */
if (ntokens == ARRAYELTS (tokens))
{
xfree (copy1);
goto fail;
}
tokens[ntokens] = SAFE_ALLOCA (strlen (token) + 1);
memcpy (tokens[ntokens], token, strlen (token) + 1);
ntokens++;
}
/* Free the copy created for strtok_r. */
xfree (copy1);
/* If there are no tokens, just return the start of the directory
tree. */
if (!ntokens)
{
SAFE_FREE ();
/* Return the size of the directory tree as the limit.
Do not subtract the initial header bytes, as the limit
is an offset from the start of the file. */
if (limit_return)
*limit_return = directory_tree_size;
return start;
}
/* Loop through tokens, indexing the directory tree each time. */
for (i = 0; i < ntokens; ++i)
{
token = tokens[i];
/* Figure out how many bytes to compare. */
token_length = strlen (token);
again:
/* If this would be past the directory, return NULL. */
if (start + token_length > limit)
goto fail;
/* Now compare the file name. */
if (!memcmp (start, token, token_length))
{
/* They probably match. Find the NULL byte. It must be
either one byte past start + token_length, with the last
byte a trailing slash (indicating that it is a
directory), or just start + token_length. Return 4 bytes
past the next NULL byte. */
max = memchr (start, 0, limit - start);
if (max != start + token_length
&& !(max == start + token_length + 1
&& *(max - 1) == '/'))
goto false_positive;
/* Return it if it exists and is in range, and this is the
last token. Otherwise, set it as start and the limit as
start + the offset and continue the loop. */
if (max && max + 5 <= limit)
{
if (i < ntokens - 1)
{
start = max + 5;
limit = ((char *) directory_tree
+ android_extract_long (max + 1));
/* Make sure limit is still in range. */
if (limit > directory_tree + directory_tree_size
|| start > directory_tree + directory_tree_size)
goto fail;
continue;
}
/* Now see if max is not a directory and file is. If
file is a directory, then return NULL. */
if (*(max - 1) != '/' && file[strlen (file) - 1] == '/')
max = NULL;
else
{
/* Figure out the limit. */
if (limit_return)
*limit_return = android_extract_long (max + 1);
/* Go to the end of this file. */
max += 5;
}
SAFE_FREE ();
return max;
}
/* Return NULL otherwise. */
__android_log_print (ANDROID_LOG_WARN, __func__,
"could not scan to end of directory tree"
": %s", file);
goto fail;
}
false_positive:
/* No match was found. Set start to the next sibling and try
again. */
start = memchr (start, 0, limit - start);
if (!start || start + 5 > limit)
goto fail;
start = ((char *) directory_tree
+ android_extract_long (start + 1));
/* Make sure start is still in bounds. */
if (start > limit)
goto fail;
/* Continue the loop. */
goto again;
}
fail:
SAFE_FREE ();
return NULL;
}
/* Return whether or not the directory tree entry DIR is a
directory.
DIR should be a value returned by
`android_scan_directory_tree'. */
static bool
android_is_directory (const char *dir)
{
/* If the directory is the directory tree, then it is a
directory. */
if (dir == directory_tree + 5)
return true;
/* Otherwise, look 5 bytes behind. If it is `/', then it is a
directory. */
return (dir - 6 >= directory_tree
&& *(dir - 6) == '/');
}
/* 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
return "Android user";
#endif
}
/* Given a real file name, return the part that describes its asset
path, or NULL if it is not an asset. */
static const char *
android_get_asset_name (const char *filename)
{
if (!strcmp (filename, "/assets") || !strcmp (filename, "/assets/"))
return "/";
if (!strncmp (filename, "/assets/", sizeof "/assets/" - 1))
return filename + (sizeof "/assets/" - 1);
return NULL;
}
/* Return whether or not the specified FILENAME actually resolves to a
content resolver URI. */
static bool
android_content_name_p (const char *filename)
{
/* Content URIs aren't supported before Android 4.4, so return
false. */
if (android_api_level < 19)
return false;
return (!strcmp (filename, "/content")
|| !strncmp (filename, "/content/",
sizeof "/content/" - 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, *saveptr, *copy;
size_t n;
n = PATH_MAX;
/* First handle content ``URIs'' without a provider. */
if (!strcmp (filename, "/content")
|| !strcmp (filename, "/content/"))
return "content://";
/* Next handle ordinary file names. */
if (strncmp (filename, "/content/", sizeof "/content/" - 1))
return NULL;
/* Forward past the first directory specifying the schema. */
copy = xstrdup (filename + sizeof "/content");
token = saveptr = NULL;
head = stpcpy (buffer, "content:/");
/* Split FILENAME by slashes. */
while ((token = strtok_r (!token ? copy : NULL,
"/", &saveptr)))
{
head = stpncpy (head, "/", n--);
head = stpncpy (head, token, n);
/* Check that head has not overflown the buffer. */
eassert ((head - buffer) <= PATH_MAX);
n = PATH_MAX - (head - buffer);
}
/* Make sure the given buffer ends up NULL terminated. */
buffer[PATH_MAX] = '\0';
xfree (copy);
return buffer;
}
/* Return whether or not the specified FILENAME is an accessible
content URI. MODE specifies what to check. */
static bool
android_check_content_access (const char *filename, int mode)
{
const char *name;
jobject string;
size_t length;
jboolean rc;
name = android_get_content_name (filename);
length = strlen (name);
string = (*android_java_env)->NewByteArray (android_java_env,
length);
android_exception_check ();
(*android_java_env)->SetByteArrayRegion (android_java_env,
string, 0, length,
(jbyte *) name);
rc = (*android_java_env)->CallBooleanMethod (android_java_env,
emacs_service,
service_class.check_content_uri,
string,
(jboolean) ((mode & R_OK)
!= 0),
(jboolean) ((mode & W_OK)
!= 0));
android_exception_check_1 (string);
ANDROID_DELETE_LOCAL_REF (string);
return rc;
}
/* Like fstat. However, look up the asset corresponding to the file
descriptor. If it exists, return the right information. */
int
android_fstat (int fd, struct stat *statb)
{
if (fd < ANDROID_MAX_ASSET_FD
&& (android_table[fd].flags
& ANDROID_FD_TABLE_ENTRY_IS_VALID))
{
memcpy (statb, &android_table[fd].statb,
sizeof *statb);
return 0;
}
return fstat (fd, statb);
}
static int android_lookup_asset_directory_fd (int,
const char *restrict *,
const char *restrict);
/* Like fstatat. However, if dirfd is AT_FDCWD and PATHNAME is an
asset, find the information for the corresponding asset, and if
dirfd is an offset into directory_tree as returned by
`android_dirfd', find the information within the corresponding
directory tree entry. */
int
android_fstatat (int dirfd, const char *restrict pathname,
struct stat *restrict statbuf, int flags)
{
AAsset *asset_desc;
const char *asset;
const char *asset_dir;
int fd, rc;
/* Look up whether or not DIRFD belongs to an open struct
android_dir. */
if (dirfd != AT_FDCWD)
dirfd
= android_lookup_asset_directory_fd (dirfd, &pathname,
pathname);
if (dirfd == AT_FDCWD
&& asset_manager
&& (asset = android_get_asset_name (pathname)))
{
/* Look up whether or not PATHNAME happens to be a
directory. */
asset_dir = android_scan_directory_tree ((char *) asset,
NULL);
if (!asset_dir)
{
errno = ENOENT;
return -1;
}
if (android_is_directory (asset_dir))
{
memset (statbuf, 0, sizeof *statbuf);
/* Fill in the stat buffer. */
statbuf->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
return 0;
}
/* AASSET_MODE_STREAMING is fastest here. */
asset_desc = AAssetManager_open (asset_manager, asset,
AASSET_MODE_STREAMING);
if (!asset_desc)
return ENOENT;
memset (statbuf, 0, sizeof *statbuf);
/* Fill in the stat buffer. */
statbuf->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
statbuf->st_size = AAsset_getLength (asset_desc);
/* Close the asset. */
AAsset_close (asset_desc);
return 0;
}
if (dirfd == AT_FDCWD
&& android_init_gui
&& android_content_name_p (pathname))
{
/* This is actually a content:// URI. Open that file and call
stat on it. */
fd = android_open (pathname, O_RDONLY, 0);
if (fd < 0)
return -1;
rc = fstat (fd, statbuf);
android_close (fd);
return rc;
}
return fstatat (dirfd, pathname, statbuf, flags);
}
/* Return if NAME, a file name relative to the /assets directory, is
accessible, as long as !(amode & W_OK). */
static bool
android_file_access_p (const char *name, int amode)
{
if (!asset_manager)
return false;
if (!(amode & W_OK))
{
if (!strcmp (name, "") || !strcmp (name, "/"))
/* /assets always exists. */
return true;
/* Check if the file exists by looking in the ``directory tree''
asset generated during the build process. This is used
instead of the AAsset functions, because the latter are
buggy and treat directories inconsistently. */
return android_scan_directory_tree ((char *) name, NULL) != NULL;
}
return false;
}
/* Do the same as android_hack_asset_fd, but use an unlinked temporary
file to cater to old Android kernels where ashmem files are not
readable. */
static int
android_hack_asset_fd_fallback (AAsset *asset)
{
int fd;
char filename[PATH_MAX];
size_t size;
void *mem;
/* Assets must be small enough to fit in size_t, if off_t is
larger. */
size = AAsset_getLength (asset);
/* Get an unlinked file descriptor from a file in the cache
directory, which is guaranteed to only be written to by Emacs.
Creating an ashmem file descriptor and reading from it doesn't
work on these old Android versions. */
snprintf (filename, PATH_MAX, "%s/temp~unlinked.%d",
android_cache_dir, getpid ());
fd = open (filename, O_CREAT | O_RDWR | O_TRUNC,
S_IRUSR | S_IWUSR);
if (fd < 0)
return -1;
if (unlink (filename))
goto fail;
if (ftruncate (fd, size))
goto fail;
mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"mmap: %s", strerror (errno));
goto fail;
}
if (AAsset_read (asset, mem, size) != size)
{
/* Too little was read. Close the file descriptor and
report an error. */
__android_log_print (ANDROID_LOG_ERROR, __func__,
"AAsset_read: %s", strerror (errno));
goto fail;
}
munmap (mem, size);
return fd;
fail:
close (fd);
return -1;
}
/* Pointer to the `ASharedMemory_create' function which is loaded
dynamically. */
static int (*asharedmemory_create) (const char *, size_t);
/* Return whether or not shared memory file descriptors can also be
read from, and are thus suitable for creating asset files.
This does not work on some ancient Android systems running old
versions of the kernel. */
static bool
android_detect_ashmem (void)
{
int fd, rc;
void *mem;
char test_buffer[10];
memcpy (test_buffer, "abcdefghi", 10);
/* Create the file descriptor to be used for the test. */
/* Android 28 and earlier let Emacs access /dev/ashmem directly, so
prefer that over using ASharedMemory. */
if (android_api_level <= 28)
{
fd = open ("/dev/ashmem", O_RDWR);
if (fd < 0)
return false;
/* An empty name means the memory area will exist until the file
descriptor is closed, because no other process can
attach. */
rc = ioctl (fd, ASHMEM_SET_NAME, "");
if (rc < 0)
{
close (fd);
return false;
}
rc = ioctl (fd, ASHMEM_SET_SIZE, sizeof test_buffer);
if (rc < 0)
{
close (fd);
return false;
}
}
else
{
/* On the other hand, SELinux restrictions on Android 29 and
later require that Emacs use a system service to obtain
shared memory. Load this dynamically, as this service is not
available on all versions of the NDK. */
if (!asharedmemory_create)
{
*(void **) (&asharedmemory_create)
= dlsym (RTLD_DEFAULT, "ASharedMemory_create");
if (!asharedmemory_create)
{
__android_log_print (ANDROID_LOG_FATAL, __func__,
"dlsym: %s\n",
strerror (errno));
emacs_abort ();
}
}
fd = asharedmemory_create ("", sizeof test_buffer);
if (fd < 0)
return false;
}
/* Now map the resource and write the test contents. */
mem = mmap (NULL, sizeof test_buffer, PROT_WRITE,
MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
{
close (fd);
return false;
}
/* Copy over the test contents. */
memcpy (mem, test_buffer, sizeof test_buffer);
/* Return anyway even if munmap fails. */
munmap (mem, sizeof test_buffer);
/* Try to read the content back into test_buffer. If this does not
compare equal to the original string, or the read fails, then
ashmem descriptors are not readable on this system. */
if ((read (fd, test_buffer, sizeof test_buffer)
!= sizeof test_buffer)
|| memcmp (test_buffer, "abcdefghi", sizeof test_buffer))
{
__android_log_print (ANDROID_LOG_WARN, __func__,
"/dev/ashmem does not produce real"
" temporary files on this system, so"
" Emacs will fall back to creating"
" unlinked temporary files.");
close (fd);
return false;
}
close (fd);
return true;
}
/* Get a file descriptor backed by a temporary in-memory file for the
given asset. */
static int
android_hack_asset_fd (AAsset *asset)
{
static bool ashmem_readable_p;
static bool ashmem_initialized;
int fd, rc;
unsigned char *mem;
size_t size;
/* The first time this function is called, try to determine whether
or not ashmem file descriptors can be read from. */
if (!ashmem_initialized)
ashmem_readable_p
= android_detect_ashmem ();
ashmem_initialized = true;
/* If it isn't, fall back. */
if (!ashmem_readable_p)
return android_hack_asset_fd_fallback (asset);
/* Assets must be small enough to fit in size_t, if off_t is
larger. */
size = AAsset_getLength (asset);
/* Android 28 and earlier let Emacs access /dev/ashmem directly, so
prefer that over using ASharedMemory. */
if (android_api_level <= 28)
{
fd = open ("/dev/ashmem", O_RDWR);
if (fd < 0)
return -1;
/* An empty name means the memory area will exist until the file
descriptor is closed, because no other process can
attach. */
rc = ioctl (fd, ASHMEM_SET_NAME, "");
if (rc < 0)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"ioctl ASHMEM_SET_NAME: %s",
strerror (errno));
close (fd);
return -1;
}
rc = ioctl (fd, ASHMEM_SET_SIZE, size);
if (rc < 0)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"ioctl ASHMEM_SET_SIZE: %s",
strerror (errno));
close (fd);
return -1;
}
if (!size)
return fd;
/* Now map the resource. */
mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"mmap: %s", strerror (errno));
close (fd);
return -1;
}
if (AAsset_read (asset, mem, size) != size)
{
/* Too little was read. Close the file descriptor and
report an error. */
__android_log_print (ANDROID_LOG_ERROR, __func__,
"AAsset_read: %s", strerror (errno));
close (fd);
return -1;
}
/* Return anyway even if munmap fails. */
munmap (mem, size);
return fd;
}
/* On the other hand, SELinux restrictions on Android 29 and later
require that Emacs use a system service to obtain shared memory.
Load this dynamically, as this service is not available on all
versions of the NDK. */
if (!asharedmemory_create)
{
*(void **) (&asharedmemory_create)
= dlsym (RTLD_DEFAULT, "ASharedMemory_create");
if (!asharedmemory_create)
{
__android_log_print (ANDROID_LOG_FATAL, __func__,
"dlsym: %s\n",
strerror (errno));
emacs_abort ();
}
}
fd = asharedmemory_create ("", size);
if (fd < 0)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"ASharedMemory_create: %s",
strerror (errno));
return -1;
}
/* Now map the resource. */
mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"mmap: %s", strerror (errno));
close (fd);
return -1;
}
if (AAsset_read (asset, mem, size) != size)
{
/* Too little was read. Close the file descriptor and
report an error. */
__android_log_print (ANDROID_LOG_ERROR, __func__,
"AAsset_read: %s", strerror (errno));
close (fd);
return -1;
}
/* Return anyway even if munmap fails. */
munmap (mem, size);
return fd;
}
/* Make FD close-on-exec. If any system call fails, do not abort, but
log a warning to the system log. */
static void
android_close_on_exec (int fd)
{
int flags, rc;
flags = fcntl (fd, F_GETFD);
if (flags < 0)
{
__android_log_print (ANDROID_LOG_WARN, __func__,
"fcntl: %s", strerror (errno));
return;
}
rc = fcntl (fd, F_SETFD, flags | O_CLOEXEC);
if (rc < 0)
{
__android_log_print (ANDROID_LOG_WARN, __func__,
"fcntl: %s", strerror (errno));
return;
}
}
/* `open' and such are modified even though they exist on Android,
because Emacs treats "/assets/" as a special directory that must
contain all assets in the application package. */
int
android_open (const char *filename, int oflag, mode_t mode)
{
const char *name;
AAsset *asset;
int fd;
size_t length;
jobject string;
if (asset_manager && (name = android_get_asset_name (filename)))
{
/* If Emacs is trying to write to the file, return NULL. */
if (oflag & O_WRONLY || oflag & O_RDWR)
{
errno = EROFS;
return -1;
}
if (oflag & O_DIRECTORY)
{
errno = ENOTSUP;
return -1;
}
/* If given AASSET_MODE_BUFFER (which is what Emacs probably
does, given that a file descriptor is not always available),
the framework fails to uncompress the data before it returns
a file descriptor. */
asset = AAssetManager_open (asset_manager, name,
AASSET_MODE_STREAMING);
if (!asset)
{
errno = ENOENT;
return -1;
}
/* Create a shared memory file descriptor containing the asset
contents.
The documentation misleads people into thinking that
AAsset_openFileDescriptor does precisely this. However, it
instead returns an offset into any uncompressed assets in the
ZIP archive. This cannot be found in its documentation. */
fd = android_hack_asset_fd (asset);
if (fd == -1)
{
AAsset_close (asset);
errno = ENXIO;
return -1;
}
/* If O_CLOEXEC is specified, make the file descriptor close on
exec too. */
if (oflag & O_CLOEXEC)
android_close_on_exec (fd);
if (fd >= ANDROID_MAX_ASSET_FD || fd < 0)
{
/* Too bad. Pretend this is an out of memory error. */
errno = ENOMEM;
if (fd >= 0)
close (fd);
fd = -1;
}
else
{
assert (!(android_table[fd].flags
& ANDROID_FD_TABLE_ENTRY_IS_VALID));
android_table[fd].flags = ANDROID_FD_TABLE_ENTRY_IS_VALID;
memset (&android_table[fd].statb, 0,
sizeof android_table[fd].statb);
/* Fill in some information that will be reported to
callers of android_fstat, among others. */
android_table[fd].statb.st_mode
= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
/* Owned by root. */
android_table[fd].statb.st_uid = 0;
android_table[fd].statb.st_gid = 0;
/* Size of the file. */
android_table[fd].statb.st_size
= AAsset_getLength (asset);
}
AAsset_close (asset);
return fd;
}
if (android_init_gui && android_content_name_p (filename))
{
/* This is a content:// URI. Ask the system for a descriptor to
that file. */
name = android_get_content_name (filename);
length = strlen (name);
/* Check if the mode is valid. */
if (oflag & O_DIRECTORY)
{
errno = ENOTSUP;
return -1;
}
/* Allocate a buffer to hold the file name. */
string = (*android_java_env)->NewByteArray (android_java_env,
length);
if (!string)
{
(*android_java_env)->ExceptionClear (android_java_env);
errno = ENOMEM;
return -1;
}
(*android_java_env)->SetByteArrayRegion (android_java_env,
string, 0, length,
(jbyte *) name);
/* Try to open the file descriptor. */
fd
= (*android_java_env)->CallIntMethod (android_java_env,
emacs_service,
service_class.open_content_uri,
string,
(jboolean) ((mode & O_WRONLY
|| mode & O_RDWR)
!= 0),
(jboolean) !(mode & O_WRONLY),
(jboolean) ((mode & O_TRUNC)
!= 0));
if ((*android_java_env)->ExceptionCheck (android_java_env))
{
(*android_java_env)->ExceptionClear (android_java_env);
errno = ENOMEM;
ANDROID_DELETE_LOCAL_REF (string);
return -1;
}
/* If fd is -1, just assume that the file does not exist,
and return -1 with errno set to ENOENT. */
if (fd == -1)
{
errno = ENOENT;
goto skip;
}
if (mode & O_CLOEXEC)
android_close_on_exec (fd);
skip:
ANDROID_DELETE_LOCAL_REF (string);
return fd;
}
return open (filename, oflag, mode);
}
/* Like close. However, remove the file descriptor from the asset
table as well. */
int
android_close (int fd)
{
if (fd < ANDROID_MAX_ASSET_FD)
android_table[fd].flags = 0;
return close (fd);
}
/* Like fclose. However, remove any information associated with
FILE's file descriptor from the asset table as well. */
int
android_fclose (FILE *stream)
{
int fd;
fd = fileno (stream);
if (fd != -1 && fd < ANDROID_MAX_ASSET_FD)
android_table[fd].flags = 0;
return fclose (stream);
}
/* 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/. Place the name in BUFFER, which should be able to
hold size bytes. Value is 0 upon success, and 1 upon failure. */
static int
android_proc_name (int fd, char *buffer, size_t size)
{
char format[sizeof "/proc/self/fd/"
+ INT_STRLEN_BOUND (int)];
ssize_t read;
sprintf (format, "/proc/self/fd/%d", fd);
read = readlink (format, buffer, size - 1);
if (read == -1)
return 1;
buffer[read] = '\0';
return 0;
}
/* JNI functions called by Java. */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#endif
JNIEXPORT jint JNICALL
NATIVE_NAME (dup) (JNIEnv *env, jobject object, jint fd)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
return dup (fd);
}
JNIEXPORT 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,
jobject class_path,
jobject emacs_service_object)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
int pipefd[2];
pthread_t thread;
const char *java_string;
AAsset *asset;
/* This may be called from multiple threads. setEmacsParams should
only ever be called once. */
if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_SEQ_CST))
{
ANDROID_THROW (env, "java/lang/IllegalArgumentException",
"Emacs was already initialized!");
return;
}
android_pixel_density_x = pixel_density_x;
android_pixel_density_y = pixel_density_y;
__android_log_print (ANDROID_LOG_INFO, __func__,
"Initializing "PACKAGE_STRING"...\nPlease report bugs to "
PACKAGE_BUGREPORT". Thanks.\n");
/* Set the asset manager. */
asset_manager = AAssetManager_fromJava (env, local_asset_manager);
/* Initialize the directory tree. */
asset = AAssetManager_open (asset_manager, "directory-tree",
AASSET_MODE_BUFFER);
if (!asset)
{
__android_log_print (ANDROID_LOG_FATAL, __func__,
"Failed to open directory tree");
emacs_abort ();
}
directory_tree = AAsset_getBuffer (asset);
if (!directory_tree)
emacs_abort ();
/* Now figure out how big the directory tree is, and compare the
first few bytes. */
directory_tree_size = AAsset_getLength (asset);
if (directory_tree_size < 5
|| memcmp (directory_tree, "EMACS", 5))
{
__android_log_print (ANDROID_LOG_FATAL, __func__,
"Directory tree has bad magic");
emacs_abort ();
}
/* Hold a VM reference to the asset manager to prevent the native
object from being deleted. */
(*env)->NewGlobalRef (env, local_asset_manager);
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_files_dir)
emacs_abort ();
(*env)->ReleaseStringUTFChars (env, (jstring) class_path,
java_string);
}
/* 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);
/* Make a reference to the Emacs service. */
if (emacs_service_object)
{
emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
if (!emacs_service)
emacs_abort ();
}
/* Set up events. */
android_init_events ();
/* OK, setup is now complete. The caller may start the Emacs thread
now. */
}
JNIEXPORT jobject JNICALL
NATIVE_NAME (getProcName) (JNIEnv *env, jobject object, jint fd)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
char buffer[PATH_MAX + 1];
size_t length;
jbyteArray array;
if (android_proc_name (fd, buffer, PATH_MAX + 1))
return NULL;
/* Return a byte array, as Java strings cannot always encode file
names. */
length = strlen (buffer);
array = (*env)->NewByteArray (env, length);
if (!array)
return NULL;
(*env)->SetByteArrayRegion (env, array, 0, length,
(jbyte *) 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); \
assert (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 (copy_area, "copyArea",
"(Lorg/gnu/emacs/EmacsDrawable;"
"Lorg/gnu/emacs/EmacsDrawable;"
"Lorg/gnu/emacs/EmacsGC;IIIIII)V");
FIND_METHOD (clear_window, "clearWindow",
"(Lorg/gnu/emacs/EmacsWindow;)V");
FIND_METHOD (clear_area, "clearArea",
"(Lorg/gnu/emacs/EmacsWindow;IIII)V");
FIND_METHOD (ring_bell, "ringBell", "()V");
FIND_METHOD (query_tree, "queryTree",
"(Lorg/gnu/emacs/EmacsWindow;)[S");
FIND_METHOD (get_screen_width, "getScreenWidth", "(Z)I");
FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I");
FIND_METHOD (detect_mouse, "detectMouse", "()Z");
FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;");
FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;)"
"Ljava/lang/String;");
FIND_METHOD (restart_emacs, "restartEmacs", "()V");
FIND_METHOD (update_ic, "updateIC",
"(Lorg/gnu/emacs/EmacsWindow;IIII)V");
FIND_METHOD (reset_ic, "resetIC",
"(Lorg/gnu/emacs/EmacsWindow;I)V");
FIND_METHOD (open_content_uri, "openContentUri",
"([BZZZ)I");
FIND_METHOD (check_content_uri, "checkContentUri",
"([BZZ)Z");
FIND_METHOD (query_battery, "queryBattery", "()[J");
FIND_METHOD (display_toast, "displayToast",
"(Ljava/lang/String;)V");
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");
#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); \
assert (pixmap_class.c_name);
FIND_METHOD (constructor, "", "(S[IIII)V");
FIND_METHOD (constructor_mutable, "", "(SIII)V");
#undef FIND_METHOD
}
static void
android_init_graphics_point (void)
{
jclass old;
point_class.class
= (*android_java_env)->FindClass (android_java_env,
"android/graphics/Point");
eassert (point_class.class);
old = point_class.class;
point_class.class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) old);
ANDROID_DELETE_LOCAL_REF (old);
if (!point_class.class)
emacs_abort ();
#define FIND_METHOD(c_name, name, signature) \
point_class.c_name \
= (*android_java_env)->GetMethodID (android_java_env, \
point_class.class, \
name, signature); \
assert (point_class.c_name);
FIND_METHOD (constructor, "", "(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); \
assert (drawable_class.c_name);
FIND_METHOD (get_bitmap, "getBitmap", "()Landroid/graphics/Bitmap;");
FIND_METHOD (damage_rect, "damageRect", "(Landroid/graphics/Rect;)V");
#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); \
assert (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 (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");
#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); \
assert (cursor_class.c_name);
FIND_METHOD (constructor, "", "(SI)V");
#undef FIND_METHOD
}
JNIEXPORT void JNICALL
NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
jobject dump_file_object, jint api_level)
{
/* 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;
/* Set the Android API level. */
android_api_level = api_level;
android_java_env = env;
nelements = (*env)->GetArrayLength (env, argv);
c_argv = alloca (sizeof *c_argv * nelements);
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);
}
android_init_emacs_service ();
android_init_emacs_pixmap ();
android_init_graphics_point ();
android_init_emacs_drawable ();
android_init_emacs_window ();
android_init_emacs_cursor ();
/* 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);
/* 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);
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;
/* Raise sigio to interrupt anything that could be reading
input. */
Vquit_flag = Qt;
raise (SIGIO);
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint x, jint y, jint width,
jint height)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xconfigure.type = ANDROID_CONFIGURE_NOTIFY;
event.xconfigure.serial = ++event_serial;
event.xconfigure.window = window;
event.xconfigure.time = time;
event.xconfigure.x = x;
event.xconfigure.y = y;
event.xconfigure.width = width;
event.xconfigure.height = height;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
jint unicode_char)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xkey.type = ANDROID_KEY_PRESS;
event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = time;
event.xkey.state = state;
event.xkey.keycode = keycode;
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
jint unicode_char)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xkey.type = ANDROID_KEY_RELEASE;
event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = time;
event.xkey.state = state;
event.xkey.keycode = keycode;
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xfocus.type = ANDROID_FOCUS_IN;
event.xfocus.serial = ++event_serial;
event.xfocus.window = window;
event.xfocus.time = time;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xfocus.type = ANDROID_FOCUS_OUT;
event.xfocus.serial = ++event_serial;
event.xfocus.window = window;
event.xfocus.time = time;
android_write_event (&event);
return ++event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
jshort window, jint action)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xaction.type = ANDROID_WINDOW_ACTION;
event.xaction.serial = ++event_serial;
event.xaction.window = window;
event.xaction.action = action;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xcrossing.type = ANDROID_ENTER_NOTIFY;
event.xcrossing.serial = ++event_serial;
event.xcrossing.window = window;
event.xcrossing.x = x;
event.xcrossing.y = y;
event.xcrossing.time = time;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xcrossing.type = ANDROID_LEAVE_NOTIFY;
event.xcrossing.serial = ++event_serial;
event.xcrossing.window = window;
event.xcrossing.x = x;
event.xcrossing.y = y;
event.xcrossing.time = time;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xmotion.type = ANDROID_MOTION_NOTIFY;
event.xmotion.serial = ++event_serial;
event.xmotion.window = window;
event.xmotion.x = x;
event.xmotion.y = y;
event.xmotion.time = time;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
jint button)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xbutton.type = ANDROID_BUTTON_PRESS;
event.xbutton.serial = ++event_serial;
event.xbutton.window = window;
event.xbutton.x = x;
event.xbutton.y = y;
event.xbutton.time = time;
event.xbutton.state = state;
event.xbutton.button = button;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
jint button)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xbutton.type = ANDROID_BUTTON_RELEASE;
event.xbutton.serial = ++event_serial;
event.xbutton.window = window;
event.xbutton.x = x;
event.xbutton.y = y;
event.xbutton.time = time;
event.xbutton.state = state;
event.xbutton.button = button;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
{
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;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
{
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;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
{
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;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
jfloat x_delta, jfloat y_delta)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.wheel.type = ANDROID_WHEEL;
event.wheel.serial = ++event_serial;
event.wheel.window = window;
event.wheel.x = x;
event.wheel.y = y;
event.wheel.time = time;
event.wheel.state = state;
event.wheel.x_delta = x_delta;
event.wheel.y_delta = y_delta;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
jshort window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.iconified.type = ANDROID_ICONIFIED;
event.iconified.serial = ++event_serial;
event.iconified.window = window;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
jshort window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.iconified.type = ANDROID_DEICONIFIED;
event.iconified.serial = ++event_serial;
event.iconified.window = window;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
jshort window, jint menu_event_id,
jint menu_event_serial)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.menu.type = ANDROID_CONTEXT_MENU;
event.menu.serial = ++event_serial;
event.menu.window = window;
event.menu.menu_event_id = menu_event_id;
event.menu.menu_event_serial = menu_event_serial;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jlong JNICALL
NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jint width, jint height)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.xexpose.type = ANDROID_EXPOSE;
event.xexpose.serial = ++event_serial;
event.xexpose.window = window;
event.xexpose.x = x;
event.xexpose.y = y;
event.xexpose.width = width;
event.xexpose.height = height;
android_write_event (&event);
return event_serial;
}
JNIEXPORT jboolean JNICALL
NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env,
jobject object)
{
/* Yes, android_pass_multimedia_buttons_to_system is being
read from the UI thread. */
return !android_pass_multimedia_buttons_to_system;
}
/* Forward declarations of deadlock prevention functions. */
static void android_begin_query (void);
static void android_end_query (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 ();
}
#ifdef __clang__
#pragma clang diagnostic pop
#else
#pragma GCC diagnostic pop
#endif
/* Java functions called by C.
Because all C code runs in the native function initEmacs, ALL LOCAL
REFERENCES WILL PERSIST!
This means that every local reference must be explicitly destroyed
with DeleteLocalRef. A helper macro is provided to do this. */
struct android_handle_entry
{
/* The type. */
enum android_handle_type type;
/* The handle. */
jobject handle;
};
/* Table of handles MAX_HANDLE long. */
struct android_handle_entry android_handles[USHRT_MAX];
/* The largest handle ID currently known, but subject to
wraparound. */
static android_handle max_handle;
/* Allocate a new, unused, handle identifier. If Emacs is out of
identifiers, return 0. */
static android_handle
android_alloc_id (void)
{
android_handle handle;
/* 0 is never a valid handle ID. */
if (!max_handle)
max_handle++;
/* See if the handle is already occupied. */
if (android_handles[max_handle].handle)
{
/* Look for a fresh unoccupied handle. */
handle = max_handle;
max_handle++;
while (handle != max_handle)
{
++max_handle;
/* Make sure the handle is valid. */
if (!max_handle)
++max_handle;
if (!android_handles[max_handle].handle)
return max_handle++;
}
return ANDROID_NONE;
}
return max_handle++;
}
/* Destroy the specified handle and mark it as free on the Java side
as well. */
static void
android_destroy_handle (android_handle handle)
{
static jclass old, class;
static jmethodID method;
if (!android_handles[handle].handle)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Trying to destroy free handle!");
emacs_abort ();
}
if (!class)
{
class
= (*android_java_env)->FindClass (android_java_env,
"org/gnu/emacs/EmacsHandleObject");
assert (class != NULL);
method
= (*android_java_env)->GetMethodID (android_java_env, class,
"destroyHandle", "()V");
assert (method != NULL);
old = class;
class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) class);
android_exception_check_1 (old);
ANDROID_DELETE_LOCAL_REF (old);
}
(*android_java_env)->CallVoidMethod (android_java_env,
android_handles[handle].handle,
method);
/* Just clear any exception thrown. If destroying the handle
fails from an out-of-memory error, then Emacs loses some
resources, but that is not as big deal as signalling. */
(*android_java_env)->ExceptionClear (android_java_env);
/* Delete the global reference regardless of any error. */
(*android_java_env)->DeleteGlobalRef (android_java_env,
android_handles[handle].handle);
android_handles[handle].handle = NULL;
}
jobject
android_resolve_handle (android_handle handle,
enum android_handle_type type)
{
if (!handle)
/* ANDROID_NONE. */
return NULL;
/* CheckJNI will normally ensure that the handle exists and is
the right type, but with a less informative error message.
Don't waste cycles doing our own checking here. */
#ifdef ENABLE_CHECKING
if (!android_handles[handle].handle)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Trying to resolve free handle!");
emacs_abort ();
}
if (android_handles[handle].type != type)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Handle has wrong type!");
emacs_abort ();
}
#endif /* ENABLE_CHECKING */
return android_handles[handle].handle;
}
static jobject
android_resolve_handle2 (android_handle handle,
enum android_handle_type type,
enum android_handle_type type2)
{
if (!handle)
return NULL;
/* CheckJNI will normally ensure that the handle exists and is
the right type, but with a less informative error message.
Don't waste cycles doing our own checking here. */
#ifdef ENABLE_CHECKING
if (!android_handles[handle].handle)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Trying to resolve free handle!");
emacs_abort ();
}
if (android_handles[handle].type != type
&& android_handles[handle].type != type2)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Handle has wrong type!");
emacs_abort ();
}
#endif /* ENABLE_CHECKING */
return android_handles[handle].handle;
}
void
android_change_window_attributes (android_window handle,
enum android_window_value_mask value_mask,
struct android_set_window_attributes *attrs)
{
jmethodID method;
jobject window;
jint pixel;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
if (value_mask & ANDROID_CW_BACK_PIXEL)
{
method = window_class.change_window_background;
pixel = (jint) attrs->background_pixel;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
method, pixel);
android_exception_check ();
}
}
/* Create a new window with the given width, height and
attributes. */
android_window
android_create_window (android_window parent, int x, int y,
int width, int height,
enum android_window_value_mask value_mask,
struct android_set_window_attributes *attrs)
{
static jclass class;
static jmethodID constructor;
jobject object, parent_object, old;
android_window window;
android_handle prev_max_handle;
bool override_redirect;
parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
prev_max_handle = max_handle;
window = android_alloc_id ();
if (!window)
error ("Out of window handles!");
if (!class)
{
class = (*android_java_env)->FindClass (android_java_env,
"org/gnu/emacs/EmacsWindow");
assert (class != NULL);
constructor
= (*android_java_env)->GetMethodID (android_java_env, class, "",
"(SLorg/gnu/emacs/EmacsWindow;"
"IIIIZ)V");
assert (constructor != NULL);
old = class;
class = (*android_java_env)->NewGlobalRef (android_java_env, class);
android_exception_check_1 (old);
ANDROID_DELETE_LOCAL_REF (old);
}
/* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
creation time. */
override_redirect = ((value_mask
& ANDROID_CW_OVERRIDE_REDIRECT)
&& attrs->override_redirect);
object = (*android_java_env)->NewObject (android_java_env, class,
constructor, (jshort) window,
parent_object, (jint) x, (jint) y,
(jint) width, (jint) height,
(jboolean) override_redirect);
if (!object)
{
(*android_java_env)->ExceptionClear (android_java_env);
max_handle = prev_max_handle;
memory_full (0);
}
android_handles[window].type = ANDROID_HANDLE_WINDOW;
android_handles[window].handle
= (*android_java_env)->NewGlobalRef (android_java_env,
object);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (object);
if (!android_handles[window].handle)
memory_full (0);
android_change_window_attributes (window, value_mask, attrs);
return window;
}
void
android_set_window_background (android_window window, unsigned long pixel)
{
struct android_set_window_attributes attrs;
attrs.background_pixel = pixel;
android_change_window_attributes (window, ANDROID_CW_BACK_PIXEL,
&attrs);
}
void
android_destroy_window (android_window window)
{
if (android_handles[window].type != ANDROID_HANDLE_WINDOW)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Trying to destroy something not a window!");
emacs_abort ();
}
android_destroy_handle (window);
}
static void
android_init_android_rect_class (void)
{
jclass old;
if (android_rect_class)
/* Already initialized. */
return;
android_rect_class
= (*android_java_env)->FindClass (android_java_env,
"android/graphics/Rect");
assert (android_rect_class);
android_rect_constructor
= (*android_java_env)->GetMethodID (android_java_env, android_rect_class,
"", "(IIII)V");
assert (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");
assert (emacs_gc_class);
emacs_gc_constructor
= (*android_java_env)->GetMethodID (android_java_env,
emacs_gc_class,
"", "(S)V");
assert (emacs_gc_constructor);
emacs_gc_mark_dirty
= (*android_java_env)->GetMethodID (android_java_env,
emacs_gc_class,
"markDirty", "(Z)V");
assert (emacs_gc_mark_dirty);
old = emacs_gc_class;
emacs_gc_class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) emacs_gc_class);
android_exception_check_1 (old);
ANDROID_DELETE_LOCAL_REF (old);
emacs_gc_foreground
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"foreground", "I");
emacs_gc_background
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"background", "I");
emacs_gc_function
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"function", "I");
emacs_gc_clip_rects
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"clip_rects",
"[Landroid/graphics/Rect;");
emacs_gc_clip_x_origin
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"clip_x_origin", "I");
emacs_gc_clip_y_origin
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"clip_y_origin", "I");
emacs_gc_stipple
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"stipple",
"Lorg/gnu/emacs/EmacsPixmap;");
emacs_gc_clip_mask
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"clip_mask",
"Lorg/gnu/emacs/EmacsPixmap;");
emacs_gc_fill_style
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"fill_style", "I");
emacs_gc_ts_origin_x
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"ts_origin_x", "I");
emacs_gc_ts_origin_y
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"ts_origin_y", "I");
}
struct android_gc *
android_create_gc (enum android_gc_value_mask mask,
struct android_gc_values *values)
{
struct android_gc *gc;
android_handle prev_max_handle;
jobject object;
android_init_emacs_gc_class ();
gc = xmalloc (sizeof *gc);
prev_max_handle = max_handle;
gc->gcontext = android_alloc_id ();
gc->foreground = 0;
gc->background = 0xffffff;
gc->clip_rects = NULL;
/* This means to not apply any clipping. */
gc->num_clip_rects = -1;
if (!gc->gcontext)
{
xfree (gc);
error ("Out of GContext handles!");
}
object = (*android_java_env)->NewObject (android_java_env,
emacs_gc_class,
emacs_gc_constructor,
(jshort) gc->gcontext);
if (!object)
{
(*android_java_env)->ExceptionClear (android_java_env);
max_handle = prev_max_handle;
memory_full (0);
}
android_handles[gc->gcontext].type = ANDROID_HANDLE_GCONTEXT;
android_handles[gc->gcontext].handle
= (*android_java_env)->NewGlobalRef (android_java_env, object);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (object);
if (!android_handles[gc->gcontext].handle)
memory_full (0);
android_change_gc (gc, mask, values);
return gc;
}
void
android_free_gc (struct android_gc *gc)
{
android_destroy_handle (gc->gcontext);
xfree (gc->clip_rects);
xfree (gc);
}
void
android_change_gc (struct android_gc *gc,
enum android_gc_value_mask mask,
struct android_gc_values *values)
{
jobject what, gcontext;
jboolean clip_changed;
clip_changed = false;
android_init_emacs_gc_class ();
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
if (mask & ANDROID_GC_FOREGROUND)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_foreground,
values->foreground);
gc->foreground = values->foreground;
}
if (mask & ANDROID_GC_BACKGROUND)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_background,
values->background);
gc->background = values->background;
}
if (mask & ANDROID_GC_FUNCTION)
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_function,
values->function);
if (mask & ANDROID_GC_CLIP_X_ORIGIN)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_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);
clip_changed = true;
}
if (mask & ANDROID_GC_CLIP_MASK)
{
what = android_resolve_handle (values->clip_mask,
ANDROID_HANDLE_PIXMAP);
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_clip_mask,
what);
/* Changing GCClipMask also clears the clip rectangles. */
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_clip_rects,
NULL);
xfree (gc->clip_rects);
gc->clip_rects = NULL;
gc->num_clip_rects = -1;
clip_changed = true;
}
if (mask & ANDROID_GC_STIPPLE)
{
what = android_resolve_handle (values->stipple,
ANDROID_HANDLE_PIXMAP);
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_stipple,
what);
}
if (mask & ANDROID_GC_FILL_STYLE)
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_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);
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);
if (mask)
{
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
gcontext,
emacs_gc_class,
emacs_gc_mark_dirty,
(jboolean) clip_changed);
android_exception_check ();
}
}
void
android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin,
int clip_y_origin,
struct android_rectangle *clip_rects,
int n_clip_rects)
{
jobjectArray array;
jobject rect, gcontext;
int i;
android_init_android_rect_class ();
android_init_emacs_gc_class ();
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
array = (*android_java_env)->NewObjectArray (android_java_env,
n_clip_rects,
android_rect_class,
NULL);
android_exception_check ();
for (i = 0; i < n_clip_rects; ++i)
{
rect = (*android_java_env)->NewObject (android_java_env,
android_rect_class,
android_rect_constructor,
(jint) clip_rects[i].x,
(jint) clip_rects[i].y,
(jint) (clip_rects[i].x
+ clip_rects[i].width),
(jint) (clip_rects[i].y
+ clip_rects[i].height));
/* The meaning of this call is to check whether or not an
allocation error happened, and to delete ARRAY and signal an
out-of-memory error if that is the case. */
android_exception_check_1 (array);
(*android_java_env)->SetObjectArrayElement (android_java_env,
array, i, rect);
ANDROID_DELETE_LOCAL_REF (rect);
}
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_clip_rects,
(jobject) array);
ANDROID_DELETE_LOCAL_REF (array);
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_clip_x_origin,
clip_x_origin);
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_clip_y_origin,
clip_y_origin);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
gcontext,
emacs_gc_class,
emacs_gc_mark_dirty,
(jboolean) true);
android_exception_check ();
/* Cache the clip rectangles on the C side for
sfntfont-android.c. */
if (gc->clip_rects)
xfree (gc->clip_rects);
/* If gc->num_clip_rects is 0, then no drawing will be performed at
all. */
gc->clip_rects = xmalloc (sizeof *gc->clip_rects
* n_clip_rects);
gc->num_clip_rects = n_clip_rects;
memcpy (gc->clip_rects, clip_rects,
n_clip_rects * sizeof *gc->clip_rects);
}
void
android_reparent_window (android_window w, android_window parent_handle,
int x, int y)
{
jobject window, parent;
jmethodID method;
window = android_resolve_handle (w, ANDROID_HANDLE_WINDOW);
parent = android_resolve_handle (parent_handle,
ANDROID_HANDLE_WINDOW);
method = window_class.reparent_to;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
window_class.class, method,
parent, (jint) x, (jint) y);
android_exception_check ();
}
void
android_clear_window (android_window handle)
{
jobject window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.clear_window,
window);
android_exception_check ();
}
void
android_map_window (android_window handle)
{
jobject window;
jmethodID map_window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
map_window = window_class.map_window;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
map_window);
android_exception_check ();
}
void
android_unmap_window (android_window handle)
{
jobject window;
jmethodID unmap_window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
unmap_window = window_class.unmap_window;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
unmap_window);
android_exception_check ();
}
void
android_resize_window (android_window handle, unsigned int width,
unsigned int height)
{
jobject window;
jmethodID resize_window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
resize_window = window_class.resize_window;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
resize_window,
(jint) width,
(jint) height);
android_exception_check ();
}
void
android_move_window (android_window handle, int x, int y)
{
jobject window;
jmethodID move_window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
move_window = window_class.move_window;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
move_window,
(jint) x, (jint) y);
android_exception_check ();
}
void
android_swap_buffers (struct android_swap_info *swap_info,
int num_windows)
{
jobject window;
int i;
for (i = 0; i < num_windows; ++i)
{
window = android_resolve_handle (swap_info[i].swap_window,
ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
window_class.swap_buffers);
android_exception_check ();
}
}
void
android_get_gc_values (struct android_gc *gc,
enum android_gc_value_mask mask,
struct android_gc_values *values)
{
jobject gcontext;
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
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
= (*android_java_env)->GetIntField (android_java_env,
gcontext,
emacs_gc_function);
if (mask & ANDROID_GC_CLIP_X_ORIGIN)
values->clip_x_origin
= (*android_java_env)->GetIntField (android_java_env,
gcontext,
emacs_gc_clip_x_origin);
if (mask & ANDROID_GC_CLIP_Y_ORIGIN)
values->clip_y_origin
= (*android_java_env)->GetIntField (android_java_env,
gcontext,
emacs_gc_clip_y_origin);
if (mask & ANDROID_GC_FILL_STYLE)
values->fill_style
= (*android_java_env)->GetIntField (android_java_env,
gcontext,
emacs_gc_fill_style);
if (mask & ANDROID_GC_TILE_STIP_X_ORIGIN)
values->ts_x_origin
= (*android_java_env)->GetIntField (android_java_env,
gcontext,
emacs_gc_ts_origin_x);
if (mask & ANDROID_GC_TILE_STIP_Y_ORIGIN)
values->ts_y_origin
= (*android_java_env)->GetIntField (android_java_env,
gcontext,
emacs_gc_ts_origin_y);
/* Fields involving handles are not used by Emacs, and thus not
implemented */
}
void
android_set_foreground (struct android_gc *gc, unsigned long foreground)
{
struct android_gc_values gcv;
gcv.foreground = foreground;
android_change_gc (gc, ANDROID_GC_FOREGROUND, &gcv);
}
void
android_fill_rectangle (android_drawable handle, struct android_gc *gc,
int x, int y, unsigned int width,
unsigned int height)
{
jobject drawable, gcontext;
drawable = android_resolve_handle2 (handle,
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.fill_rectangle,
drawable,
gcontext,
(jint) x, (jint) y,
(jint) width,
(jint) height);
}
android_pixmap
android_create_pixmap_from_bitmap_data (char *data, unsigned int width,
unsigned int height,
unsigned long foreground,
unsigned long background,
unsigned int depth)
{
android_handle prev_max_handle;
jobject object;
jintArray colors;
android_pixmap pixmap;
unsigned int x, y;
jint *region;
USE_SAFE_ALLOCA;
/* Create the color array holding the data. */
colors = (*android_java_env)->NewIntArray (android_java_env,
width * height);
android_exception_check ();
SAFE_NALLOCA (region, sizeof *region, width);
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
if (depth == 24)
{
/* The alpha channels must be set, or otherwise, the
pixmap will be created entirely transparent. */
if (data[x / 8] & (1 << (x % 8)))
region[x] = foreground | 0xff000000;
else
region[x] = background | 0xff000000;
}
else
{
if (data[x / 8] & (1 << (x % 8)))
region[x] = foreground;
else
region[x] = background;
}
}
(*android_java_env)->SetIntArrayRegion (android_java_env,
colors,
width * y, width,
region);
data += width / 8;
}
/* First, allocate the pixmap handle. */
prev_max_handle = max_handle;
pixmap = android_alloc_id ();
if (!pixmap)
{
ANDROID_DELETE_LOCAL_REF ((jobject) colors);
error ("Out of pixmap handles!");
}
object = (*android_java_env)->NewObject (android_java_env,
pixmap_class.class,
pixmap_class.constructor,
(jshort) pixmap, colors,
(jint) width, (jint) height,
(jint) depth);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF ((jobject) colors);
if (!object)
{
max_handle = prev_max_handle;
memory_full (0);
}
android_handles[pixmap].type = ANDROID_HANDLE_PIXMAP;
android_handles[pixmap].handle
= (*android_java_env)->NewGlobalRef (android_java_env, object);
ANDROID_DELETE_LOCAL_REF (object);
if (!android_handles[pixmap].handle)
memory_full (0);
SAFE_FREE ();
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);
}
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, gcontext;
src_object = android_resolve_handle2 (src, ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
dest_object = android_resolve_handle2 (dest, ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.copy_area,
src_object,
dest_object,
gcontext,
(jint) src_x, (jint) src_y,
(jint) width, (jint) height,
(jint) dest_x, (jint) dest_y);
android_exception_check ();
}
void
android_free_pixmap (android_pixmap pixmap)
{
android_destroy_handle (pixmap);
}
void
android_set_background (struct android_gc *gc, unsigned long background)
{
struct android_gc_values gcv;
gcv.background = background;
android_change_gc (gc, ANDROID_GC_BACKGROUND, &gcv);
}
void
android_fill_polygon (android_drawable drawable, struct android_gc *gc,
struct android_point *points, int npoints,
enum android_shape shape, enum android_coord_mode mode)
{
jobjectArray array;
jobject point, drawable_object, gcontext;
int i;
drawable_object = android_resolve_handle2 (drawable,
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
array = (*android_java_env)->NewObjectArray (android_java_env,
npoints,
point_class.class,
NULL);
android_exception_check ();
for (i = 0; i < npoints; ++i)
{
point = (*android_java_env)->NewObject (android_java_env,
point_class.class,
point_class.constructor,
(jint) points[i].x,
(jint) points[i].y);
android_exception_check_1 (array);
(*android_java_env)->SetObjectArrayElement (android_java_env,
array, i, point);
ANDROID_DELETE_LOCAL_REF (point);
}
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.fill_polygon,
drawable_object,
gcontext, array);
ANDROID_DELETE_LOCAL_REF (array);
}
void
android_draw_rectangle (android_drawable handle, struct android_gc *gc,
int x, int y, unsigned int width, unsigned int height)
{
jobject drawable, gcontext;
drawable = android_resolve_handle2 (handle,
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.draw_rectangle,
drawable, gcontext,
(jint) x, (jint) y,
(jint) width, (jint) height);
}
void
android_draw_point (android_drawable handle, struct android_gc *gc,
int x, int y)
{
jobject drawable, gcontext;
drawable = android_resolve_handle2 (handle,
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.draw_point,
drawable, gcontext,
(jint) x, (jint) y);
}
void
android_draw_line (android_drawable handle, struct android_gc *gc,
int x, int y, int x2, int y2)
{
jobject drawable, gcontext;
drawable = android_resolve_handle2 (handle,
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.draw_line,
drawable, gcontext,
(jint) x, (jint) y,
(jint) x2, (jint) y2);
}
android_pixmap
android_create_pixmap (unsigned int width, unsigned int height,
int depth)
{
android_handle prev_max_handle;
jobject object;
android_pixmap pixmap;
/* First, allocate the pixmap handle. */
prev_max_handle = max_handle;
pixmap = android_alloc_id ();
if (!pixmap)
error ("Out of pixmap handles!");
object = (*android_java_env)->NewObject (android_java_env,
pixmap_class.class,
pixmap_class.constructor_mutable,
(jshort) pixmap,
(jint) width, (jint) height,
(jint) depth);
if (!object)
{
(*android_java_env)->ExceptionClear (android_java_env);
max_handle = prev_max_handle;
memory_full (0);
}
android_handles[pixmap].type = ANDROID_HANDLE_PIXMAP;
android_handles[pixmap].handle
= (*android_java_env)->NewGlobalRef (android_java_env, object);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (object);
if (!android_handles[pixmap].handle)
memory_full (0);
return pixmap;
}
void
android_set_ts_origin (struct android_gc *gc, int x, int y)
{
struct android_gc_values gcv;
gcv.ts_x_origin = x;
gcv.ts_y_origin = y;
android_change_gc (gc, (ANDROID_GC_TILE_STIP_X_ORIGIN
| ANDROID_GC_TILE_STIP_Y_ORIGIN),
&gcv);
}
void
android_clear_area (android_window handle, int x, int y,
unsigned int width, unsigned int height)
{
jobject window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.clear_area,
window, (jint) x, (jint) y,
(jint) width, (jint) height);
}
android_pixmap
android_create_bitmap_from_data (char *bits, unsigned int width,
unsigned int height)
{
return android_create_pixmap_from_bitmap_data (bits, 1, 0,
width, height, 1);
}
struct android_image *
android_create_image (unsigned int depth, enum android_image_format format,
char *data, unsigned int width, unsigned int height)
{
struct android_image *image;
image = xmalloc (sizeof *image);
/* Fill in the fields required by image.c. N.B. that
android_destroy_image ostensibly will free data, but image.c
mostly sets and frees data itself. */
image->width = width;
image->height = height;
image->data = data;
image->depth = depth;
image->format = format;
/* Now fill in the image dimensions. There are only two depths
supported by this function. */
if (depth == 1)
{
image->bytes_per_line = (width + 7) / 8;
image->bits_per_pixel = 1;
}
else if (depth == 24)
{
image->bytes_per_line = width * 4;
image->bits_per_pixel = 32;
}
else
emacs_abort ();
return image;
}
void
android_destroy_image (struct android_image *ximg)
{
/* If XIMG->data is NULL, then it has already been freed by
image.c. */
if (ximg->data)
xfree (ximg->data);
xfree (ximg);
}
void
android_put_pixel (struct android_image *ximg, int x, int y,
unsigned long pixel)
{
char *byte, *word;
unsigned int r, g, b;
unsigned int pixel_int;
/* Ignore out-of-bounds accesses. */
if (x >= ximg->width || y >= ximg->height || x < 0 || y < 0)
return;
switch (ximg->depth)
{
case 1:
byte = ximg->data + y * ximg->bytes_per_line + x / 8;
if (pixel)
*byte |= (1 << x % 8);
else
*byte &= ~(1 << x % 8);
break;
case 24:
/* Unaligned accesses are problematic on Android devices. */
word = ximg->data + y * ximg->bytes_per_line + x * 4;
/* Swizzle the pixel into ABGR format. Android uses Skia's
``native color type'', which is ABGR. This is despite the
format being named ``ARGB'', and more confusingly
`ANDROID_BITMAP_FORMAT_RGBA_8888' in bitmap.h. */
r = pixel & 0x00ff0000;
g = pixel & 0x0000ff00;
b = pixel & 0x000000ff;
pixel = (r >> 16) | g | (b << 16) | 0xff000000;
pixel_int = pixel;
memcpy (word, &pixel_int, sizeof pixel_int);
break;
}
}
unsigned long
android_get_pixel (struct android_image *ximg, int x, int y)
{
char *byte, *word;
unsigned int pixel, r, g, b;
if (x >= ximg->width || y >= ximg->height
|| x < 0 || y < 0)
return 0;
switch (ximg->depth)
{
case 1:
byte = ximg->data + y * ximg->bytes_per_line + x / 8;
return (*byte & (1 << x % 8)) ? 1 : 0;
case 24:
word = ximg->data + y * ximg->bytes_per_line + x * 4;
memcpy (&pixel, word, sizeof pixel);
/* Convert the pixel back to RGB. */
b = pixel & 0x00ff0000;
g = pixel & 0x0000ff00;
r = pixel & 0x000000ff;
pixel = ((r << 16) | g | (b >> 16)) & ~0xff000000;
return pixel;
}
emacs_abort ();
}
struct android_image *
android_get_image (android_drawable handle,
enum android_image_format format)
{
jobject drawable, bitmap;
AndroidBitmapInfo bitmap_info;
size_t byte_size;
void *data;
struct android_image *image;
unsigned char *data1, *data2;
int i, x;
drawable = android_resolve_handle2 (handle, ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
/* Look up the drawable and get the bitmap corresponding to it.
Then, lock the bitmap's bits. */
bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
drawable,
drawable_class.get_bitmap);
android_exception_check ();
/* Clear the bitmap info structure. */
memset (&bitmap_info, 0, sizeof bitmap_info);
/* The NDK doc seems to imply this function can fail but doesn't say
what value it gives when it does! */
AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info);
if (!bitmap_info.stride)
{
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
/* Compute how big the image data will be. Fail if it would be too
big. */
if (bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8)
{
if (INT_MULTIPLY_WRAPV ((size_t) bitmap_info.stride,
(size_t) bitmap_info.height,
&byte_size))
{
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
}
else
/* This A8 image will be packed into A1 later on. */
byte_size = (bitmap_info.width + 7) / 8;
/* Lock the image data. Once again, the NDK documentation says the
call can fail, but does not say how to determine whether or not
it has failed, nor how the address is aligned. */
data = NULL;
AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
if (!data)
{
/* Take a NULL pointer to mean that AndroidBitmap_lockPixels
failed. */
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
/* Copy the data into a new struct android_image. */
image = xmalloc (sizeof *image);
image->width = bitmap_info.width;
image->height = bitmap_info.height;
image->data = malloc (byte_size);
if (!image->data)
{
ANDROID_DELETE_LOCAL_REF (bitmap);
xfree (image);
memory_full (byte_size);
}
/* Use the format of the bitmap to determine the image depth. */
switch (bitmap_info.format)
{
case ANDROID_BITMAP_FORMAT_RGBA_8888:
image->depth = 24;
image->bits_per_pixel = 32;
break;
/* A8 images are used by Emacs to represent bitmaps. They have
to be packed manually. */
case ANDROID_BITMAP_FORMAT_A_8:
image->depth = 1;
image->bits_per_pixel = 1;
break;
/* Other formats are currently not supported. */
default:
emacs_abort ();
}
image->format = format;
if (image->depth == 24)
{
image->bytes_per_line = bitmap_info.stride;
/* Copy the bitmap data over. */
memcpy (image->data, data, byte_size);
}
else
{
/* Pack the A8 image data into bits manually. */
image->bytes_per_line = (image->width + 7) / 8;
data1 = (unsigned char *) image->data;
data2 = data;
for (i = 0; i < image->height; ++i)
{
for (x = 0; x < image->width; ++x)
/* Some bits in data1 might be initialized at this point,
but they will all be set properly later. */
data1[x / 8] = (data2[x]
? (data1[x / 8] | (1 << (x % 8)))
: (data1[x / 8] & ~(1 << (x % 8))));
data1 += image->bytes_per_line;
data2 += bitmap_info.stride;
}
}
/* Unlock the bitmap pixels. */
AndroidBitmap_unlockPixels (android_java_env, bitmap);
/* Delete the bitmap reference. */
ANDROID_DELETE_LOCAL_REF (bitmap);
return image;
}
void
android_put_image (android_pixmap handle, struct android_image *image)
{
jobject drawable, bitmap;
AndroidBitmapInfo bitmap_info;
void *data;
unsigned char *data_1, *data_2;
int i, x;
drawable = android_resolve_handle (handle, ANDROID_HANDLE_PIXMAP);
/* Look up the drawable and get the bitmap corresponding to it.
Then, lock the bitmap's bits. */
bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
drawable,
drawable_class.get_bitmap);
android_exception_check ();
/* Clear the bitmap info structure. */
memset (&bitmap_info, 0, sizeof bitmap_info);
/* The NDK doc seems to imply this function can fail but doesn't say
what value it gives when it does! */
AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info);
if (!bitmap_info.stride)
{
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
if (bitmap_info.width != image->width
|| bitmap_info.height != image->height)
/* This is not yet supported. */
emacs_abort ();
/* Make sure the bitmap formats are compatible with each other. */
if ((image->depth == 24
&& bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
|| (image->depth == 1
&& bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8))
emacs_abort ();
/* Lock the image data. Once again, the NDK documentation says the
call can fail, but does not say how to determine whether or not
it has failed, nor how the address is aligned. */
data = NULL;
AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
if (!data)
{
/* Take a NULL pointer to mean that AndroidBitmap_lockPixels
failed. */
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
data_1 = data;
data_2 = (unsigned char *) image->data;
/* Copy the bitmap data over scanline-by-scanline. */
for (i = 0; i < image->height; ++i)
{
if (image->depth != 1)
memcpy (data_1, data_2,
image->width * (image->bits_per_pixel / 8));
else
{
/* Android internally uses a 1 byte-per-pixel format for
ALPHA_8 images. Expand the image from the 1
bit-per-pixel X format correctly. */
for (x = 0; x < image->width; ++x)
data_1[x] = (data_2[x / 8] & (1 << x % 8)) ? 0xff : 0;
}
data_1 += bitmap_info.stride;
data_2 += image->bytes_per_line;
}
/* Unlock the bitmap pixels. */
AndroidBitmap_unlockPixels (android_java_env, bitmap);
/* Delete the bitmap reference. */
ANDROID_DELETE_LOCAL_REF (bitmap);
}
void
android_bell (void)
{
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.ring_bell);
android_exception_check ();
}
void
android_set_input_focus (android_window handle, unsigned long time)
{
jobject window;
jmethodID make_input_focus;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
make_input_focus = window_class.make_input_focus;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
make_input_focus,
(jlong) time);
android_exception_check ();
}
void
android_raise_window (android_window handle)
{
jobject window;
jmethodID raise;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
raise = window_class.raise;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
raise);
android_exception_check ();
}
void
android_lower_window (android_window handle)
{
jobject window;
jmethodID lower;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
lower = window_class.lower;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window,
window_class.class,
lower);
android_exception_check ();
}
int
android_query_tree (android_window handle, android_window *root_return,
android_window *parent_return,
android_window **children_return,
unsigned int *nchildren_return)
{
jobject window, array;
jsize nelements, i;
android_window *children;
jshort *shorts;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
/* window can be NULL, so this is a service method. */
array
= (*android_java_env)->CallObjectMethod (android_java_env,
emacs_service,
service_class.query_tree,
window);
android_exception_check ();
/* The first element of the array is the parent window. The rest
are the children. */
nelements = (*android_java_env)->GetArrayLength (android_java_env,
array);
eassert (nelements);
/* Now fill in the children. */
children = xnmalloc (nelements - 1, sizeof *children);
shorts
= (*android_java_env)->GetShortArrayElements (android_java_env, array,
NULL);
android_exception_check_nonnull (shorts, array);
for (i = 1; i < nelements; ++i)
children[i] = shorts[i];
/* Finally, return the parent and other values. */
*root_return = 0;
*parent_return = shorts[0];
*children_return = children;
*nchildren_return = nelements - 1;
/* Release the array contents. */
(*android_java_env)->ReleaseShortArrayElements (android_java_env, array,
shorts, JNI_ABORT);
ANDROID_DELETE_LOCAL_REF (array);
return 1;
}
void
android_get_geometry (android_window handle,
android_window *root_return,
int *x_return, int *y_return,
unsigned int *width_return,
unsigned int *height_return,
unsigned int *border_width_return)
{
jobject window;
jarray window_geometry;
jmethodID get_geometry;
jint *ints;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
get_geometry = window_class.get_window_geometry;
window_geometry
= (*android_java_env)->CallObjectMethod (android_java_env,
window,
get_geometry);
android_exception_check ();
/* window_geometry is an array containing x, y, width and
height. border_width is always 0 on Android. */
eassert ((*android_java_env)->GetArrayLength (android_java_env,
window_geometry)
== 4);
*root_return = 0;
*border_width_return = 0;
ints
= (*android_java_env)->GetIntArrayElements (android_java_env,
window_geometry,
NULL);
android_exception_check_nonnull (ints, window_geometry);
*x_return = ints[0];
*y_return = ints[1];
*width_return = ints[2];
*height_return = ints[3];
(*android_java_env)->ReleaseIntArrayElements (android_java_env,
window_geometry,
ints, JNI_ABORT);
/* Now free the local reference. */
ANDROID_DELETE_LOCAL_REF (window_geometry);
}
void
android_move_resize_window (android_window window, int x, int y,
unsigned int width, unsigned int height)
{
android_move_window (window, x, y);
android_resize_window (window, width, height);
}
void
android_map_raised (android_window window)
{
android_raise_window (window);
android_map_window (window);
}
void
android_translate_coordinates (android_window src, int x,
int y, int *root_x, int *root_y)
{
jobject window;
jarray coordinates;
jmethodID method;
jint *ints;
window = android_resolve_handle (src, ANDROID_HANDLE_WINDOW);
method = window_class.translate_coordinates;
coordinates
= (*android_java_env)->CallObjectMethod (android_java_env,
window, 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);
}
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)
{
enum android_lookup_status status;
int rc;
jobject window, string;
const jchar *characters;
jsize size;
size_t i;
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)
{
if (wchars_buffer < 1)
{
*status_return = ANDROID_BUFFER_OVERFLOW;
return 0;
}
else
{
buffer_return[0] = event->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;
}
*status_return = status;
return rc;
}
/* Now look up the window. */
rc = 0;
if (!android_handles[event->window].handle
|| (android_handles[event->window].type
!= ANDROID_HANDLE_WINDOW))
status = ANDROID_LOOKUP_NONE;
else
{
window = android_handles[event->window].handle;
string
= (*android_java_env)->CallObjectMethod (android_java_env, window,
window_class.lookup_string,
(jint) event->serial);
android_exception_check ();
if (!string)
status = ANDROID_LOOKUP_NONE;
else
{
/* Now return this input method string. */
characters = (*android_java_env)->GetStringChars (android_java_env,
string, NULL);
android_exception_check_1 (string);
/* Figure out how big the string is. */
size = (*android_java_env)->GetStringLength (android_java_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;
(*android_java_env)->ReleaseStringChars (android_java_env, string,
characters);
ANDROID_DELETE_LOCAL_REF (string);
}
}
*status_return = status;
return rc;
}
/* Low level drawing primitives. */
/* Lock the bitmap corresponding to the window WINDOW. 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_window window,
AndroidBitmapInfo *bitmap_info,
jobject *bitmap_return)
{
jobject drawable, bitmap;
void *data;
drawable = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
/* 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);
if (!bitmap)
/* NULL is returned when the bitmap does not currently exist due
to ongoing 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, rect;
drawable = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
/* Now turn DAMAGE into a Java rectangle. */
rect = (*android_java_env)->NewObject (android_java_env,
android_rect_class,
android_rect_constructor,
(jint) damage->x,
(jint) damage->y,
(jint) (damage->x
+ damage->width),
(jint) (damage->y
+ damage->height));
android_exception_check ();
/* Post the damage to the drawable. */
(*android_java_env)->CallVoidMethod (android_java_env,
drawable,
drawable_class.damage_rect,
rect);
android_exception_check_1 (rect);
ANDROID_DELETE_LOCAL_REF (rect);
}
/* 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;
}
void
android_set_dont_focus_on_map (android_window handle,
bool no_focus_on_map)
{
jmethodID method;
jobject window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
method = window_class.set_dont_focus_on_map;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
window_class.class,
method,
(jboolean) no_focus_on_map);
android_exception_check ();
}
void
android_set_dont_accept_focus (android_window handle,
bool no_accept_focus)
{
jmethodID method;
jobject window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
method = window_class.set_dont_accept_focus;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
window_class.class,
method,
(jboolean) no_accept_focus);
android_exception_check ();
}
void
android_get_keysym_name (int keysym, char *name_return, size_t size)
{
jobject string;
const char *buffer;
string = (*android_java_env)->CallObjectMethod (android_java_env,
emacs_service,
service_class.name_keysym,
(jint) keysym);
android_exception_check ();
buffer = (*android_java_env)->GetStringUTFChars (android_java_env,
(jstring) string,
NULL);
android_exception_check_nonnull ((void *) buffer, string);
strncpy (name_return, buffer, size - 1);
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
(jstring) string,
buffer);
ANDROID_DELETE_LOCAL_REF (string);
}
/* Display the on screen keyboard on window WINDOW, or hide it if SHOW
is false. Ask the system to bring up or hide the on-screen
keyboard on behalf of WINDOW. The request may be rejected by the
system, especially when the window does not have the input
focus. */
void
android_toggle_on_screen_keyboard (android_window window, bool show)
{
jobject object;
jmethodID method;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
method = window_class.toggle_on_screen_keyboard;
/* Now display the on screen keyboard. */
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env, object,
window_class.class,
method, (jboolean) show);
/* Check for out of memory errors. */
android_exception_check ();
}
/* When calling the system's faccessat, make sure to clear the flag
AT_EACCESS.
Android's faccessat simply fails upon using AT_EACCESS, so replace
it with zero here. This isn't caught during configuration as Emacs
is being cross compiled.
This replacement is only done when building for Android 16 or
later, because earlier versions use the gnulib replacement that
lacks these issues.
This is unnecessary on earlier API versions, as gnulib's
rpl_faccessat will be used instead, which lacks these problems. */
/* Like faccessat, except it also understands DIRFD opened using
android_dirfd. */
int
android_faccessat (int dirfd, const char *pathname, int mode, int flags)
{
const char *asset;
if (dirfd != AT_FDCWD)
dirfd
= android_lookup_asset_directory_fd (dirfd, &pathname,
pathname);
/* Check if pathname is actually an asset. If that is the case,
simply fall back to android_file_access_p. */
if (dirfd == AT_FDCWD
&& asset_manager
&& (asset = android_get_asset_name (pathname)))
{
if (android_file_access_p (asset, mode))
return 0;
/* Set errno to an appropriate value. */
errno = ENOENT;
return 1;
}
/* Check if pathname is actually a content resolver URI. */
if (dirfd == AT_FDCWD
&& android_init_gui
&& android_content_name_p (pathname))
{
if (android_check_content_access (pathname, mode))
return 0;
/* Set errno to an appropriate value. */
errno = ENOENT;
return 1;
}
#if __ANDROID_API__ >= 16
return faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
#else
return faccessat (dirfd, pathname, mode, flags);
#endif
}
/* Directory listing emulation. */
struct android_dir
{
/* The real DIR *, if it exists. */
DIR *dir;
/* Otherwise, the pointer to the directory in directory_tree. */
char *asset_dir;
/* And the end of the files in asset_dir. */
char *asset_limit;
/* The next struct android_dir. */
struct android_dir *next;
/* Path to the directory relative to /. */
char *asset_file;
/* File descriptor used when asset_dir is set. */
int fd;
};
/* List of all struct android_dir's corresponding to an asset
directory that are currently open. */
static struct android_dir *android_dirs;
/* Like opendir. However, return an asset directory if NAME points to
an asset. */
struct android_dir *
android_opendir (const char *name)
{
struct android_dir *dir;
char *asset_dir;
const char *asset_name;
size_t limit, length;
asset_name = android_get_asset_name (name);
/* If the asset manager exists and NAME is an asset, return an asset
directory. */
if (asset_manager && asset_name)
{
asset_dir
= (char *) android_scan_directory_tree ((char *) asset_name,
&limit);
if (!asset_dir)
{
errno = ENOENT;
return NULL;
}
length = strlen (name);
dir = xmalloc (sizeof *dir);
dir->dir = NULL;
dir->asset_dir = asset_dir;
dir->asset_limit = (char *) directory_tree + limit;
dir->fd = -1;
dir->asset_file = xzalloc (length + 2);
/* Make sure dir->asset_file is terminated with /. */
strcpy (dir->asset_file, name);
if (dir->asset_file[length - 1] != '/')
dir->asset_file[length] = '/';
/* Make sure dir->asset_limit is within bounds. It is a limit,
and as such can be exactly one byte past directory_tree. */
if (dir->asset_limit > directory_tree + directory_tree_size)
{
xfree (dir);
__android_log_print (ANDROID_LOG_VERBOSE, __func__,
"Invalid dir tree, limit %zu, size %zu\n",
limit, directory_tree_size);
dir = NULL;
errno = EACCES;
}
dir->next = android_dirs;
android_dirs = dir;
return dir;
}
/* Otherwise, open the directory normally. */
dir = xmalloc (sizeof *dir);
dir->asset_dir = NULL;
dir->dir = opendir (name);
if (!dir->dir)
{
xfree (dir);
return NULL;
}
return dir;
}
/* Like dirfd. However, value is not a real directory file descriptor
if DIR is an asset directory. */
int
android_dirfd (struct android_dir *dirp)
{
int fd;
if (dirp->dir)
return dirfd (dirp->dir);
else if (dirp->fd != -1)
return dirp->fd;
fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
/* Record this file descriptor in dirp. */
dirp->fd = fd;
return fd;
}
/* Like readdir, except it understands asset directories. */
struct dirent *
android_readdir (struct android_dir *dir)
{
static struct dirent dirent;
const char *last;
if (dir->asset_dir)
{
/* There are no more files to read. */
if (dir->asset_dir >= dir->asset_limit)
return NULL;
/* Otherwise, scan forward looking for the next NULL byte. */
last = memchr (dir->asset_dir, 0,
dir->asset_limit - dir->asset_dir);
/* No more NULL bytes remain. */
if (!last)
return NULL;
/* Forward last past the NULL byte. */
last++;
/* Make sure it is still within the directory tree. */
if (last >= directory_tree + directory_tree_size)
return NULL;
/* Now, fill in the dirent with the name. */
memset (&dirent, 0, sizeof dirent);
dirent.d_ino = 0;
dirent.d_off = 0;
dirent.d_reclen = sizeof dirent;
/* If this is not a directory, return DT_UNKNOWN. Otherwise,
return DT_DIR. */
if (android_is_directory (dir->asset_dir))
dirent.d_type = DT_DIR;
else
dirent.d_type = DT_UNKNOWN;
/* Note that dir->asset_dir is actually a NULL terminated
string. */
memcpy (dirent.d_name, dir->asset_dir,
MIN (sizeof dirent.d_name,
last - dir->asset_dir));
dirent.d_name[sizeof dirent.d_name - 1] = '\0';
/* Strip off the trailing slash, if any. */
if (dirent.d_name[MIN (sizeof dirent.d_name,
last - dir->asset_dir)
- 2] == '/')
dirent.d_name[MIN (sizeof dirent.d_name,
last - dir->asset_dir)
- 2] = '\0';
/* Finally, forward dir->asset_dir to the file past last. */
dir->asset_dir = ((char *) directory_tree
+ android_extract_long ((char *) last));
return &dirent;
}
return readdir (dir->dir);
}
/* Like closedir, but it also closes asset manager directories. */
void
android_closedir (struct android_dir *dir)
{
struct android_dir **next, *tem;
if (dir->dir)
closedir (dir->dir);
else
{
if (dir->fd != -1)
close (dir->fd);
/* Unlink this directory from the list of all asset manager
directories. */
for (next = &android_dirs; (tem = *next);)
{
if (tem == dir)
*next = dir->next;
else
next = &(*next)->next;
}
/* Free the asset file name. */
xfree (dir->asset_file);
}
/* There is no need to close anything else, as the directory tree
lies in statically allocated memory. */
xfree (dir);
}
/* Subroutine used by android_fstatat and android_faccessat. If DIRFD
belongs to an open asset directory and FILE is a relative file
name, then return AT_FDCWD and the absolute file name of the
directory prepended to FILE in *PATHNAME. Else, return DIRFD. */
int
android_lookup_asset_directory_fd (int dirfd,
const char *restrict *pathname,
const char *restrict file)
{
struct android_dir *dir;
static char *name;
if (file[0] == '/')
return dirfd;
for (dir = android_dirs; dir; dir = dir->next)
{
if (dir->fd == dirfd && dirfd != -1)
{
if (name)
xfree (name);
/* dir->asset_file is always separator terminated. */
name = xzalloc (strlen (dir->asset_file)
+ strlen (file) + 1);
strcpy (name, dir->asset_file);
strcpy (name + strlen (dir->asset_file),
file);
*pathname = name;
return AT_FDCWD;
}
}
return dirfd;
}
/* 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 ensuing"
" stack trace");
/* Cause 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;
}
/* Given a Lisp string TEXT, return a local reference to an equivalent
Java string. */
jstring
android_build_string (Lisp_Object text)
{
Lisp_Object encoded;
jstring string;
size_t nchars;
jchar *characters;
USE_SAFE_ALLOCA;
/* Directly encode TEXT if it contains no multibyte
characters. This is okay because the Java extended UTF
format is compatible with ASCII. */
if (SBYTES (text) == SCHARS (text)
&& android_check_string (text))
{
string = (*android_java_env)->NewStringUTF (android_java_env,
SSDATA (text));
android_exception_check ();
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);
android_exception_check ();
SAFE_FREE ();
return string;
}
/* 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 are not used after leaving
the current scope. For example, to allocate foo and then 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. */
/* Check for JNI exceptions and call memory_full in that
situation. */
void
android_exception_check (void)
{
if ((*android_java_env)->ExceptionCheck (android_java_env))
{
__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. */
void
android_exception_check_1 (jobject object)
{
if ((*android_java_env)->ExceptionCheck (android_java_env))
{
__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);
ANDROID_DELETE_LOCAL_REF (object);
memory_full (0);
}
}
/* Like android_exception_check_one, except it takes more than one
local reference argument. */
void
android_exception_check_2 (jobject object, jobject object1)
{
if ((*android_java_env)->ExceptionCheck (android_java_env))
{
__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);
ANDROID_DELETE_LOCAL_REF (object);
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 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 (object)
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 (object)
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 encoded URL. Upon
failure, return a string describing the error. Else, value is
nil. */
Lisp_Object
android_browse_url (Lisp_Object url)
{
jobject value, string;
Lisp_Object tem;
const char *buffer;
string = android_build_string (url);
value = (*android_java_env)->CallObjectMethod (android_java_env,
emacs_service,
service_class.browse_url,
string);
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 (string);
/* 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 33 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;
array = (*android_java_env)->CallObjectMethod (android_java_env,
emacs_service,
service_class.query_battery);
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 small momentary notification on screen containing
TEXT, which must be in the modified UTF encoding used by the
JVM. */
void
android_display_toast (const char *text)
{
jstring string;
/* Make the string. */
string = (*android_java_env)->NewStringUTF (android_java_env,
text);
android_exception_check ();
/* Display the toast. */
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.display_toast,
string);
android_exception_check_1 (string);
/* Delete the local reference to the string. */
ANDROID_DELETE_LOCAL_REF (string);
}
/* Whether or not a query is currently being made. */
static bool 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 incremented every time a query starts, and is set to
zerp every time one ends. If the UI thread tries to make a query
and sees that the counter is non-zero, it simply returns so that
its event loop can proceed to perform and respond to the query. If
the Emacs thread sees the same thing, then it stops to service all
queries being made by the input method, then proceeds to make its
query. */
/* 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_SEQ_CST))
return;
/* First, load the procedure and closure. */
__atomic_load (&android_query_context, &closure, __ATOMIC_SEQ_CST);
__atomic_load (&android_query_function, &proc, __ATOMIC_SEQ_CST);
if (!proc)
return;
proc (closure);
/* Finish the query. */
__atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST);
__atomic_store_n (&android_query_function, NULL, __ATOMIC_SEQ_CST);
__atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST);
/* Signal completion. */
sem_post (&android_query_sem);
}
/* 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)
{
if (__atomic_test_and_set (&android_servicing_query,
__ATOMIC_SEQ_CST))
{
/* Answer the query that is currently being made. */
assert (android_query_function != NULL);
android_check_query ();
/* Wait for that query to complete. */
while (__atomic_load_n (&android_servicing_query,
__ATOMIC_SEQ_CST))
;;
}
}
/* Notice that a query has stopped. This function may be called from
Java. */
static void
android_end_query (void)
{
__atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST);
}
/* 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;
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. */
__atomic_store_n (&android_query_context, closure, __ATOMIC_SEQ_CST);
__atomic_store_n (&android_query_function, proc, __ATOMIC_SEQ_CST);
/* Don't allow deadlocks to happen; make sure the Emacs thread is
not waiting for something to be done. */
if (__atomic_test_and_set (&android_servicing_query,
__ATOMIC_SEQ_CST))
{
__atomic_store_n (&android_query_context, NULL,
__ATOMIC_SEQ_CST);
__atomic_store_n (&android_query_function, NULL,
__ATOMIC_SEQ_CST);
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. */
while (sem_wait (&android_query_sem) < 0)
;;
return 0;
}
/* Input method related functions. */
/* Change WINDOW's active selection to the characters between
SELECTION_START and SELECTION_END.
Also, update the composing region to COMPOSING_REGION_START and
COMPOSING_REGION_END.
If any value cannot fit in jint, then the behavior of the input
method is undefined. */
void
android_update_ic (android_window window, ptrdiff_t selection_start,
ptrdiff_t selection_end, ptrdiff_t composing_region_start,
ptrdiff_t composing_region_end)
{
jobject object;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.update_ic,
object,
(jint) selection_start,
(jint) selection_end,
(jint) composing_region_start,
(jint) composing_region_end);
android_exception_check ();
}
/* Reinitialize any ongoing input method connection on WINDOW.
Any input method that is connected to WINDOW will invalidate its
cache of the buffer contents.
MODE controls certain aspects of the input method's behavior:
- If MODE is ANDROID_IC_MODE_NULL, the input method will be
deactivated, and an ASCII only keyboard will be displayed
instead.
- If MODE is ANDROID_IC_MODE_ACTION, the input method will
edit text normally, but send ``return'' as a key event.
This is useful inside the mini buffer.
- If MODE is ANDROID_IC_MODE_TEXT, the input method is free
to behave however it wants. */
void
android_reset_ic (android_window window, enum android_ic_mode mode)
{
jobject object;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
service_class.reset_ic,
object, (jint) mode);
android_exception_check ();
}
/* Make updates to extracted text known to the input method on
WINDOW. TEXT should be a local reference to the new
extracted text. TOKEN should be the token specified by the
input method. */
void
android_update_extracted_text (android_window window, void *text,
int token)
{
jobject object;
jmethodID method;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
method = service_class.update_extracted_text;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
method, object,
/* N.B. that
text is not
jobject,
because that
type is not
available in
androidgui.h. */
(jobject) text,
(jint) token);
android_exception_check_1 (text);
}
/* Report the position of the cursor to the input method connection on
WINDOW.
X is the horizontal position of the end of the insertion marker. Y
is the top of the insertion marker. Y_BASELINE is the baseline of
the row containing the insertion marker, and Y_BOTTOM is the bottom
of the insertion marker. */
void
android_update_cursor_anchor_info (android_window window, float x,
float y, float y_baseline,
float y_bottom)
{
jobject object;
jmethodID method;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
method = service_class.update_cursor_anchor_info;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
emacs_service,
service_class.class,
method,
object,
(jfloat) x,
(jfloat) y,
(jfloat) y_baseline,
(jfloat) y_bottom);
android_exception_check ();
}
/* Window decoration management functions. */
/* Make the specified WINDOW fullscreen, i.e. obscure all of the
system navigation and status bars. If not FULLSCREEN, make it
maximized instead.
Value is 1 if the system does not support this, else 0. */
int
android_set_fullscreen (android_window window, bool fullscreen)
{
jobject object;
/* Android 4.0 and earlier don't support fullscreen windows. */
if (android_api_level < 16)
return 1;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
object,
window_class.class,
window_class.set_fullscreen,
(jboolean) fullscreen);
android_exception_check ();
return 0;
}
/* External asset management interface. By using functions here
to read and write from files, Emacs can avoid opening a
shared memory file descriptor for each ``asset'' file. */
/* Like android_open. However, return a structure that can
either directly hold an AAsset or a file descriptor.
Value is the structure upon success. Upon failure, value
consists of an uninitialized file descriptor, but its asset
field is set to -1, and errno is set accordingly. */
struct android_fd_or_asset
android_open_asset (const char *filename, int oflag, mode_t mode)
{
const char *name;
struct android_fd_or_asset fd;
AAsset *asset;
/* Initialize FD by setting its asset to an invalid
pointer. */
fd.asset = (void *) -1;
/* See if this is an asset. */
if (asset_manager && (name = android_get_asset_name (filename)))
{
/* Return failure for unsupported flags. */
if (oflag & O_WRONLY || oflag & O_RDWR)
{
errno = EROFS;
return fd;
}
if (oflag & O_DIRECTORY)
{
errno = ENOTSUP;
return fd;
}
/* Now try to open the asset. */
asset = AAssetManager_open (asset_manager, name,
AASSET_MODE_STREAMING);
if (!asset)
{
errno = ENOENT;
return fd;
}
/* Return the asset. */
fd.asset = asset;
return fd;
}
/* If the file is not an asset, fall back to android_open and
get a regular file descriptor. */
fd.fd = android_open (filename, oflag, mode);
if (fd.fd < 0)
return fd;
/* Set fd.asset to NULL, signifying that it is a file
descriptor. */
fd.asset = NULL;
return fd;
}
/* Like android_close. However, it takes a ``file descriptor''
opened using android_open_asset. */
int
android_close_asset (struct android_fd_or_asset asset)
{
if (!asset.asset)
return android_close (asset.fd);
AAsset_close (asset.asset);
return 0;
}
/* Like `emacs_read_quit'. However, it handles file descriptors
opened using `android_open_asset' as well. */
ssize_t
android_asset_read_quit (struct android_fd_or_asset asset,
void *buffer, size_t size)
{
if (!asset.asset)
return emacs_read_quit (asset.fd, buffer, size);
/* It doesn't seem possible to quit from inside AAsset_read,
sadly. */
return AAsset_read (asset.asset, buffer, size);
}
/* Like `read'. However, it handles file descriptors opened
using `android_open_asset' as well. */
ssize_t
android_asset_read (struct android_fd_or_asset asset,
void *buffer, size_t size)
{
if (!asset.asset)
return read (asset.fd, buffer, size);
/* It doesn't seem possible to quit from inside AAsset_read,
sadly. */
return AAsset_read (asset.asset, buffer, size);
}
/* Like `lseek', but it handles ``file descriptors'' opened with
android_open_asset. */
off_t
android_asset_lseek (struct android_fd_or_asset asset, off_t off,
int whence)
{
if (!asset.asset)
return lseek (asset.fd, off, whence);
return AAsset_seek (asset.asset, off, whence);
}
/* Like `fstat'. */
int
android_asset_fstat (struct android_fd_or_asset asset,
struct stat *statb)
{
if (!asset.asset)
return fstat (asset.fd, statb);
/* Clear statb. */
memset (statb, 0, sizeof *statb);
/* Set the mode. */
statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
/* Owned by root. */
statb->st_uid = 0;
statb->st_gid = 0;
/* Size of the file. */
statb->st_size = AAsset_getLength (asset.asset);
return 0;
}
/* Window cursor support. */
android_cursor
android_create_font_cursor (enum android_cursor_shape shape)
{
android_cursor id;
short prev_max_handle;
jobject object;
/* First, allocate the cursor handle. */
prev_max_handle = max_handle;
id = android_alloc_id ();
if (!id)
error ("Out of cursor handles!");
/* Next, create the cursor. */
object = (*android_java_env)->NewObject (android_java_env,
cursor_class.class,
cursor_class.constructor,
(jshort) id,
(jint) shape);
if (!object)
{
(*android_java_env)->ExceptionClear (android_java_env);
max_handle = prev_max_handle;
memory_full (0);
}
android_handles[id].type = ANDROID_HANDLE_CURSOR;
android_handles[id].handle
= (*android_java_env)->NewGlobalRef (android_java_env, object);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (object);
if (!android_handles[id].handle)
memory_full (0);
return id;
}
void
android_define_cursor (android_window window, android_cursor cursor)
{
jobject window1, cursor1;
jmethodID method;
window1 = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
cursor1 = android_resolve_handle (cursor, ANDROID_HANDLE_CURSOR);
method = window_class.define_cursor;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
window1,
window_class.class,
method, cursor1);
android_exception_check ();
}
void
android_free_cursor (android_cursor cursor)
{
if (android_handles[cursor].type != ANDROID_HANDLE_CURSOR)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Trying to destroy something not a CURSOR!");
emacs_abort ();
}
android_destroy_handle (cursor);
}
/* Process execution.
Newer Android systems use SELinux to restrict user programs from
executing programs installed in the application data directory for
security reasons. Emacs uses a `loader' binary installed in the
application data directory to manually load executables and replace
the `execve' system call. */
enum
{
/* Maximum number of arguments available. */
MAXARGS = 1024,
};
/* Rewrite the command line given in *ARGV to utilize the `exec1'
bootstrap binary if necessary.
Value is 0 upon success, else 1. Set errno upon failure.
ARGV holds a pointer to a NULL-terminated array of arguments given
to `emacs_spawn'. */
int
android_rewrite_spawn_argv (const char ***argv)
{
static const char *new_args[MAXARGS];
static char exec1_name[PATH_MAX], loader_name[PATH_MAX];
size_t i, nargs;
/* This isn't required on Android 9 or earlier. */
if (android_api_level < 29 || !android_use_exec_loader)
return 0;
/* Get argv[0]; this should never be NULL.
Then, verify that it exists and is executable. */
eassert (**argv);
if (access (**argv, R_OK | X_OK))
return 1;
/* Count the number of arguments in *argv. */
nargs = 0;
while ((*argv)[nargs])
++nargs;
/* nargs now holds the number of arguments in argv. If it's larger
than MAXARGS, return failure. */
if (nargs + 2 > MAXARGS)
{
errno = E2BIG;
return 1;
}
/* Fill in the name of `libexec1.so'. */
snprintf (exec1_name, PATH_MAX, "%s/libexec1.so",
android_lib_dir);
/* And libloader.so. */
snprintf (loader_name, PATH_MAX, "%s/libloader.so",
android_lib_dir);
/* Now fill in the first two arguments. */
new_args[0] = exec1_name;
new_args[1] = loader_name;
/* And insert the rest, including the trailing NULL. */
for (i = 0; i < nargs + 1; ++i)
new_args[i + 2] = (*argv)[i];
/* Replace argv. */
*argv = new_args;
/* Return success. */
return 0;
}
#else /* ANDROID_STUBIFY */
/* X emulation functions for Android. */
struct android_gc *
android_create_gc (enum android_gc_value_mask mask,
struct android_gc_values *values)
{
/* This function should never be called when building stubs. */
emacs_abort ();
}
void
android_free_gc (struct android_gc *gc)
{
/* This function should never be called when building stubs. */
emacs_abort ();
}
struct android_image *
android_create_image (unsigned int depth, enum android_image_format format,
char *data, unsigned int width, unsigned int height)
{
emacs_abort ();
}
void
android_destroy_image (struct android_image *ximg)
{
emacs_abort ();
}
void
android_put_pixel (struct android_image *ximg, int x, int y,
unsigned long pixel)
{
emacs_abort ();
}
unsigned long
android_get_pixel (struct android_image *ximg, int x, int y)
{
emacs_abort ();
}
struct android_image *
android_get_image (android_drawable drawable,
enum android_image_format format)
{
emacs_abort ();
}
void
android_put_image (android_pixmap pixmap,
struct android_image *image)
{
emacs_abort ();
}
void
android_project_image_bilinear (struct android_image *image,
struct android_image *out,
struct android_transform *transform)
{
emacs_abort ();
}
void
android_project_image_nearest (struct android_image *image,
struct android_image *out,
struct android_transform *transform)
{
emacs_abort ();
}
#endif