1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-15 10:30:25 -08:00
emacs/src/android.c
Po Lu 56e55a8008 Update Android port
* INSTALL.android: Update.
* build-aux/ndk-build-helper-1.mk: Fix typo.
* configure.ac: Enable --with-json on Android.
* cross/ndk-build/ndk-build-shared-library.mk:
(NDK_CFLAGS_$(LOCAL_MODULE)):
(LOCAL_MODULE_FILENAME):
* cross/ndk-build/ndk-build-static-library.mk:
(ALL_OBJECT_FILES$(LOCAL_MODULE)):
(LOCAL_MODULE_FILENAME): Recursively resolve dependencies.
* cross/ndk-build/ndk-resolve.mk: New function.

* doc/emacs/android.texi (Android Startup): Document how Emacs
is dumped during initial startup.

* java/Makefile.in (filename): Fix build with multiple shared
libraries.
* java/README: Improve commentary.
* java/org/gnu/emacs/EmacsApplication.java (onCreate): Look and
set dump file.
* java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
function getFingerprint.
* java/org/gnu/emacs/EmacsPreferencesActivity.java (onCreate):
Add option to erase the dump file.
* java/org/gnu/emacs/EmacsService.java (browseUrl): New
function.
* java/org/gnu/emacs/EmacsThread.java (run): Specify dump file
if found.
* lisp/loadup.el: Always dump during loadup on Android.

* lisp/net/browse-url.el (browse-url--browser-defcustom-type):
(browse-url-default-browser):
(browse-url-default-android-browser): New browse url type.

* m4/ndk-build.m4 (ndk_package_map): Map jansson to libjansson.
* src/android.c (struct android_emacs_service): New method
`browse_url'.
(getFingerprint): New function.
(android_init_emacs_service): Initialize new method.
(android_browse_url): New function.

* src/android.h: Update prototypes.

* src/androidselect.c (Fandroid_browse_url): New function.
(syms_of_androidselect): Define it.

* src/emacs.c (load_pdump): Don't look in fancy places on
Android.
* src/pdumper.c (Fdump_emacs_portable): Allow dumping while
interactive on Android.
(syms_of_pdumper): New variable `pdumper-fingerprint'.

* src/sfntfont-android.c (sfntfont_android_composite_bitmap):
Fix unused variables.
2023-01-24 17:31:16 +08:00

4874 lines
122 KiB
C
Raw Blame History

This file contains invisible Unicode characters

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

