Android headers. In addition, it may be necessary to specify
"-gdwarf-2", due to a bug in the Android NDK.
-Emacs is known to build for Android 2.2 (API version 8) or later, and
-run on Android 2.3 or later. It is supposed to run on Android 2.2 as
-well.
+Even older versions of the Android SDK do not require the extra
+`-isystem' directives.
+
+Emacs is known to run on Android 2.2 (API version 8) or later, with
+the NDK r10b or later. We wanted to make Emacs work on even older
+versions of Android, but they are missing the required JNI graphics
+library that allows Emacs to display text from C code.
+
+Due to an extremely nasty bug in the Android 2.2 system, the generated
+Emacs package cannot be compressed in builds for Android 2.2. As a
+result, the Emacs package will be approximately 100 megabytes larger
+than a compressed package for a newer version of Android.
DEBUG AND RELEASE BUILDS
# Now tell java/Makefile if Emacs is being built for Android 4.3 or
# earlier.
ANDROID_SDK_18_OR_EARLIER=
- if test "$android_sdk" -lt "18"; then
+ if test "$android_sdk" -le "18"; then
ANDROID_SDK_18_OR_EARLIER=yes
fi
AC_SUBST([ANDROID_SDK_18_OR_EARLIER])
+ # Likewise for Android 2.2.
+ ANDROID_SDK_8_OR_EARLIER=
+ if test "$android_sdk" -le "8"; then
+ ANDROID_SDK_8_OR_EARLIER=yes
+ fi
+ AC_SUBST([ANDROID_SDK_8_OR_EARLIER])
+
# Save confdefs.h and config.log for now.
mv -f confdefs.h _confdefs.h
mv -f config.log _config.log
[The type of system you are compiling for; sets 'system-type'.])
AC_SUBST([SYSTEM_TYPE])
+# Check for pw_gecos in struct passwd; this is known to be missing on
+# Android.
+
+AC_CHECK_MEMBERS([struct passwd.pw_gecos], [], [], [#include <pwd.h>])
pre_PKG_CONFIG_CFLAGS=$CFLAGS
pre_PKG_CONFIG_LIBS=$LIBS
ANDROID_CFLAGS="$ANDROID_CFLAGS -ftree-vectorize"
# Link with libraries required for Android support.
- ANDROID_LIBS="-landroid -llog -ljnigraphics"
+ # API 9 and later require `-landroid' for the asset manager.
+ # API 8 uses an emulation via the JNI.
+ if test "$ANDROID_SDK" -lt "9"; then
+ ANDROID_LIBS="-llog -ljnigraphics"
+ else
+ ANDROID_LIBS="-landroid -llog -ljnigraphics"
+ fi
# This is required to make the system load emacs.apk's libpng
# (among others) instead of the system's own. But it doesn't work
LIBS="$LIB_PTHREAD $LIB_MATH $LIBS"
AC_CHECK_FUNCS([accept4 fchdir gethostname \
getrusage get_current_dir_name \
-lrand48 random rint trunc \
+lrand48 random rint tcdrain trunc \
select getpagesize setlocale newlocale \
getrlimit setrlimit shutdown \
pthread_sigmask strsignal setitimer \
ANDROID_JAR = @ANDROID_JAR@
ANDROID_ABI = @ANDROID_ABI@
ANDROID_SDK_18_OR_EARLIER = @ANDROID_SDK_18_OR_EARLIER@
+ANDROID_SDK_8_OR_EARLIER = @ANDROID_SDK_8_OR_EARLIER@
WARN_JAVAFLAGS = -Xlint:deprecation
JAVAFLAGS = -classpath "$(ANDROID_JAR):." -target 1.7 -source 1.7 \
JARSIGNER_FLAGS =
endif
+# When building Emacs for Android 2.2, assets must not be compressed.
+# Otherwise, the asset manager fails to extract files larger than 1
+# MB.
+
+ifneq (,$(ANDROID_SDK_8_OR_EARLIER))
+AAPT_ASSET_ARGS = -0 ""
+else
+AAPT_ASSET_ARGS =
+endif
+
SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 $(JARSIGNER_FLAGS)
SIGN_EMACS_V2 = sign --v2-signing-enabled --ks emacs.keystore \
--debuggable-apk-permitted --ks-pass pass:emacs1
# of Android. Make sure not to generate R.java, as it's already been
# generated.
$(AM_V_AAPT) $(AAPT) p -I "$(ANDROID_JAR)" -F $@ \
- -f -M AndroidManifest.xml -A install_temp/assets \
+ -f -M AndroidManifest.xml $(AAPT_ASSET_ARGS) \
+ -A install_temp/assets \
-S res -J install_temp
$(AM_V_SILENT) pushd install_temp &> /dev/null; \
$(AAPT) add ../$@ `find lib -type f`; \
jdb=no
attach_existing=no
gdbserver=
+gdb=gdb
while [ $# -gt 0 ]; do
case "$1" in
echo " --port PORT run the GDB server on a specific port"
echo " --jdb-port PORT run the JDB server on a specific port"
echo " --jdb run JDB instead of GDB"
+ echo " --gdb use specified GDB binary"
echo " --attach-existing attach to an existing process"
echo " --gdbserver BINARY upload and use the specified gdbserver binary"
echo " --help print this message"
"--jdb" )
jdb=yes
;;
+ "--gdb" )
+ shift
+ gdb=$1
+ ;;
"--gdbserver" )
shift
gdbserver=$1
# Finally, start gdb with any extra arguments needed.
cd "$oldpwd"
-gdb --eval-command "target remote localhost:$gdb_port" $gdbargs
+$gdb --eval-command "target remote localhost:$gdb_port" $gdbargs
for a file named ``emacs-<fingerprint>.pdmp'' and delete the
rest. */
filesDirectory = context.getFilesDir ();
+
allFiles = filesDirectory.listFiles (new FileFilter () {
@Override
public boolean
public void
onCreate ()
{
- AssetManager manager;
+ final AssetManager manager;
Context app_context;
- String filesDir, libDir, cacheDir, classPath;
- double pixelDensityX;
- double pixelDensityY;
+ final String filesDir, libDir, cacheDir, classPath;
+ final double pixelDensityX;
+ final double pixelDensityY;
SERVICE = this;
handler = new Handler (Looper.getMainLooper ());
Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+ ", libDir = " + libDir + ", and classPath = " + classPath);
- EmacsNative.setEmacsParams (manager, filesDir, libDir,
- cacheDir, (float) pixelDensityX,
- (float) pixelDensityY,
- classPath, this);
-
/* Start the thread that runs Emacs. */
- thread = new EmacsThread (this, needDashQ);
+ thread = new EmacsThread (this, new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ EmacsNative.setEmacsParams (manager, filesDir, libDir,
+ cacheDir, (float) pixelDensityX,
+ (float) pixelDensityY,
+ classPath, EmacsService.this);
+ }
+ }, needDashQ);
thread.start ();
}
catch (IOException exception)
/* Whether or not Emacs should be started -Q. */
private boolean startDashQ;
+ /* Runnable run to initialize Emacs. */
+ private Runnable paramsClosure;
+
public
- EmacsThread (EmacsService service, boolean startDashQ)
+ EmacsThread (EmacsService service, Runnable paramsClosure,
+ boolean startDashQ)
{
super ("Emacs main thread");
this.startDashQ = startDashQ;
+ this.paramsClosure = paramsClosure;
}
@Override
else
args = new String[] { "libandroid-emacs.so", "-Q", };
+ paramsClosure.run ();
+
/* Run the native code now. */
EmacsNative.initEmacs (args, EmacsApplication.dumpFileName,
Build.VERSION.SDK_INT);
--- /dev/null
+/* 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 <android/log.h>
+
+/* This file contains an emulation of the Android asset manager API
+ used on builds for Android 2.2. It is included by android.c
+ whenever appropriate.
+
+ The replacements in this file are not thread safe and must only be
+ called from the creating thread. */
+
+struct android_asset_manager
+{
+ /* JNI environment. */
+ JNIEnv *env;
+
+ /* Asset manager class and functions. */
+ jclass class;
+ jmethodID open_fd;
+
+ /* Asset file descriptor class and functions. */
+ jclass fd_class;
+ jmethodID get_length;
+ jmethodID create_input_stream;
+ jmethodID close;
+
+ /* Input stream class and functions. */
+ jclass input_stream_class;
+ jmethodID read;
+ jmethodID stream_close;
+
+ /* Associated asset manager object. */
+ jobject asset_manager;
+};
+
+typedef struct android_asset_manager AAssetManager;
+
+struct android_asset
+{
+ /* The asset manager. */
+ AAssetManager *manager;
+
+ /* The length of the asset, or -1. */
+ jlong length;
+
+ /* The asset file descriptor and input stream. */
+ jobject fd, stream;
+
+ /* The mode. */
+ int mode;
+};
+
+typedef struct android_asset AAsset;
+
+static AAssetManager *
+AAssetManager_fromJava (JNIEnv *env, jobject java_manager)
+{
+ AAssetManager *manager;
+ jclass temp;
+
+ manager = malloc (sizeof *manager);
+
+ if (!manager)
+ return NULL;
+
+ manager->env = env;
+ manager->asset_manager
+ = (*env)->NewGlobalRef (env, java_manager);
+
+ if (!manager->asset_manager)
+ {
+ free (manager);
+ return NULL;
+ }
+
+ manager->class
+ = (*env)->FindClass (env, "android/content/res/AssetManager");
+ assert (manager->class);
+
+ manager->open_fd
+ = (*env)->GetMethodID (env, manager->class, "openFd",
+ "(Ljava/lang/String;)"
+ "Landroid/content/res/AssetFileDescriptor;");
+ assert (manager->open);
+
+ manager->fd_class
+ = (*env)->FindClass (env, "android/content/res/AssetFileDescriptor");
+ assert (manager->fd_class);
+
+ manager->get_length
+ = (*env)->GetMethodID (env, manager->fd_class, "getLength",
+ "()J");
+ assert (manager->get_length);
+
+ manager->create_input_stream
+ = (*env)->GetMethodID (env, manager->fd_class,
+ "createInputStream",
+ "()Ljava/io/FileInputStream;");
+ assert (manager->create_input_stream);
+
+ manager->close
+ = (*env)->GetMethodID (env, manager->fd_class,
+ "close", "()V");
+ assert (manager->close);
+
+ manager->input_stream_class
+ = (*env)->FindClass (env, "java/io/InputStream");
+ assert (manager->input_stream_class);
+
+ manager->read
+ = (*env)->GetMethodID (env, manager->input_stream_class,
+ "read", "([B)I");
+ assert (manager->read);
+
+ manager->stream_close
+ = (*env)->GetMethodID (env, manager->input_stream_class,
+ "close", "()V");
+ assert (manager->stream_close);
+
+ /* Now convert all the class references to global ones. */
+ temp = manager->class;
+ manager->class
+ = (*env)->NewGlobalRef (env, temp);
+ assert (manager->class);
+ (*env)->DeleteLocalRef (env, temp);
+ temp = manager->fd_class;
+ manager->fd_class
+ = (*env)->NewGlobalRef (env, temp);
+ assert (manager->fd_class);
+ (*env)->DeleteLocalRef (env, temp);
+ temp = manager->input_stream_class;
+ manager->input_stream_class
+ = (*env)->NewGlobalRef (env, temp);
+ assert (manager->input_stream_class);
+ (*env)->DeleteLocalRef (env, temp);
+
+ /* Return the asset manager. */
+ return manager;
+}
+
+enum
+ {
+ AASSET_MODE_STREAMING = 0,
+ AASSET_MODE_BUFFER = 1,
+ };
+
+static AAsset *
+AAssetManager_open (AAssetManager *manager, const char *c_name,
+ int mode)
+{
+ jobject desc;
+ jstring name;
+ AAsset *asset;
+
+ /* Push a local frame. */
+ asset = NULL;
+
+ (*(manager->env))->PushLocalFrame (manager->env, 3);
+
+ if ((*(manager->env))->ExceptionCheck (manager->env))
+ goto fail;
+
+ /* Encoding issues can be ignored for now as there are only ASCII
+ file names in Emacs. */
+ name = (*(manager->env))->NewStringUTF (manager->env, c_name);
+
+ if (!name)
+ goto fail;
+
+ /* Now try to open an ``AssetFileDescriptor''. */
+ desc = (*(manager->env))->CallObjectMethod (manager->env,
+ manager->asset_manager,
+ manager->open_fd,
+ name);
+
+ if (!desc)
+ goto fail;
+
+ /* Allocate the asset. */
+ asset = calloc (1, sizeof *asset);
+
+ if (!asset)
+ {
+ (*(manager->env))->CallVoidMethod (manager->env,
+ desc,
+ manager->close);
+ goto fail;
+ }
+
+ /* Pop the local frame and return desc. */
+ desc = (*(manager->env))->NewGlobalRef (manager->env, desc);
+
+ if (!desc)
+ goto fail;
+
+ (*(manager->env))->PopLocalFrame (manager->env, NULL);
+
+ asset->manager = manager;
+ asset->length = -1;
+ asset->fd = desc;
+ asset->mode = mode;
+
+ return asset;
+
+ fail:
+ (*(manager->env))->ExceptionClear (manager->env);
+ (*(manager->env))->PopLocalFrame (manager->env, NULL);
+ free (asset);
+
+ return NULL;
+}
+
+static AAsset *
+AAsset_close (AAsset *asset)
+{
+ JNIEnv *env;
+
+ env = asset->manager->env;
+
+ (*env)->CallVoidMethod (asset->manager->env,
+ asset->fd,
+ asset->manager->close);
+ (*env)->DeleteGlobalRef (asset->manager->env,
+ asset->fd);
+
+ if (asset->stream)
+ {
+ (*env)->CallVoidMethod (asset->manager->env,
+ asset->stream,
+ asset->manager->stream_close);
+ (*env)->DeleteGlobalRef (asset->manager->env,
+ asset->stream);
+ }
+
+ free (asset);
+}
+
+/* Create an input stream associated with the given ASSET. Set
+ ASSET->stream to its global reference.
+
+ Value is 1 upon failure, else 0. ASSET must not already have an
+ input stream. */
+
+static int
+android_asset_create_stream (AAsset *asset)
+{
+ jobject stream;
+ JNIEnv *env;
+
+ env = asset->manager->env;
+ stream
+ = (*env)->CallObjectMethod (env, asset->fd,
+ asset->manager->create_input_stream);
+
+ if (!stream)
+ {
+ (*env)->ExceptionClear (env);
+ return 1;
+ }
+
+ asset->stream
+ = (*env)->NewGlobalRef (env, stream);
+
+ if (!asset->stream)
+ {
+ (*env)->ExceptionClear (env);
+ (*env)->DeleteLocalRef (env, stream);
+ return 1;
+ }
+
+ (*env)->DeleteLocalRef (env, stream);
+ return 0;
+}
+
+/* Read NBYTES from the specified asset into the given BUFFER;
+
+ Internally, allocate a Java byte array containing 4096 elements and
+ copy the data to and from that array.
+
+ Value is the number of bytes actually read, 0 at EOF, or -1 upon
+ failure, in which case errno is set accordingly. If NBYTES is
+ zero, behavior is undefined. */
+
+static int
+android_asset_read_internal (AAsset *asset, int nbytes, char *buffer)
+{
+ jbyteArray stash;
+ JNIEnv *env;
+ jint bytes_read, total;
+
+ /* Allocate a suitable amount of storage. Either nbytes or 4096,
+ whichever is larger. */
+ env = asset->manager->env;
+ stash = (*env)->NewByteArray (env, MIN (nbytes, 4096));
+
+ if (!stash)
+ {
+ (*env)->ExceptionClear (env);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Try to create an input stream. */
+
+ if (!asset->stream
+ && android_asset_create_stream (asset))
+ {
+ (*env)->DeleteLocalRef (env, stash);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Start reading. */
+
+ total = 0;
+
+ while (nbytes)
+ {
+ bytes_read = (*env)->CallIntMethod (env, asset->stream,
+ asset->manager->read,
+ stash);
+
+ /* Detect error conditions. */
+
+ if ((*env)->ExceptionCheck (env))
+ goto out;
+
+ /* Detect EOF. */
+
+ if (bytes_read == -1)
+ goto out;
+
+ /* Finally write out the amount that was read. */
+ bytes_read = MIN (bytes_read, nbytes);
+ (*env)->GetByteArrayRegion (env, stash, 0, bytes_read, buffer);
+
+ buffer += bytes_read;
+ total += bytes_read;
+ nbytes -= bytes_read;
+ }
+
+ /* Make sure the value of nbytes still makes sense. */
+ assert (nbytes >= 0);
+
+ out:
+ (*env)->ExceptionClear (env);
+ (*env)->DeleteLocalRef (env, stash);
+ return total;
+}
+
+static int
+AAsset_openFileDescriptor (AAsset *asset, off_t *out_start,
+ off_t *out_end)
+{
+ *out_start = 0;
+ *out_end = 0;
+ return -1;
+}
+
+static long
+AAsset_getLength (AAsset *asset)
+{
+ JNIEnv *env;
+
+ if (asset->length != -1)
+ return asset->length;
+
+ env = asset->manager->env;
+ asset->length
+ = (*env)->CallLongMethod (env, asset->fd,
+ asset->manager->get_length);
+ return asset->length;
+}
+
+static char *
+AAsset_getBuffer (AAsset *asset)
+{
+ long length;
+ char *buffer;
+
+ length = AAsset_getLength (asset);
+
+ if (!length)
+ return NULL;
+
+ buffer = malloc (length);
+
+ if (!buffer)
+ return NULL;
+
+ if (android_asset_read_internal (asset, length, buffer)
+ != length)
+ {
+ xfree (buffer);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+static size_t
+AAsset_read (AAsset *asset, void *buffer, size_t size)
+{
+ return android_asset_read_internal (asset, MIN (size, INT_MAX),
+ buffer);
+}
#include <sys/mman.h>
#include <sys/param.h>
+/* Old NDK versions lack MIN and MAX. */
+#include <minmax.h>
+
#include <assert.h>
#include <fingerprint.h>
#ifndef ANDROID_STUBIFY
+#if __ANDROID_API__ >= 9
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
+#else
+#include "android-asset.h"
+#endif
+
#include <android/bitmap.h>
#include <android/log.h>
char *
android_user_full_name (struct passwd *pw)
{
+#ifdef HAVE_STRUCT_PASSWD_PW_GECOS
if (!pw->pw_gecos)
return (char *) "Android user";
return pw->pw_gecos;
+#else
+ return "Android user";
+#endif
}
/* Given a real file name, return the part that describes its asset
static int
android_hack_asset_fd (AAsset *asset)
{
+#if __ANDROID_API__ < 9
+ int fd;
+ char filename[PATH_MAX];
+ size_t size;
+ void *mem;
+
+ /* Assets must be small enough to fit in size_t, if off_t is
+ larger. */
+ size = AAsset_getLength (asset);
+
+ /* Get an unlinked file descriptor from a file in the cache
+ directory, which is guaranteed to only be written to by Emacs.
+ Creating an asset file descriptor doesn't work on these old
+ Android versions. */
+
+ snprintf (filename, PATH_MAX, "%s/%s.%d",
+ android_cache_dir, "temp-unlinked",
+ getpid ());
+ fd = open (filename, O_CREAT | O_RDWR | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+
+ if (fd < 1)
+ return -1;
+
+ if (unlink (filename))
+ goto fail;
+
+ if (ftruncate (fd, size))
+ goto fail;
+
+ mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+ if (mem == MAP_FAILED)
+ {
+ __android_log_print (ANDROID_LOG_ERROR, __func__,
+ "mmap: %s", strerror (errno));
+ goto fail;
+ }
+
+ if (AAsset_read (asset, mem, size) != size)
+ {
+ /* Too little was read. Close the file descriptor and
+ report an error. */
+ __android_log_print (ANDROID_LOG_ERROR, __func__,
+ "AAsset_read: %s", strerror (errno));
+ goto fail;
+ }
+
+ munmap (mem, size);
+ return fd;
+
+ fail:
+ close (fd);
+ return -1;
+#else
int fd, rc;
unsigned char *mem;
size_t size;
/* Return anyway even if munmap fails. */
munmap (mem, size);
return fd;
+#endif
}
/* Read two bytes from FD and see if they are ``PK'', denoting ZIP
- archive compressed data.
+ archive compressed data. If FD is -1, return -1.
If they are not, rewind the file descriptor to offset 0.
{
char bytes[2];
+ if (fd == -1)
+ return -1;
+
if (read (fd, bytes, 2) != 2)
goto lseek_back;
send_process (proc, "\004", 1, Qnil);
else if (EQ (XPROCESS (proc)->type, Qserial))
{
-#ifndef WINDOWSNT
+#if !defined WINDOWSNT && defined HAVE_TCDRAIN
if (tcdrain (XPROCESS (proc)->outfd) != 0)
report_file_error ("Failed tcdrain", Qnil);
#endif /* not WINDOWSNT */
/* Array of directories to search for system fonts. */
static char *system_font_directories[] =
{
- "/system/fonts",
+ (char *) "/system/fonts",
/* This should be filled in by init_sfntfont_android. */
(char[PATH_MAX]) { },
};
/* Pick a character map and place it in *CMAP. */
fd = emacs_open (desc->path, O_RDONLY, 0);
- if (fd < 1)
+ if (fd < 0)
return;
font = sfnt_read_table_directory (fd);
{
Lisp_Object data;
enum text_conversion_operation operation;
- struct buffer *buffer;
+ struct buffer *buffer UNINIT;
struct window *w;
specpdl_ref count;
unsigned long token;