1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-21 06:55:39 +00:00

Fix build and running on Android 2.2

* INSTALL.android: Document that Android 2.2 is now supported,
with caveats.
* configure.ac (ANDROID_MIN_SDK, ANDROID_SDK_18_OR_EARLIER)
(SYSTEM_TYPE, ANDROID_STUBIFY, SIZEOF_LONG): Correctly detect
things missing on Android 2.2.
* java/Makefile.in (ANDROID_JAR, JARSIGNER_FLAGS):
* java/debug.sh (jdb, gdbserver, line):
* java/org/gnu/emacs/EmacsApplication.java (findDumpFile):
* java/org/gnu/emacs/EmacsService.java (onCreate):
* java/org/gnu/emacs/EmacsThread.java (EmacsThread, run): Run
parameter initialization on main thread.
* src/android-asset.h (struct android_asset_manager)
(struct android_asset, AAssetManager_fromJava, AAssetManager_open)
(AAsset_close, android_asset_create_stream)
(android_asset_read_internal, AAsset_openFileDescriptor)
(AAsset_getLength, AAsset_getBuffer, AAsset_read): New file.
* src/android.c (android_user_full_name, android_hack_asset_fd)
(android_check_compressed_file): Implement for Android 2.2.
* src/process.c (Fprocess_send_eof): Don't call tcdrain if
unavailable.
* src/sfntfont-android.c (system_font_directories): Fix compiler
warning.
* src/sfntfont.c (sfntfont_read_cmap): Correctly test rc of
emacs_open.
* src/textconv.c (handle_pending_conversion_events_1): Mark
buffer UNINIT.
This commit is contained in:
Po Lu 2023-02-17 16:27:00 +08:00
parent 759e6a24ab
commit 88afd96e36
13 changed files with 574 additions and 24 deletions

View File

@ -91,9 +91,18 @@ for, and the include directories specify the paths to the relevant
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

View File

@ -1044,11 +1044,18 @@ package will likely install on older systems but crash on startup.])
# 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
@ -2251,6 +2258,10 @@ AC_DEFINE_UNQUOTED([SYSTEM_TYPE], ["$SYSTEM_TYPE"],
[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
@ -2487,7 +2498,13 @@ for Android, but all API calls need to be stubbed out])
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
@ -5610,7 +5627,7 @@ OLD_LIBS=$LIBS
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 \

View File

@ -39,6 +39,7 @@ JARSIGNER_FLAGS =
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 \
@ -53,6 +54,16 @@ else
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
@ -192,7 +203,8 @@ emacs.apk-in: install_temp install_temp/assets/directory-tree \
# 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`; \

View File

@ -32,6 +32,7 @@ jdb_port=64013
jdb=no
attach_existing=no
gdbserver=
gdb=gdb
while [ $# -gt 0 ]; do
case "$1" in
@ -51,6 +52,7 @@ while [ $# -gt 0 ]; do
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"
@ -65,6 +67,10 @@ while [ $# -gt 0 ]; do
"--jdb" )
jdb=yes
;;
"--gdb" )
shift
gdb=$1
;;
"--gdbserver" )
shift
gdbserver=$1
@ -355,4 +361,4 @@ fi
# 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

View File

@ -49,6 +49,7 @@ public class EmacsApplication extends Application
for a file named ``emacs-<fingerprint>.pdmp'' and delete the
rest. */
filesDirectory = context.getFilesDir ();
allFiles = filesDirectory.listFiles (new FileFilter () {
@Override
public boolean

View File

@ -180,11 +180,11 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
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 ());
@ -210,13 +210,18 @@ invocation of app_process (through android-emacs) can
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)

View File

@ -28,11 +28,16 @@ public class EmacsThread extends Thread
/* 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
@ -46,6 +51,8 @@ public class EmacsThread extends Thread
else
args = new String[] { "libandroid-emacs.so", "-Q", };
paramsClosure.run ();
/* Run the native code now. */
EmacsNative.initEmacs (args, EmacsApplication.dumpFileName,
Build.VERSION.SDK_INT);

423
src/android-asset.h Normal file
View File

@ -0,0 +1,423 @@
/* 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);
}

View File

@ -32,6 +32,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <sys/mman.h>
#include <sys/param.h>
/* Old NDK versions lack MIN and MAX. */
#include <minmax.h>
#include <assert.h>
#include <fingerprint.h>
@ -49,8 +52,13 @@ bool android_init_gui;
#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>
@ -907,10 +915,14 @@ android_is_directory (const char *dir)
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
@ -1048,6 +1060,60 @@ android_file_access_p (const char *name, int amode)
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;
@ -1172,10 +1238,11 @@ android_hack_asset_fd (AAsset *asset)
/* 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.
@ -1187,6 +1254,9 @@ android_check_compressed_file (int fd)
{
char bytes[2];
if (fd == -1)
return -1;
if (read (fd, bytes, 2) != 2)
goto lseek_back;

View File

@ -7248,7 +7248,7 @@ process has been transmitted to the serial port. */)
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 */

View File

@ -51,7 +51,7 @@ struct sfntfont_android_scanline_buffer
/* 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]) { },
};

View File

@ -953,7 +953,7 @@ sfntfont_read_cmap (struct sfnt_font_desc *desc,
/* 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);

View File

@ -989,7 +989,7 @@ handle_pending_conversion_events_1 (struct frame *f,
{
Lisp_Object data;
enum text_conversion_operation operation;
struct buffer *buffer;
struct buffer *buffer UNINIT;
struct window *w;
specpdl_ref count;
unsigned long token;