/* Android initialization for GNU Emacs.
Copyright (C) 2023 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <config.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <limits.h>
#include <signal.h>
#include <semaphore.h>
#include <dlfcn.h>
#include <errno.h>
#include <math.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <assert.h>
#include <fingerprint.h>
#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
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <linux/ashmem.h>
#include <linux/unistd.h>
#include <sys/syscall.h>
#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 sync;
jmethodID browse_url;
};
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 window_updated;
};
/* 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 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;
/* The last event serial used. This is a 32 bit value, but it is
stored in unsigned long to be consistent with X. */
static unsigned int event_serial;
/* 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;
/* Whether or not pselect finished. */
static volatile bool android_pselect_completed;
/* 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];
#endif
static void *
android_run_select_thread (void *data)
{
int rc;
#if __ANDROID_API__ < 16
int nfds;
fd_set readfds;
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;
readfds = *android_pselect_readfds;
if (nfds < select_pipe[0] + 1)
nfds = select_pipe[0] + 1;
FD_SET (select_pipe[0], &readfds);
rc = pselect (nfds, &readfds,
android_pselect_writefds,
android_pselect_exceptfds,
android_pselect_timeout,
NULL);
/* Subtract 1 from rc if writefds contains the select pipe. */
if (FD_ISSET (select_pipe[0],
android_pselect_writefds))
rc -= 1;
android_pselect_rc = rc;
pthread_mutex_unlock (&event_queue.select_mutex);
/* Signal the main thread that there is now data to read.
It is ok to signal this condition variable without holding
the event queue lock, because android_select will always
wait for this to complete before returning. */
android_pselect_completed = true;
pthread_cond_signal (&event_queue.read_var);
/* 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)
;;
/* 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.
It is ok to signal this condition variable without holding
the event queue lock, because android_select will always
wait for this to complete before returning. */
android_pselect_completed = true;
pthread_cond_signal (&event_queue.read_var);
if (rc != -1 || errno != EINTR)
/* 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)
{
/* Nothing to do here, this signal handler is only installed to make
sure the disposition of SIGUSR1 is enough. */
}
#endif
/* 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);
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);
}
static 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_signal (&event_queue.read_var);
pthread_mutex_unlock (&event_queue.mutex);
/* Now set pending_signals to true. This allows C-g to be handled
immediately even without SIGIO. */
pending_signals = true;
}
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
pthread_mutex_lock (&event_queue.mutex);
if (event_queue.num_events)
{
pthread_mutex_unlock (&event_queue.mutex);
return 1;
}
nfds_return = 0;
android_pselect_completed = false;
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
/* Wait for pselect to return in any case. */
while (sem_wait (&android_pselect_sem) < 0)
;;
/* If there are now events in the queue, return 1. */
if (event_queue.num_events)
nfds_return = 1;
/* 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;
/* Unlock the event queue mutex. */
pthread_mutex_unlock (&event_queue.mutex);
/* This is to shut up process.c when pselect gets EINTR. */
if (nfds_return < 0)
errno = EINTR;
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 ();
/* Subtract the initial header bytes. */
if (limit_return)
*limit_return = directory_tree_size - 5;
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)
{
if (!pw->pw_gecos)
return (char *) "Android user";
return pw->pw_gecos;
}
/* 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;
}
/* 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;
/* 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;
}
return fstatat (dirfd, pathname, statbuf, flags);
}
/* Return if NAME is a file that is actually an asset and is
accessible, as long as !(amode & W_OK). */
bool
android_file_access_p (const char *name, int amode)
{
if (!asset_manager)
return false;
if (!(amode & W_OK) && (name = android_get_asset_name (name)))
{
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;
}
/* Get a file descriptor backed by a temporary in-memory file for the
given asset. */
static int
android_hack_asset_fd (AAsset *asset)
{
int fd, rc;
unsigned char *mem;
size_t size;
fd = open ("/dev/ashmem", O_RDWR);
if (fd < 0)
return -1;
/* Assets must be small enough to fit in size_t, if off_t is
larger. */
size = AAsset_getLength (asset);
/* 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;
}
/* Read two bytes from FD and see if they are ``PK'', denoting ZIP
archive compressed data.
If they are not, rewind the file descriptor to offset 0.
If either operation fails, return -1 and close FD. Else, value is
FD. */
static int
android_check_compressed_file (int fd)
{
char bytes[2];
if (read (fd, bytes, 2) != 2)
goto lseek_back;
if (bytes[0] != 'P' || bytes[1] != 'K')
goto lseek_back;
/* This could be compressed data! */
return -1;
lseek_back:
/* Seek back to offset 0. If this fails, return -1. */
if (lseek (fd, 0, SEEK_SET) != 0)
{
close (fd);
return -1;
}
return fd;
}
/* `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, int mode)
{
const char *name;
AAsset *asset;
int fd, oldfd;
off_t out_start, out_length;
bool fd_hacked;
/* This flag means whether or not fd should not be duplicated. */
fd_hacked = false;
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 = EINVAL;
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;
}
/* Try to obtain the file descriptor corresponding to this
asset. */
fd = AAsset_openFileDescriptor (asset, &out_start,
&out_length);
/* The platform sometimes returns a file descriptor to ZIP
compressed data. Detect that and fall back to creating a
shared memory file descriptor. */
fd = android_check_compressed_file (fd);
if (fd == -1)
{
/* The asset can't be accessed for some reason. Try to
create a shared memory file descriptor. */
fd = android_hack_asset_fd (asset);
if (fd == -1)
{
AAsset_close (asset);
errno = ENXIO;
return -1;
}
fd_hacked = true;
}
/* Duplicate the file descriptor and then close the asset,
which will close the original file descriptor. */
if (!fd_hacked)
{
oldfd = fd;
fd = fcntl (oldfd, F_DUPFD_CLOEXEC);
/* Close the original file descriptor. */
close (oldfd);
}
if (fd >= ANDROID_MAX_ASSET_FD || fd < 0)
{
/* Too bad. You lose. */
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;
}
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
& ANDROID_FD_TABLE_ENTRY_IS_VALID))
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
& ANDROID_FD_TABLE_ENTRY_IS_VALID))
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;
}
/* JNI functions called by Java. */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#endif
JNIEXPORT jstring JNICALL
NATIVE_NAME (getFingerprint) (JNIEnv *env, jobject object)
{
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 emacs_service_object)
{
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_RELAXED))
{
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);
/* Create a pipe and duplicate it to stdout and stderr. Next, make
a thread that prints stderr to the system log. */
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);
/* 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",
android_site_load_path,
android_files_dir,
android_lib_dir, android_game_path);
/* Make a reference to the Emacs service. */
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. */
}
/* 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 (sync, "sync", "()V");
FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;)"
"Ljava/lang/String;");
#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, "<init>", "(S[IIII)V");
FIND_METHOD (constructor_mutable, "<init>", "(SIII)V");
#undef FIND_METHOD
}
static void
android_init_graphics_point (void)
{
jclass old;
point_class.class
= (*android_java_env)->FindClass (android_java_env,
"android/graphics/Point");
eassert (point_class.class);
old = point_class.class;
point_class.class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) old);
ANDROID_DELETE_LOCAL_REF (old);
if (!point_class.class)
emacs_abort ();
#define FIND_METHOD(c_name, name, signature) \
point_class.c_name \
= (*android_java_env)->GetMethodID (android_java_env, \
point_class.class, \
name, signature); \
assert (point_class.c_name);
FIND_METHOD (constructor, "<init>", "(II)V");
#undef FIND_METHOD
}
static void
android_init_emacs_drawable (void)
{
jclass old;
drawable_class.class
= (*android_java_env)->FindClass (android_java_env,
"org/gnu/emacs/EmacsDrawable");
eassert (drawable_class.class);
old = drawable_class.class;
drawable_class.class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) old);
ANDROID_DELETE_LOCAL_REF (old);
if (!drawable_class.class)
emacs_abort ();
#define FIND_METHOD(c_name, name, signature) \
drawable_class.c_name \
= (*android_java_env)->GetMethodID (android_java_env, \
drawable_class.class, \
name, signature); \
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 (window_updated, "windowUpdated", "(J)V");
#undef FIND_METHOD
}
extern JNIEXPORT void JNICALL
NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv)
{
char **c_argv;
jsize nelements, i;
jobject argument;
const char *c_argument;
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 ();
/* 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. */
android_init_gui = true;
android_emacs_init (nelements, c_argv);
/* android_emacs_init should never return. */
emacs_abort ();
}
extern JNIEXPORT void JNICALL
NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object)
{
emacs_abort ();
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint x, jint y, jint width,
jint height)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
jint unicode_char)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
jint unicode_char)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
jshort window, jint action)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
jint button)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
jint button)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
{
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;
}
extern 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)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
jshort window)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
jshort window)
{
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;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
jshort window, jint menu_event_id)
{
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;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jint width, jint height)
{
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;
}
#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++;
if (android_handles[max_handle].handle)
{
handle = max_handle + 1;
while (max_handle < handle)
{
++max_handle;
if (!max_handle)
++max_handle;
if (!android_handles[max_handle].handle)
return 0;
}
return 0;
}
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_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (old);
if (!class)
memory_full (0);
}
(*android_java_env)->CallVoidMethod (android_java_env,
android_handles[handle].handle,
method);
(*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;
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 ();
}
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;
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 ();
}
return android_handles[handle].handle;
}
static jmethodID android_lookup_method (const char *, const char *,
const char *);
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;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
if (value_mask & ANDROID_CW_BACK_PIXEL)
{
method = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"changeWindowBackground", "(I)V");
(*android_java_env)->CallVoidMethod (android_java_env,
window, method,
(jint) attrs->background_pixel);
}
}
/* 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, "<init>",
"(SLorg/gnu/emacs/EmacsWindow;"
"IIIIZ)V");
assert (constructor != NULL);
old = class;
class = (*android_java_env)->NewGlobalRef (android_java_env, class);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (old);
if (!class)
memory_full (0);
}
/* 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,
"<init>", "(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_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (old);
if (!android_rect_class)
memory_full (0);
}
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,
"<init>", "(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_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (old);
if (!emacs_gc_class)
memory_full (0);
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)->CallVoidMethod (android_java_env,
gcontext,
emacs_gc_mark_dirty,
(jboolean) clip_changed);
}
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);
if (!array)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
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));
if (!rect)
{
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (array);
memory_full (0);
}
(*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)->CallVoidMethod (android_java_env,
gcontext,
emacs_gc_mark_dirty,
(jboolean) true);
/* 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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"reparentTo",
"(Lorg/gnu/emacs/EmacsWindow;II)V");
(*android_java_env)->CallVoidMethod (android_java_env, window,
method,
parent, (jint) x, (jint) y);
}
/* Look up the method with SIGNATURE by NAME in CLASS. Abort if it
could not be found. This should be used for functions which are
not called very often.
CLASS must never be unloaded, or the behavior is undefined. */
static jmethodID
android_lookup_method (const char *class, const char *name,
const char *signature)
{
jclass java_class;
jmethodID method;
java_class
= (*android_java_env)->FindClass (android_java_env, class);
if (!java_class)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Failed to find class %s", class);
emacs_abort ();
}
method
= (*android_java_env)->GetMethodID (android_java_env,
java_class, name,
signature);
if (!method)
{
__android_log_print (ANDROID_LOG_ERROR, __func__,
"Failed to find method %s in class %s"
" with signature %s",
name, class, signature);
emacs_abort ();
}
ANDROID_DELETE_LOCAL_REF (java_class);
return method;
}
void
android_clear_window (android_window handle)
{
jobject window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
(*android_java_env)->CallVoidMethod (android_java_env,
emacs_service,
service_class.clear_window,
window);
}
void
android_map_window (android_window handle)
{
jobject window;
jmethodID map_window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
map_window = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"mapWindow", "()V");
(*android_java_env)->CallVoidMethod (android_java_env,
window, map_window);
}
void
android_unmap_window (android_window handle)
{
jobject window;
jmethodID unmap_window;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
unmap_window = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"unmapWindow", "()V");
(*android_java_env)->CallVoidMethod (android_java_env,
window, unmap_window);
}
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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"resizeWindow", "(II)V");
(*android_java_env)->CallVoidMethod (android_java_env,
window, resize_window,
(jint) width, (jint) height);
}
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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"moveWindow", "(II)V");
(*android_java_env)->CallVoidMethod (android_java_env,
window, move_window,
(jint) x, (jint) y);
}
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)->CallVoidMethod (android_java_env,
window,
window_class.swap_buffers);
}
}
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)->CallVoidMethod (android_java_env,
emacs_service,
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);
if (!colors)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
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)->CallVoidMethod (android_java_env,
emacs_service,
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);
}
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);
if (!array)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
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);
if (!point)
{
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (array);
memory_full (0);
}
(*android_java_env)->SetObjectArrayElement (android_java_env,
array, i, point);
ANDROID_DELETE_LOCAL_REF (point);
}
(*android_java_env)->CallVoidMethod (android_java_env,
emacs_service,
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)->CallVoidMethod (android_java_env,
emacs_service,
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)->CallVoidMethod (android_java_env,
emacs_service,
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)->CallVoidMethod (android_java_env,
emacs_service,
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)->CallVoidMethod (android_java_env,
emacs_service,
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);
if (!bitmap)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
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);
if (!bitmap)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
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)->CallVoidMethod (android_java_env,
emacs_service,
service_class.ring_bell);
}
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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"makeInputFocus", "(J)V");
(*android_java_env)->CallVoidMethod (android_java_env, window,
make_input_focus, (jlong) time);
}
void
android_raise_window (android_window handle)
{
jobject window;
jmethodID raise;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
raise = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"raise", "()V");
(*android_java_env)->CallVoidMethod (android_java_env, window,
raise);
}
void
android_lower_window (android_window handle)
{
jobject window;
jmethodID lower;
window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
lower = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"lower", "()V");
(*android_java_env)->CallVoidMethod (android_java_env, window,
lower);
}
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);
if (!array)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
/* 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);
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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"getWindowGeometry", "()[I");
window_geometry
= (*android_java_env)->CallObjectMethod (android_java_env,
window,
get_geometry);
if (!window_geometry)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
/* 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);
*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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"translateCoordinates",
"(II)[I");
coordinates
= (*android_java_env)->CallObjectMethod (android_java_env,
window, method,
(jint) x, (jint) y);
if (!coordinates)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
/* 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);
*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);
}
void
android_sync (void)
{
(*android_java_env)->CallVoidMethod (android_java_env,
emacs_service,
service_class.sync);
android_exception_check ();
}
/* 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));
if (!rect)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
/* Post the damage to the drawable. */
(*android_java_env)->CallVoidMethod (android_java_env,
drawable,
drawable_class.damage_rect,
rect);
ANDROID_DELETE_LOCAL_REF (rect);
}
/* Other misc system routines. */
int
android_get_screen_width (void)
{
return (*android_java_env)->CallIntMethod (android_java_env,
emacs_service,
service_class.get_screen_width,
(jboolean) false);
}
int
android_get_screen_height (void)
{
return (*android_java_env)->CallIntMethod (android_java_env,
emacs_service,
service_class.get_screen_height,
(jboolean) false);
}
int
android_get_mm_width (void)
{
return (*android_java_env)->CallIntMethod (android_java_env,
emacs_service,
service_class.get_screen_width,
(jboolean) true);
}
int
android_get_mm_height (void)
{
return (*android_java_env)->CallIntMethod (android_java_env,
emacs_service,
service_class.get_screen_height,
(jboolean) true);
}
bool
android_detect_mouse (void)
{
return (*android_java_env)->CallBooleanMethod (android_java_env,
emacs_service,
service_class.detect_mouse);
}
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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"setDontFocusOnMap", "(Z)V");
(*android_java_env)->CallVoidMethod (android_java_env, window,
method,
(jboolean) no_focus_on_map);
}
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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"setDontAcceptFocus", "(Z)V");
(*android_java_env)->CallVoidMethod (android_java_env, window,
method,
(jboolean) no_accept_focus);
}
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 ();
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)->CallVoidMethod (android_java_env, object,
method, (jboolean) show);
/* Check for out of memory errors. */
android_exception_check ();
}
/* Tell the window system that all configure events sent to WINDOW
have been fully processed, and that it is now okay to display its
new contents. SERIAL is the serial of the last configure event
processed. */
void
android_window_updated (android_window window, unsigned long serial)
{
jobject object;
jmethodID method;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
method = window_class.window_updated;
(*android_java_env)->CallVoidMethod (android_java_env, object,
method, (jlong) serial);
android_exception_check ();
}
#if __ANDROID_API__ >= 17
#undef faccessat
/* Replace the system faccessat with one which understands AT_EACCESS.
Android's faccessat simply fails upon using AT_EACCESS, so replace
it with zero here. This isn't caught during configuration.
This replacement is only done when building for Android 17 or
later, because earlier versions use the gnulib replacement that
lacks these issues. */
int
faccessat (int dirfd, const char *pathname, int mode, int flags)
{
static int (*real_faccessat) (int, const char *, int, int);
if (!real_faccessat)
real_faccessat = dlsym (RTLD_NEXT, "faccessat");
return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
}
#endif /* __ANDROID_API__ >= 17 */
/* 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. 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 ();
}
/* 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;
encoded = ENCODE_UTF_8 (text);
/* 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. The only consequence of passing an actual
UTF-8 string is that NUL bytes cannot be represented, which is
not really of consequence. */
string = (*android_java_env)->NewStringUTF (android_java_env,
SSDATA (encoded));
if (!string)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
return string;
}
/* Do the same, except TEXT is constant string data. */
jstring
android_build_jstring (const char *text)
{
jstring string;
string = (*android_java_env)->NewStringUTF (android_java_env,
text);
if (!string)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
return string;
}
/* 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_java_env)->ExceptionClear (android_java_env);
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 ();
/* 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;
}
#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