From cfbc8a5dbcd362b69b37b4e6832ae4a31834364c Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 31 Dec 2022 18:04:18 +0800 Subject: Bring up the Android operating system and its window system * .dir-locals.el (c-mode): Add ANDROID_EXPORT noise macro. * .gitignore: Add new files to ignore. * Makefile.in: Adjust for Android. * admin/merge-gnulib: Add new warning. * configure.ac: Detect Android. Run cross-configuration for Android when appropriate. * etc/DEBUG: Document how to debug Emacs on Android. * java/AndroidManifest.xml: * java/Makefile.in: * java/README: * java/debug.sh: * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication): * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea): * java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine): * java/org/gnu/emacs/EmacsDrawPoint.java (EmacsDrawPoint): * java/org/gnu/emacs/EmacsDrawRectangle.java (EmacsDrawRectangle): * java/org/gnu/emacs/EmacsDrawable.java (EmacsDrawable): * java/org/gnu/emacs/EmacsFillPolygon.java (EmacsFillPolygon): * java/org/gnu/emacs/EmacsFillRectangle.java (EmacsFillRectangle): * java/org/gnu/emacs/EmacsFontDriver.java (EmacsFontDriver): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsHandleObject.java (EmacsHandleObject): * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsPaintQueue.java (EmacsPaintQueue): * java/org/gnu/emacs/EmacsPaintReq.java (EmacsPaintReq): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver): * java/org/gnu/emacs/EmacsService.java (class Holder) (EmacsService): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsThread.java (EmacsThread): * java/org/gnu/emacs/EmacsView.java (EmacsView): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): New files and classes. * lib-src/Makefile.in (srcdir): * lib/Makefile.in (VPATH): (HAVE_NATIVE_COMP): (libgnu_a_SOURCES): (DEPFLAGS): Configure correctly for cross-compiling. * lib/faccessat.c: * lib/fpending.c (__fpending): * lib/open.c: * lib/unistd.c (_GL_UNISTD_INLINE): Temporary adjustments to gnulib. * lisp/frame.el (display-graphic-p): (display-screens): (display-pixel-height): (display-pixel-width): (display-mm-height): (display-mm-width): (display-backing-store): (display-save-under): (display-planes): (display-color-cells): (display-visual-class): Adjust for new window system `android'. * lisp/image/wallpaper.el (x-open-connection): Add declaration. * lisp/loadup.el (featurep): Load up files for Android. * lisp/net/eww.el (eww-form-submit, eww-form-file) (eww-form-checkbox, eww-form-select): Adjust faces for android. * lisp/term/android-win.el: New file. * src/Makefile.in: Add new targets emacs.so and android-emacs, then adjust for cross compilation. * src/alloc.c (cleanup_vector): Clean up Android font entities as well. (garbage_collect): Mark androidterm. * src/android-emacs.c (main): * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags) (struct android_emacs_service, struct android_emacs_pixmap) (struct android_graphics_point, struct android_event_container) (struct android_event_queue, android_run_select_thread) (android_handle_sigusr1, android_init_events, android_pending) (android_next_event, android_write_event, android_select) (android_run_debug_thread, android_user_full_name) (android_get_asset_name, android_fstat, android_fstatat) (android_file_access_p, android_hack_asset_fd, android_open) (android_close, JNICALL, android_init_emacs_service) (android_init_emacs_pixmap, android_init_graphics_point) (MAX_HANDLE, struct android_handle_entry, android_alloc_id) (android_destroy_handle, android_resolve_handle) (android_resolve_handle2, android_change_window_attributes) (android_create_window, android_set_window_background) (android_destroy_window, android_init_android_rect_class) (android_init_emacs_gc_class, android_create_gc, android_free_gc) (android_change_gc, android_set_clip_rectangles) (android_reparent_window, android_lookup_method) (android_clear_window, android_map_window, android_unmap_window) (android_resize_window, android_move_window, android_swap_buffers) (android_get_gc_values, android_set_foreground) (android_fill_rectangle, android_create_pixmap_from_bitmap_data) (android_set_clip_mask, android_set_fill_style, android_copy_area) (android_free_pixmap, android_set_background, android_fill_polygon) (android_draw_rectangle, android_draw_point, android_draw_line) (android_create_pixmap, android_set_ts_origin, android_clear_area): * src/android.h (ANDROID_EXPORT): * src/androidfns.c (android_display_info_for_name) (check_android_display_info, check_x_display_info, gamma_correct) (android_defined_color, android_decode_color) (android_implicitly_set_name, android_explicitly_set_name) (android_set_tool_bar_lines, android_change_tool_bar_height) (android_set_tab_bar_lines, android_change_tab_bar_height) (android_set_scroll_bar_default_height) (android_set_scroll_bar_default_width, android_icon_verify) (android_icon, android_make_gc, android_free_gcs) (unwind_create_frame, do_unwind_create_frame) (android_default_font_parameter, android_create_frame_window) (Fx_create_frame, Fxw_color_defined_p, Fxw_color_values) (Fxw_display_color_p, Fx_display_grayscale_p) (Fx_display_pixel_width, Fx_display_pixel_height) (Fx_display_planes, Fx_display_color_cells, Fx_display_screens) (Fx_display_mm_width, Fx_display_mm_height) (Fx_display_backing_store, Fx_display_visual_class) (Fx_display_monitor_attributes_list, Fx_frame_geometry) (Fx_frame_list_z_order, Fx_frame_restack) (Fx_mouse_absolute_pixel_position) (Fx_set_mouse_absolute_pixel_position, Fandroid_get_connection) (Fx_display_list, Fx_show_tip, Fx_hide_tip) (android_set_background_color, android_set_border_color) (android_set_cursor_color, android_set_cursor_type) (android_set_foreground_color) (android_set_child_frame_border_width) (android_set_internal_border_width, android_set_menu_bar_lines) (android_set_mouse_color, android_set_title, android_set_alpha) (android_frame_parm_handlers, syms_of_androidfns): * src/androidfont.c (struct android_emacs_font_driver) (struct android_emacs_font_spec, struct android_emacs_font_metrics) (struct android_emacs_font_object, struct android_integer) (struct androidfont_info, struct androidfont_entity) (android_init_font_driver, android_init_font_spec) (android_init_font_metrics, android_init_integer) (android_init_font_object, androidfont_get_cache) (androidfont_from_lisp, androidfont_from_java, androidfont_list) (androidfont_match, androidfont_draw, androidfont_open_font) (androidfont_close_font, androidfont_has_char) (androidfont_encode_char, androidfont_text_extents) (androidfont_list_family, androidfont_driver) (syms_of_androidfont_for_pdumper, syms_of_androidfont) (init_androidfont, android_finalize_font_entity): * src/androidgui.h (_ANDROID_GUI_H_, struct android_rectangle) (struct android_point, enum android_gc_function) (enum android_gc_value_mask, enum android_fill_style) (enum android_window_value_mask) (struct android_set_window_attributes, struct android_gc_values) (struct android_gc, enum android_swap_action, enum android_shape) (enum android_coord_mode, struct android_swap_info) (NativeRectangle, struct android_any_event) (struct android_key_event, struct android_configure_event) (union android_event): * src/androidterm.c (android_window_to_frame, android_clear_frame) (android_ring_bell, android_toggle_invisible_pointer) (android_update_begin, android_update_end, show_back_buffer) (android_flush_dirty_back_buffer_on, handle_one_android_event) (android_read_socket, android_frame_up_to_date) (android_buffer_flipping_unblocked_hook) (android_query_frame_background_color, android_parse_color) (android_alloc_nearest_color, android_query_colors) (android_mouse_position, android_get_focus_frame) (android_focus_frame, android_frame_rehighlight) (android_frame_raise_lower, android_make_frame_visible) (android_make_frame_invisible) (android_make_frame_visible_invisible, android_fullscreen_hook) (android_iconify_frame, android_set_window_size_1) (android_set_window_size, android_set_offset, android_set_alpha) (android_new_font, android_bitmap_icon, android_free_pixmap_hook) (android_free_frame_resources, android_delete_frame) (android_delete_terminal, android_scroll_run) (android_after_update_window_line, android_flip_and_flush) (android_clear_rectangle, android_reset_clip_rectangles) (android_clip_to_row, android_draw_fringe_bitmap) (android_set_cursor_gc, android_set_mouse_face_gc) (android_set_mode_line_face_gc, android_set_glyph_string_gc) (android_set_glyph_string_clipping) (android_set_glyph_string_clipping_exactly) (android_compute_glyph_string_overhangs) (android_clear_glyph_string_rect) (android_draw_glyph_string_background, android_fill_triangle) (android_make_point, android_inside_rect_p, android_clear_point) (android_draw_relief_rect, android_draw_box_rect) (HIGHLIGHT_COLOR_DARK_BOOST_LIMIT, android_setup_relief_color) (android_setup_relief_colors, android_draw_glyph_string_box) (android_draw_glyph_string_bg_rect, android_draw_image_relief) (android_draw_image_foreground, android_draw_image_foreground_1) (android_draw_image_glyph_string) (android_draw_stretch_glyph_string, android_draw_underwave) (android_draw_glyph_string_foreground) (android_draw_composite_glyph_string_foreground) (android_draw_glyphless_glyph_string_foreground) (android_draw_glyph_string, android_define_frame_cursor) (android_clear_frame_area, android_clear_under_internal_border) (android_draw_hollow_cursor, android_draw_bar_cursor) (android_draw_window_cursor, android_draw_vertical_window_border) (android_draw_window_divider, android_redisplay_interface) (frame_set_mouse_pixel_position, get_keysym_name) (android_create_terminal, android_term_init, syms_of_androidterm) (mark_androidterm): * src/androidterm.h (_ANDROID_TERM_H_, struct android_display_info) (struct android_output, FRAME_ANDROID_OUTPUT, XSCROLL_BAR): New files. * src/dired.c (file_attributes): Do not use openat on Android. * src/dispextern.h (No_Cursor): Define appropriately on Android. (struct glyph_string, struct face): Make gc field of type struct android_gc on Android. * src/dispnew.c (clear_current_matrices, clear_desired_matrices) (adjust_frame_glyphs_for_window_redisplay, free_glyphs) (update_frame, scrolling, char_ins_del_cost, update_frame_line) (init_display_interactive): Disable text terminal support completely on Android. Fix non-toolkit menus for non-X systems. * src/editfns.c (Fuser_full_name): Call android_user_full_name. * src/emacs.c (android_emacs_init): Make main this on Android. Prohibit argv sorting from exceeding end of argv. * src/epaths.in: Add path definitions for Android. * src/fileio.c (file_access_p): Call android_file_access_p. (file_name_directory): Avoid using openat on Android. (Fcopy_file): Adjust to call sys_fstat instead. (file_directory_p): (Finsert_file_contents): (write_region): Likewise. * src/filelock.c: * src/fns.c (Flocale_info): Pacify warning on Android. * src/font.c (font_make_entity_android): New function. * src/font.h: * src/frame.c (Fframep): (Fwindow_system): Handle new window system `android'. Update doc strings. (Fmake_terminal_frame): Disable on Android. (gui_display_get_resource): Disable get_string_resource_hook on Android. (syms_of_frame): New defsym `android'. * src/frame.h (GCALIGNED_STRUCT): Add new output data for Android. (ENUM_BF): Expand enumerator size. (FRAME_ANDROID_P, FRAME_WINDOW_P, MOUSE_HL_INFO): Add definitions for Android. * src/image.c (GET_PIXEL): (image_create_bitmap_from_file): (image_create_x_image_and_pixmap_1): (image_get_x_image): (slurp_file): (lookup_rgb_color): (image_to_emacs_colors): (image_from_emacs_colors): (image_pixmap_draw_cross): (image_disable_image): (MaskForeground): (gif_load): Add stubs for Android. * src/lisp.h: * src/lread.c (safe_to_load_version, maybe_swap_for_eln1, openp): * src/pdumper.c (pdumper_load): Call sys_fstat instead of fstat. * src/process.c (wait_reading_process_output): Use android_select instead of pselect. * src/scroll.c: Disable on Android. * src/sysdep.c (widen_foreground_group, reset_sys_modes) (init_signals, emacs_fstatat, sys_fstat): New function. (emacs_open, emacs_open_noquit, emacs_close): Implement differently on Android. (close_output_streams): Disable what is not required on Android. * src/term.c (OUTPUT1_IF, encode_terminal_code, string_cost) (string_cost_one_line, per_line_cost, calculate_costs) (struct fkey_table, tty_append_glyph, produce_glyphs) (tty_capable_p, Fsuspend_tty, Fresume_tty, device, init_tty) (maybe_fatal, syms_of_term): Disable text terminal support on Android. * src/termhooks.h (enum output_method): Add android output method. (GCALIGNED_STRUCT, TERMINAL_FONT_CACHE): Define for Android. * src/terminal.c (Fterminal_live_p): Implement for Android. * src/verbose.mk.in (AM_V_GLOBALS): Add JAVAC and DX. * src/xdisp.c (redisplay_internal): Disable text terminals on Android. (display_menu_bar): (display_tty_menu_item): (draw_row_with_mouse_face): (expose_frame): Make the non toolkit menu bar work on Android. * src/xfaces.c (GCGraphicsExposures): (x_create_gc): (x_free_gc): (Fx_load_color_file): Define for Android. * xcompile/Makefile.in (top_srcdir): (top_builddir): * xcompile/README: * xcompile/langinfo.h (nl_langinfo): New files. --- java/org/gnu/emacs/EmacsThread.java | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 java/org/gnu/emacs/EmacsThread.java (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java new file mode 100644 index 00000000000..e9281a13391 --- /dev/null +++ b/java/org/gnu/emacs/EmacsThread.java @@ -0,0 +1,44 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +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 . */ + +package org.gnu.emacs; + +import java.lang.Thread; + +public class EmacsThread extends Thread +{ + EmacsService context; + + public + EmacsThread (EmacsService service) + { + context = service; + } + + public void + run () + { + String args[]; + + args = new String[] { "android-emacs", }; + + /* Run the native code now. */ + EmacsNative.initEmacs (args); + } +}; -- cgit v1.3 From 28a9baccd4c8e997895d3adb3cfce4a11fa29896 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 14 Jan 2023 09:34:53 +0800 Subject: Improve reliability of Android build system * .gitignore: Add new files. * INSTALL.android: New file. * Makefile.in (clean_dirs): Clean xcompile as well. * admin/merge-gnulib (avoided_flags): Import gnulib into Android directory as well. * doc/emacs/android.texi (Android): * doc/emacs/emacs.texi (Top): New node `Android'. * java/org/gnu/emacs/EmacsThread.java (run): Use right executable name. * lib/Makefile.in (ANDROID_CFLAGS): Use better way to refer to /src. (vpath): Delete ugly block of vpath statements. (mostlyclean): Remove Makefile.android. * lib/fpending.c (__fpending): * lib/open.c: * lib/unistd.c (_GL_UNISTD_INLINE): Revert changes to gnulib in lib/. * src/android.h: * src/androidterm.c: Fix build. * xcompile/Makefile.in (LIB_SRCDIR): (LIBSRC_BINARIES, src/verbose.mk): (PRE_BUILD_DEPS, PHONY): Use gnulib in xcompile/lib/ as opposed to lib/. * xcompile/README: Adjust README. --- .gitignore | 27 +++- INSTALL.android | 76 +++++++++ Makefile.in | 3 +- admin/merge-gnulib | 11 +- doc/emacs/android.texi | 297 ++++++++++++++++++++++++++++++++++++ doc/emacs/emacs.texi | 8 + java/org/gnu/emacs/EmacsThread.java | 2 +- lib/Makefile.in | 121 +-------------- lib/fpending.c | 6 +- lib/open.c | 4 - lib/unistd.c | 2 +- src/android.h | 1 + src/androidterm.c | 2 +- xcompile/Makefile.in | 96 ++++-------- xcompile/README | 8 +- 15 files changed, 458 insertions(+), 206 deletions(-) create mode 100644 INSTALL.android create mode 100644 doc/emacs/android.texi (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/.gitignore b/.gitignore index 19eb587673b..cf739958403 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ src/emacs-module.h # Built by recursive call to `configure'. *.android +!INSTALL.android # Built by `java'. java/install_temp/* @@ -94,7 +95,31 @@ src/lisp.mk src/verbose.mk # Stuff built during cross compilation -xcompile/lib/* +xcompile/lib/alloca.h +xcompile/lib/assert.h +xcompile/lib/byteswap.h +xcompile/lib/dirent.h +xcompile/lib/errno.h +xcompile/lib/execinfo.h +xcompile/lib/fcntl.h +xcompile/lib/getopt.h +xcompile/lib/getopt-cdefs.h +xcompile/lib/gmp.h +xcompile/lib/ieee754.h +xcompile/lib/inttypes.h +xcompile/lib/libgnu.a +xcompile/lib/limits.h +xcompile/lib/malloc/*.gl.h +xcompile/lib/signal.h +xcompile/lib/std*.h +xcompile/!lib/std*.in.h +xcompile/!lib/stdio-impl.h +xcompile/lib/string.h +xcompile/lib/sys/ +xcompile/lib/time.h +xcompile/lib/unistd.h +xcompile/lib/config.h +xcompile/lib/gnulib.mk xcompile/src/* xcompile/lib-src/* xcompile/sys/* diff --git a/INSTALL.android b/INSTALL.android new file mode 100644 index 00000000000..ee54b053a9b --- /dev/null +++ b/INSTALL.android @@ -0,0 +1,76 @@ +Installation instructions for Android +Copyright (C) 2023 Free Software Foundation, Inc. +See the end of the file for license conditions. + +Please read the entirety of this file before attempting to build Emacs +as an application package which can run on Android devices. + +When building from the source repository, make sure to read +INSTALL.REPO as well. + + + +Android is an unusual operating system in that program binaries cannot +be produced on computers running Android themselves. Instead, they +must be built on some other computer using a set of tools known as the +``Android SDK'' (Software Development Kit) and the ``Android NDK'' +(Native Development Kit). Appropriate versions of both must be +obtained to build GNU Emacs; after being built, the generated binaries +will work on almost all Android devices. This document does not +elaborate on how both sets of tools can be obtained. However, for +your freedom's sake, you should use the Android SDK provided by the +Debian project. + +In addition to the Android SDK and Android NDK, Emacs also requires +the Java compiler from OpenJDK 1.7.0 to be installed on your system. + +Once all of those tools are obtained, you may invoke the `configure' +script like so: + + ./configure --with-android=/path/to/android.jar \ + ANDROID_CC=/path/to/android/ndk/cc \ + SDK_BUILD_TOOLS=/path/to/sdk/build/tools + +Replacing the paths in the command line above with: + + - the path to the `android.jar' headers which come with the Android + SDK. They must correspond to Android version 13 or later. + + - the path to the C compiler in the Android NDK, for the machine you + are building Emacs to run on. + + - the path to the directory in the Android SDK containing binaries + such as `aapt' and `d8'. These are used to build the application + package. + +After the configuration process completes, you may run: + + make all + +Once `make' finishes, there should be a file in the `java' directory +named along the lines of: + + emacs---.apk + +where is the oldest version of Android that the package +will run on, and is the type of Android machine the package was +built for. + +The generated package can be uploaded onto an SD card (or similar +medium) and installed on-device. + + +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 . diff --git a/Makefile.in b/Makefile.in index 6cf2781a6ab..0090b858228 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1012,7 +1012,8 @@ mostlyclean: $(mostlyclean_dirs:=_mostlyclean) ### with them. ### ### Delete '.dvi' files here if they are not part of the distribution. -clean_dirs = $(mostlyclean_dirs) java nextstep admin/charsets admin/unidata +clean_dirs = $(mostlyclean_dirs) java xcompile nextstep admin/charsets \ + admin/unidata $(foreach dir,$(clean_dirs),$(eval $(call submake_template,$(dir),clean))) diff --git a/admin/merge-gnulib b/admin/merge-gnulib index e17ecd95ac3..b99e38672a4 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -114,6 +114,11 @@ for module in $AVOIDED_MODULES; do avoided_flags="$avoided_flags --avoid=$module" done +# Clean the lib directory as well. +if [ -e "$src"/lib/Makefile ]; then + make -C "$src"/lib maintainer-clean +fi + "$gnulib_srcdir"/gnulib-tool --dir="$src" $GNULIB_TOOL_FLAGS \ $avoided_flags $GNULIB_MODULES && rm -- "$src"lib/gl_openssl.h \ @@ -135,4 +140,8 @@ cp -- "$gnulib_srcdir"/lib/af_alg.h \ { test -z "$src" || cd "$src"; } && ./autogen.sh -echo "Please update the block of vpath statements in lib/Makefile.in" +# Finally, update the files in lib/ to xcompile/lib. +rsync "$src"/lib "$src"/xcompile + +# Remove unnecessary files. +rm -f "$src"/xcompile/lib/*.mk.in "$src"/xcompile/lib/Makefile.in diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi new file mode 100644 index 00000000000..57ef10de9f3 --- /dev/null +++ b/doc/emacs/android.texi @@ -0,0 +1,297 @@ +@c This is part of the Emacs manual. +@c Copyright (C) 2021--2023 Free Software Foundation, Inc. +@c See file emacs.texi for copying conditions. +@node Android +@appendix Emacs and Android +@cindex Android + + Android is a mobile operating system developed by the Open Handset +Alliance. This section describes the peculiarities of using Emacs on +an Android device running Android 2.2 or later. + +@menu +* What is Android?:: Preamble. +* Android Startup:: Starting up Emacs on Android. +* Android File System:: The Android file system. +* Android Environment:: Running Emacs under Android. +@end menu + +@node What is Android? +@section Android history + + Android is an operating system for mobile devices developed by the +Open Handset Alliance, a group of companies interested in developing +handsets that can run a common set of software. It is supposedly free +software. + + Like the X Consortium of times past, the Open Handset Alliance +believes that ``openness'' (namely, the regular release of the Android +source code) is simply a tool to increase the popularity of the +Android platform. Computer companies normally produce proprietary +software. The companies in the Open Handset Alliance are no different +-- most versions of Android installed on devices are proprietary, by +virtue of containing proprietary components, that often cannot even be +replaced by the user. + + Android is not designed to respect users' freedom. Almost all +versions of Android (including some which are supposedly free +software) include support for Digital Restrictions Management, +technology that is designed to limit users' ability to copy media to +and from their own devices. Most Android devices also come with +proprietary Google applications which are required to run the system, +and many other Android applications. + + Thus, it must be necessary to consider Android proprietary software +from a practical standpoint. That is an injustice. If you use +Android, we urge you to switch to a free operating system, if only for +your freedom's sake. + + We support GNU Emacs on proprietary operating systems because we +hope this taste of freedom will inspire users to escape from them. + +@node Android Startup +@section Starting up Emacs on Android + + Emacs is not installed on Android devices from source code, or by a +package manager. Instead, Emacs is compiled for Android on a +different operating system, with the resulting binaries packaged into +an archive, that is then transferred to the device and unpacked. + +After being unpacked, Emacs instructs the system to display an +application icon on the desktop. Emacs then starts up once the +application icon is clicked. + +@cindex ``adb logcat'' + +During startup, Emacs will display messages in the system log buffer; +reading that buffer requires the Android Debug Bridge (@code{adb}) +utility to be installed on another computer; it cannot be read on the +computer running Android itself. + +After enabling the ``USB Debugging'' feature on the Android system, +and connecting it via USB to another system with the @code{adb} +utility installed, the log can be viewed by running the following +command on that other system: + +@example +$ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)" +@end example + +@node Android File System +@section What files Emacs can access under Android +@cindex /assets directory, android + + Emacs exposes a special directory on Android systems: the name of +the directory is @file{/assets}, and it contains the @file{etc}, +@file{lisp} and @file{info} directories which are normally installed +in @file{/usr/share/emacs} directory on GNU and Unix systems. + +@cindex limitations of the /assets directory + +This directory exists because Android does not extract the contents of +application packages on to the file system while unpacking them, but +instead requires programs like Emacs to access its contents using a +special ``asset manager'' interface. Here are the peculiarities that +result from such an implementation: + +@itemize @bullet +@item +Subprocesses (such as @command{ls}) can not run from the +@file{/assets} directory, so Dired, and functions such as +@code{list-directory} do not work. + +@item +There are no @file{.} and @file{..} directories inside the +@file{/assets} directory. + +@item +Files in the @file{/assets} directory are always read only, and have +to be completely read in to memory each time they are opened. + +@item +@code{directory-files} does not return a useful value on the +@file{/assets} directory itself, and does not return subdirectories +inside subdirectories of the @file{/assets} directory. +@end itemize + + Aside from the @file{/assets} directory, Android programs normally +have access to three other directories. They are: + +@itemize @bullet +@item +The @dfn{app data} directory. This also serves as the home directory +for Emacs, and is always accessible read-write. + +@item +The @dfn{app library} directory. This is automatically appended to +@code{exec-path} upon startup. + +@item +The @dfn{external storage} directory. This is accessible to Emacs +when the user grants the @code{Files and media} permission to Emacs +via system settings. +@end itemize + +The external storage directory is found at @file{/sdcard}; the other +directories are not found at any fixed location. + +@node Android Environment +@section Running Emacs under Android + + From the perspective of users, Android is mostly a single user +operating system; however, from the perspective of applications and +Emacs, the system has an overwhelming number of users! + + Each application runs in its own user, with his own home directory, +which is the app data directory (@pxref{Android File System}.) + + Each application is also prohibited from accessing system +directories, and the app data directories of other applications. In +recent versions of Android, the system also prohibits, for security +reasons, even Emacs itself from running executables inside the app +data directory! + + Emacs comes with several binaries. While being executable files, +they are packaged as libraries in the library directory, because +otherwise the system will not unpack them while Emacs is being +installed. This means, instead of specifying @code{ctags} or +@code{emacsclient} in a subprocess, Lisp code must specify +@code{libctags.so} or @code{libemacsclient.so} on the commnd line +instead when starting either of those programs in a subprocess. + +@c TODO: remove this limitation. + In addition, the @file{/assets} directory containing Emacs start-up +files is inaccessible to processes not directly created by +@code{zygote}, the system service responsible for starting +applications. This makes it impossible to run Emacs in a subprocess +within itself. + +@section Running Emacs in the background +@cindex emacs killed, android +@cindex emacs in the background, android + + Application processes are treated as disposable entities by the +system. When all Emacs frames move to the background, Emacs is liable +to be killed by the system at any time, for the purpose of saving +resources. There is currently no easy way to bypass these +restrictions, aside from keeping Emacs constantly running in the +foreground. + +@section Android permissions +@cindex external storage, android + + Android also defines a permissions system that determines what +system services Emacs is allowed to access. Programs must specify +what permissions they want; what then happens depends on the version +of Android being used: + +@itemize @bullet +@item +On Android 5.1 and earlier, Emacs automatically receives the following +permissions it has requested upon being installed: + +@itemize @minus +@item +@code{android.permission.READ_CONTACTS} +@item +@code{android.permission.WRITE_CONTACTS} +@item +@code{android.permission.VIBRATE} +@item +@code{android.permission.ACCESS_COARSE_LOCATION} +@item +@code{android.permission.INTERNET} +@item +@code{android.permission.SET_WALLPAPER} +@item +@code{android.permission.WRITE_EXTERNAL_STORAGE} +@item +@code{android.permission.SEND_SMS} +@item +@code{android.permission.RECEIVE_SMS} +@item +@code{android.permission.RECEIVE_MMS} +@item +@code{android.permission.WRITE_SMS} +@item +@code{android.permission.READ_SMS} +@item +@code{android.permission.NFC} +@item +@code{android.permission.TRANSMIT_IR} +@item +@code{android.permission.READ_PHONE_STATE} +@item +@code{android.permission.WAKE_LOCK} +@item +@code{android.permission.FOREGROUND_SEVICE} +@item +@code{android.permission.REQUEST_INSTALL_PACKAGES} +@item +@code{android.permission.REQUEST_DELETE_PACKAGES} +@item +@code{android.permission.SYSTEM_ALERT_WINDOW} +@item +@code{android.permission.RECORD_AUDIO} +@item +@code{android.permission.CAMERA} +@end itemize + +While most of these permissions are left unused by Emacs itself, they +are declared by Emacs as they could be useful for other programs; for +example, the permission to access contacts may be useful for EUDC. + +@item +On Android 6.0 and later, Emacs only receives the following +permissions upon installation: + +@itemize @minus +@item +@code{android.permission.VIBRATE} +@item +@code{android.permission.INTERNET} +@item +@code{android.permission.SET_WALLPAPER} +@item +@code{android.permission.NFC} +@item +@code{android.permission.TRANSMIT_IR} +@item +@code{android.permission.WAKE_LOCK} +@end itemize + +Other permissions must be granted by the user through the system +settings application. Consult the manufacturer of your device for +more details, as how to do this varies by device. +@end itemize + +@section Android windowing + + Android has an unusual window system; there, all windows are +maximized or full-screen, and only one window can be displayed at a +time. On larger devices, the system allows up to four windows to be +tiled on the screen at any time. + +Windows on Android do not continue to exist indefinitely after they +are created. Instead, the system may choose to terminate windows that +are not on screen in order to save memory, with the assumption that +the program will save its contents to disk and restore them later, +when the user asks to open it again. As this is obvious not possible +with Emacs, Emacs separates a frame from a system window. + +Each system window created (including the initial window created +during Emacs startup) is appended to a list of windows that do not +have associated frames. When a frame is created, Emacs looks up any +window within that list, and displays the contents of the frame +within; if there is no window at all, then one is created. Likewise, +when a new window is created by the system, Emacs places the contents +of any frame that is not already displayed within a window inside. +When a frame is closed, the corresponding system window is also +closed. + +This strategy works as long as one window is in the foreground. +Otherwise, Emacs can only run in the background for a limited amount +of time before the process is killed completely. + +@c TODO: write more documentation here about what is supported and +@c what is not, and fonts. diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index b6d149eb3ef..19a99d1deb2 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -1256,6 +1256,13 @@ Emacs and Haiku * Haiku Basics:: Basic Emacs usage and installation under Haiku. * Haiku Fonts:: The various options for displaying fonts on Haiku. +Emacs and Android + +* What is Android?:: Preamble. +* Android Startup:: Starting up Emacs on Android. +* Android File System:: The Android file system. +* Android Environment:: Running Emacs under Android. + Emacs and Microsoft Windows/MS-DOS * Windows Startup:: How to start Emacs on Windows. @@ -1626,6 +1633,7 @@ Lisp programming. @include anti.texi @include macos.texi @include haiku.texi +@include android.texi @c Includes msdos-xtra. @include msdos.texi @include gnu.texi diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index e9281a13391..0882753747f 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -36,7 +36,7 @@ public class EmacsThread extends Thread { String args[]; - args = new String[] { "android-emacs", }; + args = new String[] { "libandroid-emacs.so", }; /* Run the native code now. */ EmacsNative.initEmacs (args); diff --git a/lib/Makefile.in b/lib/Makefile.in index 87c13499965..fd730b554e3 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -29,121 +29,6 @@ XCONFIGURE = @XCONFIGURE@ # counterparts. ANDROID_CFLAGS = @ANDROID_CFLAGS@ -ifneq ($(XCONFIGURE),) - -# Set vpath. Only look for C files and some headers in srcdir: -# Headers that are generated by gnulib must be spared, or otherwise -# the versions previously built on the host will be used, if builddir -# is the same as srcdir. Following this is a list of files in lib/ -# that are not generated during the gnulib build process. Please keep -# it up to date! - -vpath _Noreturn.h $(srcdir) -vpath acl-internal.h $(srcdir) -vpath acl.h $(srcdir) -vpath af_alg.h $(srcdir) -vpath alloca.in.h $(srcdir) -vpath allocator.h $(srcdir) -vpath arg-nonnull.h $(srcdir) -vpath assert.in.h $(srcdir) -vpath attribute.h $(srcdir) -vpath binary-io.h $(srcdir) -vpath byteswap.in.h $(srcdir) -vpath c++defs.h $(srcdir) -vpath c-ctype.h $(srcdir) -vpath c-strcase.h $(srcdir) -vpath careadlinkat.h $(srcdir) -vpath cdefs.h $(srcdir) -vpath cloexec.h $(srcdir) -vpath close-stream.h $(srcdir) -vpath count-leading-zeros.h $(srcdir) -vpath count-one-bits.h $(srcdir) -vpath count-trailing-zeros.h $(srcdir) -vpath diffseq.h $(srcdir) -vpath dirent.h $(srcdir) -vpath dirent.in.h $(srcdir) -vpath dynarray.h $(srcdir) -vpath eloop-threshold.h $(srcdir) -vpath errno.in.h $(srcdir) -vpath execinfo.in.h $(srcdir) -vpath fcntl.in.h $(srcdir) -vpath filemode.h $(srcdir) -vpath filename.h $(srcdir) -vpath filevercmp.h $(srcdir) -vpath fingerprint.h $(srcdir) -vpath flexmember.h $(srcdir) -vpath fpending.h $(srcdir) -vpath fsusage.h $(srcdir) -vpath ftoastr.h $(srcdir) -vpath getopt-cdefs.in.h $(srcdir) -vpath getopt-core.h $(srcdir) -vpath getopt-ext.h $(srcdir) -vpath getopt-pfx-core.h $(srcdir) -vpath getopt-pfx-ext.h $(srcdir) -vpath getopt.in.h $(srcdir) -vpath getopt_int.h $(srcdir) -vpath gettext.h $(srcdir) -vpath idx.h $(srcdir) -vpath ieee754.in.h $(srcdir) -vpath ignore-value.h $(srcdir) -vpath intprops-internal.h $(srcdir) -vpath intprops.h $(srcdir) -vpath inttypes.in.h $(srcdir) -vpath libc-config.h $(srcdir) -vpath limits.in.h $(srcdir) -vpath malloc/dynarray.h $(srcdir) -vpath malloc/scratch_buffer.h $(srcdir) -vpath md5.h $(srcdir) -vpath min-max.h $(srcdir) -vpath mini-gmp.h $(srcdir) -vpath minmax.h $(srcdir) -vpath mktime-internal.h $(srcdir) -vpath nproc.h $(srcdir) -vpath openat-priv.h $(srcdir) -vpath openat.h $(srcdir) -vpath pathmax.h $(srcdir) -vpath regex.h $(srcdir) -vpath regex_internal.h $(srcdir) -vpath root-uid.h $(srcdir) -vpath save-cwd.h $(srcdir) -vpath scratch_buffer.h $(srcdir) -vpath sha1.h $(srcdir) -vpath sha256.h $(srcdir) -vpath sha512.h $(srcdir) -vpath sig2str.h $(srcdir) -vpath signal.in.h $(srcdir) -vpath stat-time.h $(srcdir) -vpath stdalign.in.h $(srcdir) -vpath stdckdint.in.h $(srcdir) -vpath stddef.in.h $(srcdir) -vpath stdint.in.h $(srcdir) -vpath stdio-impl.h $(srcdir) -vpath stdio.h $(srcdir) -vpath stdio.in.h $(srcdir) -vpath stdlib.in.h $(srcdir) -vpath str-two-way.h $(srcdir) -vpath strftime.h $(srcdir) -vpath string.in.h $(srcdir) -vpath sys_random.in.h $(srcdir) -vpath sys_select.in.h $(srcdir) -vpath sys_stat.in.h $(srcdir) -vpath sys_time.in.h $(srcdir) -vpath sys_types.in.h $(srcdir) -vpath tempname.h $(srcdir) -vpath time-internal.h $(srcdir) -vpath time.in.h $(srcdir) -vpath timespec.h $(srcdir) -vpath u64.h $(srcdir) -vpath unistd.in.h $(srcdir) -vpath unlocked-io.h $(srcdir) -vpath utimens.h $(srcdir) -vpath verify.h $(srcdir) -vpath vla.h $(srcdir) -vpath warn-on-use.h $(srcdir) -vpath xalloc-oversized.h $(srcdir) -vpath %.c $(srcdir) -endif - # Variables substituted by 'configure', and not autogenerated in gnulib.mk, # or needed before gnulib.mk is included. abs_top_srcdir = @abs_top_srcdir@ @@ -160,7 +45,7 @@ HAVE_NATIVE_COMP = @HAVE_NATIVE_COMP@ ALL_CFLAGS = \ $(C_SWITCH_SYSTEM) $(C_SWITCH_MACHINE) $(DEPFLAGS) \ $(GNULIB_WARN_CFLAGS) $(WERROR_CFLAGS) $(PROFILING_CFLAGS) $(CFLAGS) \ - -I. -I../src -I$(srcdir) -I$(srcdir)/../src \ + -I. -I../src -I$(srcdir) -I$(top_srcdir)/src \ $(if $(patsubst e-%,,$(notdir $<)),,-Demacs) $(ANDROID_CFLAGS) ifeq ($(HAVE_NATIVE_COMP),yes) @@ -244,10 +129,10 @@ clean: mostlyclean: clean rm -f $(filter-out %-t,$(MOSTLYCLEANFILES)) distclean bootstrap-clean: mostlyclean - rm -f Makefile + rm -f Makefile Makefile.android rm -fr $(DEPDIR) maintainer-clean: distclean - rm -f TAGS gnulib.mk + rm -f TAGS gnulib.mk gnulib.mk.android -rmdir malloc sys 2>/dev/null || true .PHONY: mostlyclean clean distclean bootstrap-clean maintainer-clean diff --git a/lib/fpending.c b/lib/fpending.c index 62716b90965..afa840b8512 100644 --- a/lib/fpending.c +++ b/lib/fpending.c @@ -33,15 +33,13 @@ size_t __fpending (FILE *fp) { -#if defined __ANDROID__ - return 0; /* Most systems provide FILE as a struct and the necessary bitmask in , because they need it for implementing getc() and putc() as fast macros. */ -#elif defined _IO_EOF_SEEN || defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 +#if defined _IO_EOF_SEEN || defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */ return fp->_IO_write_ptr - fp->_IO_write_base; -#elif defined __sferror || defined __DragonFly__ +#elif defined __sferror || defined __DragonFly__ || defined __ANDROID__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin < 1.7.34, Minix 3, Android */ return fp->_p - fp->_bf._base; #elif defined __EMX__ /* emx+gcc */ diff --git a/lib/open.c b/lib/open.c index 5c95d2b8c3c..7ec8fdc35dc 100644 --- a/lib/open.c +++ b/lib/open.c @@ -40,11 +40,7 @@ orig_open (const char *filename, int flags, mode_t mode) /* Specification. */ /* Write "fcntl.h" here, not , otherwise OSF/1 5.1 DTK cc eliminates this include because of the preliminary #include above. */ -#ifdef __ANDROID__ -#include -#else #include "fcntl.h" -#endif #include "cloexec.h" diff --git a/lib/unistd.c b/lib/unistd.c index 98bdb4e265d..be7a8255033 100644 --- a/lib/unistd.c +++ b/lib/unistd.c @@ -18,5 +18,5 @@ #include #define _GL_UNISTD_INLINE _GL_EXTERN_INLINE -#include +#include "unistd.h" typedef int dummy; diff --git a/src/android.h b/src/android.h index cddd315b9a3..af1e42ec370 100644 --- a/src/android.h +++ b/src/android.h @@ -30,6 +30,7 @@ along with GNU Emacs. If not, see . */ #include #include +#include #include diff --git a/src/androidterm.c b/src/androidterm.c index 220858c0fec..a8325312498 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -270,7 +270,7 @@ android_flush_dirty_back_buffer_on (struct frame *f) /* If the frame is not already up to date, do not flush buffers on input, as that will result in flicker. */ || !FRAME_ANDROID_COMPLETE_P (f) - && FRAME_ANDROID_NEED_BUFFER_FLIP (f)) + || !FRAME_ANDROID_NEED_BUFFER_FLIP (f)) return; show_back_buffer (f); diff --git a/xcompile/Makefile.in b/xcompile/Makefile.in index a63dbc2bb8b..ed2b10b91a7 100644 --- a/xcompile/Makefile.in +++ b/xcompile/Makefile.in @@ -38,7 +38,7 @@ top_builddir = @top_builddir@ # This is possibly the ugliest Makefile ever written! -LIB_SRCDIR = $(realpath $(top_srcdir)/lib) +LIB_SRCDIR = $(realpath ./lib) LIB_TOP_SRCDIR = $(realpath $(top_srcdir)) SRC_SRCDIR = $(realpath $(top_srcdir)/src) @@ -52,7 +52,7 @@ LIB_SRC_TOP_SRCDIR = $(realpath $(top_src)) LIBSRC_BINARIES = lib-src/etags lib-src/ctags lib-src/emacsclient \ lib-src/ebrowse lib-src/hexl lib-src/movemail -CLEAN_SUBDIRS=lib src lib-src +CLEAN_SUBDIRS=src lib-src .PHONY: all all: lib/libgnu.a src/libemacs.so src/android-emacs $(LIBSRC_BINARIES) @@ -68,12 +68,6 @@ src/verbose.mk: verbose.mk.android mkdir -p src cp -f verbose.mk.android src/verbose.mk -UGLY_HOST_HEADERS = dirent.h stdlib.h sys/random.h limits.h \ - string.h signal.h time.h inttypes.h assert.h \ - stdint.h unistd.h stdlib.h sys/types.h sys/time.h \ - sys/stat.h getopt.h fcntl.h sys/select.h alloca.h \ - stdio.h sys/random.h - # Gnulib, make-fingerprint and make-docfile must be built before # entering any of the rules below, or they will get the Android # versions of many headers. @@ -94,38 +88,26 @@ PRE_BUILD_DEPS=$(top_builddir)/lib/libgnu.a \ $(top_builddir)/lib-src/make-fingerprint \ $(top_builddir)/lib-src/make-docfile -.PHONY: lib/libgnu.a -lib/libgnu.a: src/verbose.mk config.status $(PRE_BUILD_DEPS) - mkdir -p lib/deps lib/deps/malloc -# Temporarily move config.h to config.h.bak and config.h.android to -# config.h +lib/config.h: $(top_builddir)/src/config.h.android cp -f -p $(top_builddir)/src/config.h.android lib/config.h -# And the Makefiles. + +lib/gnulib.mk: $(top_builddir)/lib/gnulib.mk.android cp -f -p $(top_builddir)/lib/gnulib.mk.android lib/gnulib.mk + sed -i 's/srcdir =.*$$/srcdir = $(subst /,\/,$(LIB_SRCDIR))/g' \ + lib/gnulib.mk + +lib/Makefile: $(top_builddir)/lib/Makefile.android cp -f -p $(top_builddir)/lib/Makefile.android lib/Makefile -# Next, move srcdir and top_srcdir in the Makefiles copied. - sed -i 's/srcdir =.*$$/srcdir = $(subst /,\/,$(LIB_SRCDIR))/g' lib/Makefile sed -i 's/top_srcdir =.*$$/top_srcdir = $(subst /,\/,$(LIB_TOP_SRCDIR))/g' \ lib/Makefile - sed -i 's/srcdir =.*$$/srcdir = $(subst /,\/,$(LIB_SRCDIR))/g' lib/gnulib.mk -# Ugly hack: hide some troublesome headers in $(top_builddir)/lib -# while building lib. Otherwise, they will end up overriding the -# system headers used on Android through #include_next and cause -# trouble. - mkdir -p sys - for ugly_header in $(UGLY_HOST_HEADERS); do \ - if [ -e "$(top_builddir)/lib/$$ugly_header" ]; then \ - mv -f $(top_builddir)/lib/$$ugly_header $$ugly_header.bak; \ - fi \ - done -# And make libgnu.a. Restore config.h if it fails. - -make -C lib libgnu.a -# Move the hiden headers back - for ugly_header in $(UGLY_HOST_HEADERS); do \ - if [ -e "$$ugly_header.bak" ]; then \ - mv -f $$ugly_header.bak $(top_builddir)/lib/$$ugly_header; \ - fi \ - done + +# What is needed to build gnulib. +LIB_DEPS = lib/config.h lib/gnulib.mk lib/Makefile + +.PHONY: lib/libgnu.a +lib/libgnu.a: src/verbose.mk config.status $(LIB_DEPS) $(PRE_BUILD_DEPS) + mkdir -p lib/deps lib/deps/malloc + +make -C lib libgnu.a src/Makefile src/config.h &: $(top_builddir)/src/config.h.android \ $(top_builddir)/src/Makefile.android $(PRE_BUILD_DEPS) @@ -145,32 +127,19 @@ src/Makefile src/config.h &: $(top_builddir)/src/config.h.android \ # that src/Makefile uses the binaries there, instead of any # cross-compiled binaries at ./lib-src. sed -i 's/libsrc =.*$$/libsrc = \.\.\/\.\.\/lib-src/g' src/Makefile +# Edit out anything saying -I($(top_srcdir)/lib); that should be +# covered by -I$(lib) + sed -i 's/-I\$$(top_srcdir)\/lib//g' src/Makefile .PHONY: src/android-emacs src/libemacs.so src/android-emacs src/libemacs.so &: src/Makefile src/config.h \ src/verbose.mk lib/libgnu.a $(PRE_BUILD_DEPS) -# Ugly hack: hide some troublesome headers in $(top_builddir)/lib -# while building lib. Otherwise, they will end up overriding the -# system headers used on Android through #include_next and cause -# trouble. - mkdir -p sys - for ugly_header in $(UGLY_HOST_HEADERS); do \ - if [ -e "$(top_builddir)/lib/$$ugly_header" ]; then \ - mv -f $(top_builddir)/lib/$$ugly_header $$ugly_header.bak; \ - fi \ - done -# Finally, go into src and make +make -C src android-emacs libemacs.so -# Move the hidden headers back - for ugly_header in $(UGLY_HOST_HEADERS); do \ - if [ -e "$$ugly_header.bak" ]; then \ - mv -f $$ugly_header.bak $(top_builddir)/lib/$$ugly_header; \ - fi \ - done lib-src/Makefile: $(top_builddir)/lib-src/Makefile.android mkdir -p lib-src cp -f -p $< $@ + sed -i 's/-I\$${srcdir}\/\.\.\/lib//g' lib-src/Makefile .PHONY: $(LIBSRC_BINARIES) $(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \ @@ -183,27 +152,18 @@ $(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \ lib-src/Makefile # Edit out SCRIPTS, it interferes with the build. sed -i 's/^SCRIPTS=.*$$/SCRIPTS=/g' lib-src/Makefile -# Ugly hack: hide some troublesome headers in $(top_builddir)/lib -# while building lib. Otherwise, they will end up overriding the -# system headers used on Android through #include_next and cause -# trouble. - mkdir -p sys - for ugly_header in $(UGLY_HOST_HEADERS); do \ - if [ -e "$(top_builddir)/lib/$$ugly_header" ]; then \ - mv -f $(top_builddir)/lib/$$ugly_header $$ugly_header.bak; \ - fi \ - done # Finally, go into lib-src and make everything being built +make -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin))) -# Move the hidden headers back - for ugly_header in $(UGLY_HOST_HEADERS); do \ - if [ -e "$$ugly_header.bak" ]; then \ - mv -f $$ugly_header.bak $(top_builddir)/lib/$$ugly_header; \ - fi \ - done .PHONY: clean maintainer-clean clean: rm -rf $(CLEAN_SUBDIRS) *.bak sys + if [ -e lib/Makefile ]; then \ + make -C lib clean; \ + fi + rm -rf lib/gnulib.mk lib/Makefile lib/config.h maintainer-clean: clean + if [ -e lib/Makefile ]; then \ + make -C lib maintainer-clean; \ + fi diff --git a/xcompile/README b/xcompile/README index a631b978dae..d5282212b71 100644 --- a/xcompile/README +++ b/xcompile/README @@ -1,7 +1,3 @@ This directory holds Makefiles and other required assets to build an -Emacs binary independently for another toolchain, which is currently -required when building for Android. - -The files here are extremely ugly, and contain rules that cannot be -interrupted without leaving the build tree in an unsafe state. At -some point that should be fixed! +Emacs binary independently for another toolchain. It also holds +another copy of gnulib that may be modified to work on Android. -- cgit v1.3 From d44b60c2f001d57b010f0e9b82f798fbad9a23d6 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 20 Jan 2023 19:06:32 +0800 Subject: Update Android port * .gitignore: Don't ignore verbose.mk.android. * doc/emacs/Makefile.in (EMACSSOURCES): Add android.texi and input.texi. * doc/emacs/android.texi (Android): Document support for the on-screen keyboard. (Android Startup): Document how to start Emacs with -Q on Android. (Android Environment): Document how Emacs works around the system ``task killer''. Document changes to frame deletion behavior. * doc/emacs/emacs.texi (Top): * doc/emacs/input.texi (Other Input Devices, On-Screen Keyboards): Document how to use Emacs with virtual keyboards. * doc/lispref/commands.texi (Touchscreen Events): Document changes to `touch-screen-track-drag'. * doc/lispref/frames.texi (Frames, On-Screen Keyboards): New node. * java/AndroidManifest.xml.in: Add settings activity and appropriate OSK adjustment mode. * java/org/gnu/emacs/EmacsActivity.java (onCreate): Allow creating Emacs with -Q. (onDestroy): Don't remove if killed by the system. * java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems): Fix context menus again. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Make all event sending functions return long. * java/org/gnu/emacs/EmacsPreferencesActivity.java (EmacsPreferencesActivity): New class. * java/org/gnu/emacs/EmacsService.java (EmacsService) (onStartCommand, onCreate, startEmacsService): Start as a foreground service if necessary to bypass system restrictions. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsThread.java (EmacsThread, run): * java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout) (onDetachedFromWindow): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, viewLayout): Implement frame resize synchronization.. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager, removeWindowConsumer): Adjust accordingly for changes to frame deletion behavior. * lisp/frame.el (android-toggle-on-screen-keyboard) (frame-toggle-on-screen-keyboard): New function. * lisp/minibuffer.el (minibuffer-setup-on-screen-keyboard) (minibuffer-exit-on-screen-keyboard): New functions. (minibuffer-setup-hook, minibuffer-exit-hook): Add new functions to hooks. * lisp/touch-screen.el (touch-screen-relative-xy): Accept new value of window `frame'. Return frame coordinates in that case. (touch-screen-set-point-commands): New variable. (touch-screen-handle-point-up): Respect that variable. (touch-screen-track-drag): Return `no-drag' where appropriate. (touch-screen-drag-mode-line-1, touch-screen-drag-mode-line): Refactor to use `no-drag'. * src/android.c (struct android_emacs_window): New methods. Make all event sending functions return the event serial. (android_toggle_on_screen_keyboard, android_window_updated): New functions. * src/android.h: Update prototypes. * src/androidfns.c (Fandroid_toggle_on_screen_keyboard) (syms_of_androidfns): New function. * src/androidgui.h (struct android_any_event) (struct android_key_event, struct android_configure_event) (struct android_focus_event, struct android_window_action_event) (struct android_crossing_event, struct android_motion_event) (struct android_button_event, struct android_touch_event) (struct android_wheel_event, struct android_iconify_event) (struct android_menu_event): Add `serial' fields. * src/androidterm.c (handle_one_android_event) (android_frame_up_to_date): * src/androidterm.h (struct android_output): Implement frame resize synchronization. --- .gitignore | 1 + doc/emacs/Makefile.in | 2 + doc/emacs/android.texi | 70 +++++-- doc/emacs/emacs.texi | 1 + doc/emacs/input.texi | 36 ++++ doc/lispref/commands.texi | 8 +- doc/lispref/frames.texi | 21 +++ java/AndroidManifest.xml.in | 12 ++ java/org/gnu/emacs/EmacsActivity.java | 20 +- java/org/gnu/emacs/EmacsContextMenu.java | 1 + java/org/gnu/emacs/EmacsNative.java | 39 ++-- java/org/gnu/emacs/EmacsPreferencesActivity.java | 98 ++++++++++ java/org/gnu/emacs/EmacsService.java | 62 ++++++- java/org/gnu/emacs/EmacsSurfaceView.java | 127 +++++++++---- java/org/gnu/emacs/EmacsThread.java | 12 +- java/org/gnu/emacs/EmacsView.java | 55 +++++- java/org/gnu/emacs/EmacsWindow.java | 58 +++++- .../gnu/emacs/EmacsWindowAttachmentManager.java | 4 +- lisp/frame.el | 17 ++ lisp/minibuffer.el | 23 +++ lisp/touch-screen.el | 203 +++++++++++++-------- src/android.c | 147 ++++++++++++--- src/android.h | 2 + src/androidfns.c | 25 +++ src/androidgui.h | 20 ++ src/androidterm.c | 24 +++ src/androidterm.h | 5 + 27 files changed, 894 insertions(+), 199 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsPreferencesActivity.java (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/.gitignore b/.gitignore index 3bc40c67489..6494e4e8f39 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ src/emacs-module.h # Built by recursive call to `configure'. *.android !INSTALL.android +!verbose.mk.android # Built by `java'. java/install_temp/* diff --git a/doc/emacs/Makefile.in b/doc/emacs/Makefile.in index 161bdcb1c59..c7415312753 100644 --- a/doc/emacs/Makefile.in +++ b/doc/emacs/Makefile.in @@ -146,6 +146,8 @@ EMACSSOURCES= \ ${srcdir}/glossary.texi \ ${srcdir}/ack.texi \ ${srcdir}/kmacro.texi \ + ${srcdir}/android.texi \ + ${srcdir}/input.texi \ $(EMACS_XTRA) ## Disable implicit rules. diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 01a2e68a97e..ba5d2ca3e09 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -10,8 +10,8 @@ Alliance. This section describes the peculiarities of using Emacs on an Android device running Android 2.2 or later. Android devices commonly rely on user input through a touch screen -or digitizer device. For more information about using them with -Emacs, @pxref{other Input Devices}. +or digitizer device and on-screen keyboard. For more information +about using such devices with Emacs, @pxref{Other Input Devices}. @menu * What is Android?:: Preamble. @@ -82,6 +82,16 @@ command on that other system: $ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)" @end example +@cindex emacs -Q, android +Since Android has no command line, there is normally no way to specify +command-line arguments. However, Emacs can be started with the +equivalent of the @code{--quick} option (@pxref{Initial Options}) +through a special preferences screen, which can be accessed through +the Emacs ``app info'' page in the system settings application. + +Consult the manufacturer of your device for more details, as how to do +this varies by device. + @node Android File System @section What files Emacs can access under Android @cindex /assets directory, android @@ -149,7 +159,7 @@ which is the app data directory (@pxref{Android File System}.) directories, and the app data directories of other applications. In recent versions of Android, the system also prohibits, for security reasons, even Emacs itself from running executables inside the app -data directory! +data directory. Emacs comes with several binaries. While being executable files, they are packaged as libraries in the library directory, because @@ -173,9 +183,28 @@ within itself. Application processes are treated as disposable entities by the system. When all Emacs frames move to the background, Emacs is liable to be killed by the system at any time, for the purpose of saving -resources. There is currently no easy way to bypass these -restrictions, aside from keeping Emacs constantly running in the -foreground. +system resources. + + On Android 7.1 and earlier, Emacs tells the system to treat it as a +``background service''. The system will try to avoid killing Emacs +unless the device is under memory stress. + + Android 8.0 removed the ability for background services to receive +such special treatment. However, Emacs applies a workaround: the +system considers applications that create a permanent notification to +be performing active work, and will avoid killing such applications. +Thus, on those systems, Emacs displays a permanant notification for as +long as it is running. Once the notification is displayed, it can be +safely hidden through the system settings without resulting in Emacs +being killed. + + However, it is not guaranteed that the system will not kill Emacs, +even if the notification is being displayed. While the Open Handset +Alliance's sample implementation of Android behaves correctly, many +manufacturers place additional restrictions on program execution in +the background in their proprietary versions of Android. There is a +list of such troublesome manufacturers and sometimes workarounds, at +@url{https://dontkillmyapp.com/}. @section Android permissions @cindex external storage, android @@ -272,14 +301,14 @@ maximized or full-screen, and only one window can be displayed at a time. On larger devices, the system allows up to four windows to be tiled on the screen at any time. -Windows on Android do not continue to exist indefinitely after they + Windows on Android do not continue to exist indefinitely after they are created. Instead, the system may choose to terminate windows that are not on screen in order to save memory, with the assumption that the program will save its contents to disk and restore them later, when the user asks to open it again. As this is obvious not possible with Emacs, Emacs separates a frame from a system window. -Each system window created (including the initial window created + Each system window created (including the initial window created during Emacs startup) is appended to a list of windows that do not have associated frames. When a frame is created, Emacs looks up any window within that list, and displays the contents of the frame @@ -287,11 +316,28 @@ within; if there is no window at all, then one is created. Likewise, when a new window is created by the system, Emacs places the contents of any frame that is not already displayed within a window inside. When a frame is closed, the corresponding system window is also -closed. +closed. Upon startup, the system creates a window itself (within +which Emacs displays the first window system frame shortly +thereafter.) Emacs differentiates between that window and windows +created on behalf of other frames to determine what to do when the +system window associated with a frame is closed: + +@itemize @bullet +@item +When the system closes the window created during application startup +in order to save memory, Emacs retains the frame for when that window +is created later. -This strategy works as long as one window is in the foreground. -Otherwise, Emacs can only run in the background for a limited amount -of time before the process is killed completely. +@item +When the user closes the window created during application startup, +and the window was not previously closed by the system in order to +save resources, Emacs deletes any frame displayed within that window. + +@item +When the user or the system closes any window created by Emacs on +behalf of a specific frame, Emacs deletes the frame displayed within +that window. +@end itemize @cindex windowing limitations, android @cindex frame parameters, android diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 9fd169cd5cc..0cb454e5294 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -1269,6 +1269,7 @@ Emacs and Android Emacs and unconventional input devices * Touchscreens:: Using Emacs on touchscreens. +* On-Screen Keyboards:: Using Emacs with virtual keyboards. Emacs and Microsoft Windows/MS-DOS diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi index 0e029c401e3..1a58d1ca0ac 100644 --- a/doc/emacs/input.texi +++ b/doc/emacs/input.texi @@ -16,6 +16,7 @@ input devices, which is detailed here. @menu * Touchscreens:: Using Emacs on touchscreens. +* On-Screen Keyboards:: Using Emacs with virtual keyboards. @end menu @node Touchscreens @@ -58,3 +59,38 @@ were to be held down. @xref{Mouse Commands}. By default, Emacs considers a tool as having been left on the display for a while after 0.7 seconds, but this can be changed by customizing the variable @code{touch-screen-delay}. + +@node On-Screen Keyboards +@section Using Emacs with virtual keyboards +@cindex virtual keyboards +@cindex on-screen keyboards + + When there is no physical keyboard attached to a system, the +windowing system typically provides an on-screen keyboard, more often +known as a ``virtual keyboard'', containing rows of clickable buttons +that send keyboard input to the application, much like a real keyboard +would. This virtual keyboard is hidden by default, as it uses up +valuable on-screen real estate, and must be opened once the program +being used is ready to accept keyboard input. + + Under the X Window System, the client that provides the on-screen +keyboard typically detects when the application is ready to accept +keyboard input through a set of complex heuristics, and automatically +displays the keyboard when necessary. + + On other systems such as Android, Emacs must tell the system when it +is ready to accept keyboard input. Typically, this is done in +response to a touchscreen ``tap'' gesture (@pxref{Touchscreens}), or +once to the minibuffer becomes in use (@pxref{Minibuffer}.) + +@vindex touch-screen-set-point-commands + When a ``tap'' gesture results in a command being executed, Emacs +checks to see whether or not the command is supposed to set the point +by looking for it in the list @code{touch-screen-set-point-commands}. +If it is, then Emacs looks up whether or not the text under the point +is read-only; if not, it activates the on-screen keyboard, assuming +that the user is about to enter text in to the current buffer. + + Emacs also provides a set of functions to show or hide the on-screen +keyboard. For more details, @pxref{On-Screen Keyboards,,, elisp, The +Emacs Lisp Reference Manual}. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 59367a2cecc..484c7dc2a06 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2055,9 +2055,11 @@ The caller should not perform any action in that case. @defun touch-screen-track-drag event update &optional data This function is used to track a single ``drag'' gesture originating -from the @code{touchscreen-begin} event @code{event}. Currently, it -behaves identically to @code{touch-screen-track-tap}, but differences -are anticipated in the future. +from the @code{touchscreen-begin} event @code{event}. + +It behaves like @code{touch-screen-track-tap}, except that it returns +@code{no-drag} if the touchpoint in @code{event} did not move far +enough to qualify as an actual drag. @end defun @node Focus Events diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 2ceafab7a6b..497715bdb19 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -104,6 +104,7 @@ window of another Emacs frame. @xref{Child Frames}. * Mouse Tracking:: Getting events that say when the mouse moves. * Mouse Position:: Asking where the mouse is, or moving it. * Pop-Up Menus:: Displaying a menu for the user to select from. +* On-Screen Keyboards:: Displaying the virtual keyboard. * Dialog Boxes:: Displaying a box to ask yes or no. * Pointer Shape:: Specifying the shape of the mouse pointer. * Window System Selections:: Transferring text to and from other X clients. @@ -3812,6 +3813,26 @@ keymap. It won't be called if @code{x-popup-menu} returns for some other reason without displaying a pop-up menu. @end defvar +@node On-Screen Keyboards +@section On-Screen Keyboards + + An on-screen keyboard is a special kind of pop up provided by the +system, with rows of clickable buttons that act as a real keyboard. + + On certain systems (@pxref{On-Screen Keyboards,,,emacs, The Emacs +Manual}), Emacs is supposed to display and hide the on screen keyboard +depending on whether or not the user is about to type something. + +@defun frame-toggle-on-screen-keyboard frame hide +This function displays or hides the on-screen keyboard on behalf of +the frame @var{frame}. If @var{hide} is non-@code{nil}, then the +on-screen keyboard is hidden; otherwise, it is displayed. + +This has no effect if the system automatically detects when to display +the on-screen keyboard, or when it does not provide any on-screen +keyboard. +@end defun + @node Dialog Boxes @section Dialog Boxes @cindex dialog boxes diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index b680137a9d0..74f69d2a8e5 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -62,8 +62,10 @@ along with GNU Emacs. If not, see . --> android:theme="@android:style/Theme" android:debuggable="true" android:extractNativeLibs="true"> + @@ -73,8 +75,18 @@ along with GNU Emacs. If not, see . --> + + + + + + + . */ + +package org.gnu.emacs; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.widget.TextView; + +import android.R; + +/* This module provides a ``preferences'' display for Emacs. It is + supposed to be launched from inside the Settings application to + perform various actions, such as starting Emacs with the ``-Q'' + option, which would not be possible otherwise, as there is no + command line on Android. */ + +public class EmacsPreferencesActivity extends Activity +{ + /* The linear layout associated with the activity. */ + private LinearLayout layout; + + /* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, and + tell the system to EmacsActivity with some parameters later. */ + + private void + startEmacsQ () + { + Intent intent; + + intent = new Intent (this, EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra ("org.gnu.emacs.START_DASH_Q", true); + startActivity (intent); + System.exit (0); + } + + @Override + public void + onCreate (Bundle savedInstanceState) + { + LinearLayout layout; + TextView textView; + LinearLayout.LayoutParams params; + int resid; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + setTheme (R.style.Theme_DeviceDefault_Settings); + else if (Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + setTheme (R.style.Theme_DeviceDefault); + + layout = new LinearLayout (this); + layout.setOrientation (LinearLayout.VERTICAL); + setContentView (layout); + + textView = new TextView (this); + textView.setPadding (8, 20, 20, 8); + + params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + textView.setLayoutParams (params); + textView.setText ("(Re)start Emacs with -Q"); + textView.setOnClickListener (new View.OnClickListener () { + @Override + public void + onClick (View view) + { + startEmacsQ (); + } + }); + layout.addView (textView); + + super.onCreate (savedInstanceState); + } +}; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index bcf8d9ff6e8..95f21b211a3 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -32,7 +32,13 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.annotation.TargetApi; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.NotificationChannel; +import android.app.PendingIntent; import android.app.Service; + import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; @@ -63,6 +69,7 @@ public class EmacsService extends Service public static final String TAG = "EmacsService"; public static final int MAX_PENDING_REQUESTS = 256; public static volatile EmacsService SERVICE; + public static boolean needDashQ; private EmacsThread thread; private Handler handler; @@ -74,6 +81,31 @@ public class EmacsService extends Service public int onStartCommand (Intent intent, int flags, int startId) { + Notification notification; + NotificationManager manager; + NotificationChannel channel; + String infoBlurb; + Object tem; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + tem = getSystemService (Context.NOTIFICATION_SERVICE); + manager = (NotificationManager) tem; + infoBlurb = ("See (emacs)Android Environment for more" + + " details about this notification."); + channel + = new NotificationChannel ("emacs", "Emacs persistent notification", + NotificationManager.IMPORTANCE_DEFAULT); + manager.createNotificationChannel (channel); + notification = (new Notification.Builder (this, "emacs") + .setContentTitle ("Emacs") + .setContentText (infoBlurb) + .setSmallIcon (android.R.drawable.sym_def_app_icon) + .build ()); + manager.notify (1, notification); + startForeground (1, notification); + } + return START_NOT_STICKY; } @@ -137,7 +169,7 @@ public class EmacsService extends Service this); /* Start the thread that runs Emacs. */ - thread = new EmacsThread (this); + thread = new EmacsThread (this, needDashQ); thread.start (); } catch (IOException exception) @@ -444,4 +476,32 @@ public class EmacsService extends Service } } } + + + + /* Start the Emacs service if necessary. On Android 26 and up, + start Emacs as a foreground service with a notification, to avoid + it being killed by the system. + + On older systems, simply start it as a normal background + service. */ + + public static void + startEmacsService (Context context) + { + PendingIntent intent; + + if (EmacsService.SERVICE == null) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + /* Start the Emacs service now. */ + context.startService (new Intent (context, + EmacsService.class)); + else + /* Display the permanant notification and start Emacs as a + foreground service. */ + context.startForegroundService (new Intent (context, + EmacsService.class)); + } + } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index f713818d4bc..2fe9e103b2b 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -32,53 +32,106 @@ import android.util.Log; public class EmacsSurfaceView extends SurfaceView { private static final String TAG = "EmacsSurfaceView"; - public Object surfaceChangeLock; private boolean created; + private EmacsView view; - public - EmacsSurfaceView (final EmacsView view) - { - super (view.getContext ()); - - surfaceChangeLock = new Object (); + /* This is the callback used on Android 8 to 25. */ - getHolder ().addCallback (new SurfaceHolder.Callback () { - @Override - public void - surfaceChanged (SurfaceHolder holder, int format, - int width, int height) + private class Callback implements SurfaceHolder.Callback + { + @Override + public void + surfaceChanged (SurfaceHolder holder, int format, + int width, int height) + { + Log.d (TAG, "surfaceChanged: " + view + ", " + view.pendingConfigure); + + /* Make sure not to swap buffers if there is pending + configuration, because otherwise the redraw callback will not + run correctly. */ + + if (view.pendingConfigure == 0) + view.swapBuffers (); + } + + @Override + public void + surfaceCreated (SurfaceHolder holder) + { + synchronized (surfaceChangeLock) { - Log.d (TAG, "surfaceChanged: " + view); - view.swapBuffers (); + Log.d (TAG, "surfaceCreated: " + view); + created = true; } - @Override - public void - surfaceCreated (SurfaceHolder holder) - { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceCreated: " + view); - created = true; - } - - /* Drop the lock when doing this, or a deadlock can - result. */ - view.swapBuffers (); - } + /* Drop the lock when doing this, or a deadlock can + result. */ + view.swapBuffers (); + } - @Override - public void - surfaceDestroyed (SurfaceHolder holder) + @Override + public void + surfaceDestroyed (SurfaceHolder holder) + { + synchronized (surfaceChangeLock) { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceDestroyed: " + view); - created = false; - } + Log.d (TAG, "surfaceDestroyed: " + view); + created = false; } - }); + } + } + + /* And this is the callback used on Android 26 and later. It is + used because it can tell the system when drawing completes. */ + + private class Callback2 extends Callback implements SurfaceHolder.Callback2 + { + @Override + public void + surfaceRedrawNeeded (SurfaceHolder holder) + { + /* This version is not supported. */ + return; + } + + @Override + public void + surfaceRedrawNeededAsync (SurfaceHolder holder, + Runnable drawingFinished) + { + Runnable old; + + Log.d (TAG, "surfaceRedrawNeededAsync: " + view.pendingConfigure); + + /* The system calls this function when it wants to know whether + or not Emacs is still configuring itself in response to a + resize. + + If the view did not send an outstanding ConfigureNotify + event, then call drawingFinish immediately. Else, give it to + the view to execute after drawing completes. */ + + if (view.pendingConfigure == 0) + drawingFinished.run (); + else + /* And set this runnable to run once drawing completes. */ + view.drawingFinished = drawingFinished; + } + } + + public + EmacsSurfaceView (final EmacsView view) + { + super (view.getContext ()); + + this.surfaceChangeLock = new Object (); + this.view = view; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + getHolder ().addCallback (new Callback ()); + else + getHolder ().addCallback (new Callback2 ()); } public boolean diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 0882753747f..f9bc132f354 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -23,12 +23,13 @@ import java.lang.Thread; public class EmacsThread extends Thread { - EmacsService context; + /* Whether or not Emacs should be started -Q. */ + private boolean startDashQ; public - EmacsThread (EmacsService service) + EmacsThread (EmacsService service, boolean startDashQ) { - context = service; + this.startDashQ = startDashQ; } public void @@ -36,7 +37,10 @@ public class EmacsThread extends Thread { String args[]; - args = new String[] { "libandroid-emacs.so", }; + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", }; + else + args = new String[] { "libandroid-emacs.so", "-Q", }; /* Run the native code now. */ EmacsNative.initEmacs (args); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 82f44acaebe..74bbb7b3ecc 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import android.content.Context; import android.content.res.ColorStateList; import android.view.ContextMenu; @@ -27,6 +28,8 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; + import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; @@ -86,11 +89,23 @@ public class EmacsView extends ViewGroup /* The serial of the last clip rectangle change. */ private long lastClipSerial; + /* The InputMethodManager for this view's context. */ + private InputMethodManager imManager; + + /* Runnable that will run once drawing completes. */ + public Runnable drawingFinished; + + /* Serial of the last ConfigureNotify event sent that Emacs has not + yet responded to. 0 if there is no such outstanding event. */ + public long pendingConfigure; + public EmacsView (EmacsWindow window) { super (EmacsService.SERVICE); + Object tem; + this.window = window; this.damageRegion = new Region (); this.paint = new Paint (); @@ -111,6 +126,10 @@ public class EmacsView extends ViewGroup /* Get rid of the default focus highlight. */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) setDefaultFocusHighlightEnabled (false); + + /* Obtain the input method manager. */ + tem = getContext ().getSystemService (Context.INPUT_METHOD_SERVICE); + imManager = (InputMethodManager) tem; } private void @@ -259,7 +278,8 @@ public class EmacsView extends ViewGroup if (changed || mustReportLayout) { mustReportLayout = false; - window.viewLayout (left, top, right, bottom); + pendingConfigure + = window.viewLayout (left, top, right, bottom); } measuredWidth = right - left; @@ -538,4 +558,37 @@ public class EmacsView extends ViewGroup Runtime.getRuntime ().gc (); } } + + public void + showOnScreenKeyboard () + { + /* Specifying no flags at all tells the system the user asked for + the input method to be displayed. */ + imManager.showSoftInput (this, 0); + } + + public void + hideOnScreenKeyboard () + { + imManager.hideSoftInputFromWindow (this.getWindowToken (), + 0); + } + + public void + windowUpdated (long serial) + { + Log.d (TAG, "windowUpdated: serial is " + serial); + + if (pendingConfigure <= serial + /* Detect wraparound. */ + || pendingConfigure - serial >= 0x7fffffff) + { + pendingConfigure = 0; + + if (drawingFinished != null) + drawingFinished.run (); + + drawingFinished = null; + } + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index c5b1522086c..8511af9193e 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -233,7 +233,7 @@ public class EmacsWindow extends EmacsHandleObject return attached; } - public void + public long viewLayout (int left, int top, int right, int bottom) { int rectWidth, rectHeight; @@ -249,10 +249,10 @@ public class EmacsWindow extends EmacsHandleObject rectWidth = right - left; rectHeight = bottom - top; - EmacsNative.sendConfigureNotify (this.handle, - System.currentTimeMillis (), - left, top, rectWidth, - rectHeight); + return EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rectWidth, + rectHeight); } public void @@ -589,11 +589,20 @@ public class EmacsWindow extends EmacsHandleObject EmacsActivity.invalidateFocus (); } + /* Notice that the activity has been detached or destroyed. + + ISFINISHING is set if the activity is not the main activity, or + if the activity was not destroyed in response to explicit user + action. */ + public void - onActivityDetached () + onActivityDetached (boolean isFinishing) { - /* Destroy the associated frame when the activity is detached. */ - EmacsNative.sendWindowAction (this.handle, 0); + /* Destroy the associated frame when the activity is detached in + response to explicit user action. */ + + if (isFinishing) + EmacsNative.sendWindowAction (this.handle, 0); } /* Look through the button state to determine what button EVENT was @@ -1064,4 +1073,37 @@ public class EmacsWindow extends EmacsHandleObject /* Return the resulting coordinates. */ return array; } + + public void + toggleOnScreenKeyboard (final boolean on) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + if (on) + view.showOnScreenKeyboard (); + else + view.hideOnScreenKeyboard (); + } + }); + } + + /* Notice that outstanding configure events have been processed. + SERIAL is checked in the UI thread to verify that no new + configure events have been generated in the mean time. */ + + public void + windowUpdated (final long serial) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.windowUpdated (serial); + } + }); + } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 15eb3bb65c2..510300571b8 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -134,7 +134,7 @@ public class EmacsWindowAttachmentManager } public void - removeWindowConsumer (WindowConsumer consumer) + removeWindowConsumer (WindowConsumer consumer, boolean isFinishing) { EmacsWindow window; @@ -147,7 +147,7 @@ public class EmacsWindowAttachmentManager Log.d (TAG, "removeWindowConsumer: detaching " + window); consumer.detachWindow (); - window.onActivityDetached (); + window.onActivityDetached (isFinishing); } Log.d (TAG, "removeWindowConsumer: removing " + consumer); diff --git a/lisp/frame.el b/lisp/frame.el index f21c0c369bd..f8ef17325ec 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2553,6 +2553,23 @@ symbols." ((string= name "Virtual core keyboard") 'core-keyboard)))))) + +;;;; On-screen keyboard management. + +(declare-function android-toggle-on-screen-keyboard "androidfns.c") + +(defun frame-toggle-on-screen-keyboard (frame hide) + "Display or hide the on-screen keyboard. +On systems with an on-screen keyboard, display the on screen +keyboard on behalf of the frame FRAME if HIDE is nil. Else, hide +the on screen keyboard. + +FRAME must already have the input focus for this to work + reliably." + (let ((frame-type (framep-on-display frame))) + (cond ((eq frame-type 'android) + (android-toggle-on-screen-keyboard frame hide))))) + ;;;; Frame geometry values diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 21d4607e7cf..3c42f29cd1f 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -4576,6 +4576,29 @@ is included in the return value." default))) ": ")) + +;;; On screen keyboard support. +;; Try to display the on screen keyboard whenever entering the +;; mini-buffer, and hide it whenever leaving. + +(defun minibuffer-setup-on-screen-keyboard () + "Maybe display the on-screen keyboard in the current frame. +Display the on-screen keyboard in the current frame if the +last device to have sent an input event is not a keyboard. +This is run upon minibuffer setup." + (when (not (memq (device-class last-event-frame + last-event-device) + '(keyboard core-keyboard))) + (frame-toggle-on-screen-keyboard (selected-frame) nil))) + +(defun minibuffer-exit-on-screen-keyboard () + "Hide the on-screen keyboard if it was displayed. +This is run upon minibuffer exit." + (frame-toggle-on-screen-keyboard (selected-frame) t)) + +(add-hook 'minibuffer-setup-hook #'minibuffer-setup-on-screen-keyboard) +(add-hook 'minibuffer-exit-hook #'minibuffer-exit-on-screen-keyboard) + (provide 'minibuffer) ;;; minibuffer.el ends here diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index bcc4f5e9be3..a1c9e0b4afd 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -38,6 +38,12 @@ touch point, and the initial position of the touchpoint. See `touch-screen-handle-point-update' for the meanings of the fourth element.") +(defvar touch-screen-set-point-commands '(mouse-set-point) + "List of commands known to set the point. +This is used to determine whether or not to display the on-screen +keyboard after a mouse command is executed in response to a +`touchscreen-end' event.") + (defvar touch-screen-current-timer nil "Timer used to track long-presses. This is always cleared upon any significant state change.") @@ -54,20 +60,28 @@ However, return the coordinates relative to WINDOW. If (posn-window posn) is the same as window, simply return the coordinates in POSN. Otherwise, convert them to the frame, and -then back again." - (if (eq (posn-window posn) window) +then back again. + +If WINDOW is the symbol `frame', simply convert the coordinates +to the frame that they belong in." + (if (or (eq (posn-window posn) window) + (and (eq window 'frame) + (framep (posn-window posn)))) (posn-x-y posn) (let ((xy (posn-x-y posn)) - (edges (window-inside-pixel-edges window))) + (edges (and (windowp window) + (window-inside-pixel-edges window)))) ;; Make the X and Y positions frame relative. (when (windowp (posn-window posn)) (let ((edges (window-inside-pixel-edges (posn-window posn)))) (setq xy (cons (+ (car xy) (car edges)) (+ (cdr xy) (cadr edges)))))) - ;; Make the X and Y positions window relative again. - (cons (- (car xy) (car edges)) - (- (cdr xy) (cadr edges)))))) + (if (eq window 'frame) + xy + ;; Make the X and Y positions window relative again. + (cons (- (car xy) (car edges)) + (- (cdr xy) (cadr edges))))))) (defun touch-screen-handle-scroll (dx dy) "Scroll the display assuming that a touch point has moved by DX and DY." @@ -252,7 +266,12 @@ If the fourth argument of `touch-screen-current-tool' is nil, move point to the position of POINT, selecting the window under POINT as well, and deactivate the mark; if there is a button or link at POINT, call the command bound to `mouse-2' there. -Otherwise, call the command bound to `mouse-1'." +Otherwise, call the command bound to `mouse-1'. + +If the command being executed is listed in +`touch-screen-set-point-commands' also display the on-screen +keyboard if the current buffer and the character at the new point +is not read-only." (let ((what (nth 3 touch-screen-current-tool))) (cond ((null what) (when (windowp (posn-window (cdr point))) @@ -283,9 +302,17 @@ Otherwise, call the command bound to `mouse-1'." (deactivate-mark) ;; This is necessary for following links. (goto-char (posn-point (cdr point))) + ;; Figure out if the on screen keyboard needs to be + ;; displayed. (when command (call-interactively command nil - (vector event))))))))) + (vector event)) + (when (memq command touch-screen-set-point-commands) + (if (not (or buffer-read-only + (get-text-property (point) 'read-only))) + (frame-toggle-on-screen-keyboard (selected-frame) nil) + ;; Otherwise, hide the on screen keyboard now. + (frame-toggle-on-screen-keyboard (selected-frame) t)))))))))) (defun touch-screen-handle-touch (event) "Handle a single touch EVENT, and perform associated actions. @@ -379,22 +406,34 @@ touch point with the same ID as in EVENT, call UPDATE with the touch point in event and DATA. Return nil immediately if any other kind of event is received; -otherwise, return t once the `touchscreen-end' event arrives." - (catch 'finish - (while t - (let ((new-event (read-event))) - (cond - ((eq (car-safe new-event) 'touchscreen-update) - (let ((tool (assq (caadr event) (nth 1 new-event)))) - (when (and update tool) - (funcall update new-event data)))) - ((eq (car-safe new-event) 'touchscreen-end) - (throw 'finish - ;; Now determine whether or not the `touchscreen-end' - ;; event has the same ID as EVENT. If it doesn't, - ;; then this is another touch, so return nil. - (eq (caadr event) (caadr new-event)))) - (t (throw 'finish nil))))))) +otherwise, return either t or `no-drag' once the +`touchscreen-end' event arrives; return `no-drag' returned if the +touch point in EVENT did not move significantly, and t otherwise." + (let ((return-value 'no-drag) + (start-xy (touch-screen-relative-xy (cdadr event) + 'frame))) + (catch 'finish + (while t + (let ((new-event (read-event))) + (cond + ((eq (car-safe new-event) 'touchscreen-update) + (when-let* ((tool (assq (caadr event) (nth 1 new-event))) + (xy (touch-screen-relative-xy (cdr tool) 'frame))) + (when (or (> (- (car xy) (car start-xy)) 5) + (< (- (car xy) (car start-xy)) -5) + (> (- (cdr xy) (cdr start-xy)) 5) + (< (- (cdr xy) (cdr start-xy)) -5)) + (setq return-value t)) + (when (and update tool) + (funcall update new-event data)))) + ((eq (car-safe new-event) 'touchscreen-end) + (throw 'finish + ;; Now determine whether or not the `touchscreen-end' + ;; event has the same ID as EVENT. If it doesn't, + ;; then this is another touch, so return nil. + (and (eq (caadr event) (caadr new-event)) + return-value))) + (t (throw 'finish nil)))))))) @@ -402,45 +441,8 @@ otherwise, return t once the `touchscreen-end' event arrives." (defun touch-screen-drag-mode-line-1 (event) "Internal helper for `touch-screen-drag-mode-line'. -This is called when that function determines it need not execute -any keymaps on the mode line at that particular spot." - ;; Find the window that should be dragged and the starting position. - (let* ((window (posn-window (cdadr event))) - (relative-xy (touch-screen-relative-xy - (cdadr event) window)) - (last-position (cdr relative-xy))) - (when (window-resizable window 0) - (touch-screen-track-drag - event (lambda (new-event &optional _data) - ;; Find the position of the touchpoint in NEW-EVENT. - (let* ((touchpoint (assq (caadr event) (cadr new-event))) - (new-relative-xy - (touch-screen-relative-xy (cdr touchpoint) - window)) - (position (cdr new-relative-xy)) - growth) - ;; Now set the new height of the window. - ;; If new-relative-y is above relative-xy, then - ;; make the window that much shorter. Otherwise, - ;; make it bigger. - (unless (or (zerop (setq growth (- position last-position))) - (and (> growth 0) - (< position (+ (window-pixel-top window) - (window-pixel-height window)))) - (and (< growth 0) - (> position (+ (window-pixel-top window) - (window-pixel-height window))))) - (adjust-window-trailing-edge window growth nil t)) - (setq last-position position))))))) - -(defun touch-screen-drag-mode-line (event) - "Begin dragging the mode line in response to a touch EVENT. -If EVENT lies on top of text with a mouse command bound, run -that command instead. - -Change the height of the window based on where the touch point -in EVENT moves." - (interactive "e") +This is called when that function determines that no drag really +happened. EVENT is the same as in `touch-screen-drag-mode-line'." ;; If there is an object at EVENT, then look either a keymap bound ;; to [down-mouse-1] or a command bound to [mouse-1]. Then, if a ;; keymap was found, pop it up as a menu. Otherwise, wait for a tap @@ -457,21 +459,66 @@ in EVENT moves." (keymap (lookup-key object-keymap [mode-line down-mouse-1])) (command (or (lookup-key object-keymap [mode-line mouse-1]) keymap))) - (if (or (keymapp keymap) command) - (if (keymapp keymap) - (when (touch-screen-track-tap event) - (when-let* ((command (x-popup-menu event keymap)) - (tem (lookup-key keymap - (if (consp command) - (apply #'vector command) - (vector command)) - t))) - (call-interactively tem))) - (when (and (commandp command) - (touch-screen-track-tap event)) - (call-interactively command nil - (vector (list 'mouse-1 (cdadr event)))))) - (touch-screen-drag-mode-line-1 event)))) + (when (or (keymapp keymap) command) + (if (keymapp keymap) + (when-let* ((command (x-popup-menu event keymap)) + (tem (lookup-key keymap + (if (consp command) + (apply #'vector command) + (vector command)) + t))) + (call-interactively tem)) + (when (commandp command) + (call-interactively command nil + (vector (list 'mouse-1 (cdadr event))))))))) + +(defun touch-screen-drag-mode-line (event) + "Begin dragging the mode line in response to a touch EVENT. +Change the height of the window based on where the touch point in +EVENT moves. + +If it does not actually move anywhere and the touch point is +removed, and EVENT lies on top of text with a mouse command +bound, run that command instead." + (interactive "e") + ;; Find the window that should be dragged and the starting position. + (let* ((window (posn-window (cdadr event))) + (relative-xy (touch-screen-relative-xy + (cdadr event) window)) + (last-position (cdr relative-xy))) + (when (window-resizable window 0) + (when (eq + (touch-screen-track-drag + event (lambda (new-event &optional _data) + ;; Find the position of the touchpoint in + ;; NEW-EVENT. + (let* ((touchpoint (assq (caadr event) + (cadr new-event))) + (new-relative-xy + (touch-screen-relative-xy (cdr touchpoint) + window)) + (position (cdr new-relative-xy)) + growth) + ;; Now set the new height of the window. If + ;; new-relative-y is above relative-xy, then + ;; make the window that much shorter. + ;; Otherwise, make it bigger. + (unless (or (zerop (setq growth + (- position last-position))) + (and (> growth 0) + (< position + (+ (window-pixel-top window) + (window-pixel-height window)))) + (and (< growth 0) + (> position + (+ (window-pixel-top window) + (window-pixel-height window))))) + (adjust-window-trailing-edge window growth nil t)) + (setq last-position position)))) + 'no-drag) + ;; Dragging did not actually happen, so try to run any command + ;; necessary. + (touch-screen-drag-mode-line-1 event))))) (global-set-key [mode-line touchscreen-begin] #'touch-screen-drag-mode-line) diff --git a/src/android.c b/src/android.c index eb9c404f1a3..43ac3b3f754 100644 --- a/src/android.c +++ b/src/android.c @@ -121,6 +121,8 @@ struct android_emacs_window { jclass class; jmethodID swap_buffers; + jmethodID toggle_on_screen_keyboard; + jmethodID window_updated; }; /* The asset manager being used. */ @@ -193,6 +195,10 @@ 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 @@ -1435,6 +1441,9 @@ android_init_emacs_window (void) 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 } @@ -1497,7 +1506,7 @@ NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object) emacs_abort (); } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, jshort window, jlong time, jint x, jint y, jint width, @@ -1506,6 +1515,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, 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; @@ -1514,9 +1524,10 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, event.xconfigure.height = height; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, jshort window, jlong time, jint state, jint keycode, @@ -1525,6 +1536,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, 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; @@ -1532,9 +1544,10 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, event.xkey.unicode_char = unicode_char; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, jshort window, jlong time, jint state, jint keycode, @@ -1543,6 +1556,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, 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; @@ -1550,48 +1564,55 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, event.xkey.unicode_char = unicode_char; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object, jshort window, jlong time) { union android_event event; - event.xkey.type = ANDROID_FOCUS_IN; - event.xkey.window = window; - event.xkey.time = time; + 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 void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object, jshort window, jlong time) { union android_event event; - event.xkey.type = ANDROID_FOCUS_OUT; - event.xkey.window = window; - event.xkey.time = time; + 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 void JNICALL +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 void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -1599,15 +1620,17 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, 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 void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -1615,15 +1638,17 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, 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 void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -1631,15 +1656,17 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, 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 void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -1648,6 +1675,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, 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; @@ -1656,9 +1684,10 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, event.xbutton.button = button; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -1667,6 +1696,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, 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; @@ -1675,9 +1705,10 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, event.xbutton.button = button; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -1685,6 +1716,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, 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; @@ -1692,9 +1724,10 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, event.touch.pointer_id = pointer_id; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -1702,6 +1735,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, 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; @@ -1709,9 +1743,10 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, event.touch.pointer_id = pointer_id; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -1719,6 +1754,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, 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; @@ -1726,9 +1762,10 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, event.touch.pointer_id = pointer_id; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -1737,6 +1774,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, 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; @@ -1746,43 +1784,50 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, event.wheel.y_delta = y_delta; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +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 void JNICALL +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 void JNICALL +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; } #ifdef __clang__ @@ -3599,6 +3644,15 @@ android_translate_coordinates (android_window src, int x, 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. */ @@ -3795,12 +3849,45 @@ android_get_keysym_name (int keysym, char *name_return, size_t size) 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_sync (void) +android_toggle_on_screen_keyboard (android_window window, bool show) { - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.sync); + 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 (); } @@ -3811,7 +3898,7 @@ android_sync (void) #undef faccessat /* Replace the system faccessat with one which understands AT_EACCESS. - Android's faccessat simply fails upon using AT_EACCESS, so repalce + 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 @@ -3829,7 +3916,7 @@ faccessat (int dirfd, const char *pathname, int mode, int flags) return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); } -#endif /* __ANDROID_API__ < 16 */ +#endif /* __ANDROID_API__ >= 17 */ diff --git a/src/android.h b/src/android.h index 240bc90d831..97818ab4911 100644 --- a/src/android.h +++ b/src/android.h @@ -91,6 +91,8 @@ extern void android_exception_check (void); extern void android_get_keysym_name (int, char *, size_t); extern void android_wait_event (void); +extern void android_toggle_on_screen_keyboard (android_window, bool); +extern void android_window_updated (android_window, unsigned long); diff --git a/src/androidfns.c b/src/androidfns.c index bb37c415069..77ee2e8de44 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -2332,6 +2332,30 @@ there is no mouse. */) #endif } +DEFUN ("android-toggle-on-screen-keyboard", + Fandroid_toggle_on_screen_keyboard, + Sandroid_toggle_on_screen_keyboard, 2, 2, 0, + doc: /* Display or hide the on-screen keyboard. +If HIDE is non-nil, hide the on screen keyboard if it is currently +being displayed. Else, request that the system display it on behalf +of FRAME. This request may be rejected if FRAME does not have the +input focus. */) + (Lisp_Object frame, Lisp_Object hide) +{ +#ifndef ANDROID_STUBIFY + struct frame *f; + + f = decode_window_system_frame (frame); + + block_input (); + android_toggle_on_screen_keyboard (FRAME_ANDROID_WINDOW (f), + NILP (hide)); + unblock_input (); +#endif + + return Qnil; +} + #ifndef ANDROID_STUBIFY @@ -2781,6 +2805,7 @@ syms_of_androidfns (void) defsubr (&Sx_show_tip); defsubr (&Sx_hide_tip); defsubr (&Sandroid_detect_mouse); + defsubr (&Sandroid_toggle_on_screen_keyboard); #ifndef ANDROID_STUBIFY tip_timer = Qnil; diff --git a/src/androidgui.h b/src/androidgui.h index 1f28c18ff34..3b9a74dc0b0 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -239,6 +239,7 @@ enum android_event_type struct android_any_event { enum android_event_type type; + unsigned long serial; android_window window; }; @@ -252,6 +253,7 @@ enum android_modifier_mask struct android_key_event { enum android_event_type type; + unsigned long serial; android_window window; android_time time; unsigned int state; @@ -271,6 +273,7 @@ struct android_key_event struct android_configure_event { enum android_event_type type; + unsigned long serial; android_window window; android_time time; int x, y; @@ -280,6 +283,7 @@ struct android_configure_event struct android_focus_event { enum android_event_type type; + unsigned long serial; android_window window; android_time time; }; @@ -287,6 +291,7 @@ struct android_focus_event struct android_window_action_event { enum android_event_type type; + unsigned long serial; /* The window handle. This can be ANDROID_NONE. */ android_window window; @@ -301,6 +306,7 @@ struct android_window_action_event struct android_crossing_event { enum android_event_type type; + unsigned long serial; android_window window; int x, y; unsigned long time; @@ -309,6 +315,7 @@ struct android_crossing_event struct android_motion_event { enum android_event_type type; + unsigned long serial; android_window window; int x, y; unsigned long time; @@ -317,6 +324,7 @@ struct android_motion_event struct android_button_event { enum android_event_type type; + unsigned long serial; android_window window; int x, y; unsigned long time; @@ -329,6 +337,9 @@ struct android_touch_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. */ android_window window; @@ -347,6 +358,9 @@ struct android_wheel_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. */ android_window window; @@ -368,6 +382,9 @@ struct android_iconify_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. */ android_window window; }; @@ -377,6 +394,9 @@ struct android_menu_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. Always None. */ android_window window; diff --git a/src/androidterm.c b/src/androidterm.c index 3c16b542d91..3024890d789 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -591,7 +591,17 @@ handle_one_android_event (struct android_display_info *dpyinfo, android_clear_under_internal_border (f); SET_FRAME_GARBAGED (f); cancel_mouse_face (f); + + /* Now stash the serial of this configure event somewhere, + and call android_window_updated with it once the redraw + completes. */ + FRAME_OUTPUT_DATA (f)->last_configure_serial + = configureEvent.xconfigure.serial; } + else + /* Reply to this ConfigureNotify event immediately. */ + android_window_updated (FRAME_ANDROID_WINDOW (f), + configureEvent.xconfigure.serial); goto OTHER; @@ -1194,6 +1204,9 @@ handle_one_android_event (struct android_display_info *dpyinfo, /* Iconification. This is vastly simpler than on X. */ case ANDROID_ICONIFIED: + if (!any) + goto OTHER; + if (FRAME_ICONIFIED_P (any)) goto OTHER; @@ -1206,6 +1219,9 @@ handle_one_android_event (struct android_display_info *dpyinfo, case ANDROID_DEICONIFIED: + if (!any) + goto OTHER; + if (!FRAME_ICONIFIED_P (any)) goto OTHER; @@ -1311,6 +1327,14 @@ android_frame_up_to_date (struct frame *f) /* The frame is now complete, as its contents have been drawn. */ FRAME_ANDROID_COMPLETE_P (f) = true; + /* If there was an outstanding configure event, then tell system + that the update has finished and the new contents can now be + displayed. */ + if (FRAME_OUTPUT_DATA (f)->last_configure_serial) + android_window_updated (FRAME_ANDROID_WINDOW (f), + FRAME_OUTPUT_DATA (f)->last_configure_serial); + FRAME_OUTPUT_DATA (f)->last_configure_serial = 0; + /* Shrink the scanline buffer used by the font backend. */ sfntfont_android_shrink_scanline_buffer (); unblock_input (); diff --git a/src/androidterm.h b/src/androidterm.h index c0f862e35fb..11b24d40b73 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -241,6 +241,11 @@ struct android_output /* List of all tools (either styluses or fingers) pressed onto the frame. */ struct android_touch_point *touch_points; + + /* Event serial of the last ConfigureNotify event received that has + not yet been drawn. This is used to synchronize resize with the + window system. 0 if no such outstanding event exists. */ + unsigned long last_configure_serial; }; enum -- cgit v1.3 From aaacf24ca25fc284038ec9f17be358067309a8cf Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 21 Jan 2023 20:03:37 +0800 Subject: Update Android port * doc/emacs/android.texi (Android File System): Document that ls-lisp is now used by default. * java/org/gnu/emacs/EmacsThread.java (EmacsThread): Name the thread something meaningful. * lisp/loadup.el (featurep): Load ls-lisp on Android. * lisp/ls-lisp.el (ls-lisp-use-insert-directory-program): Default to off on Android. * src/android.c (android_is_directory): New fucntion. (android_fstatat): Handle directories created by `android_opendir'. (android_open): Return meaningful file mode. (struct android_dir): New fields `next', `asset_file' and `fd'. (android_opendir): Populate those fields. (android_dirfd): New function. (android_closedir): Close file descriptor if set. (android_lookup_asset_directory_fd): New function. * src/android.h: Update prototypes. * src/androidfont.c (androidfont_check_init): New function. (androidfont_list, androidfont_match, androidfont_draw) (androidfont_open_font, androidfont_close_font) (androidfont_has_char, androidfont_encode_char) (androidfont_text_extents, androidfont_list_family): Initialize font driver if necessary. (init_androidfont): Don't initialize Java font if necessary. * src/dired.c (open_directory): Return android_dirfd if appropriate. (directory_files_internal, file_name_completion_dirp): Implement correctly for Android. * src/fileio.c (check_mutable_filename): New function. (Fcopy_file, Fdelete_directory_internal, Fdelete_file) (Frename_file, Fadd_name_to_file, Fmake_symbolic_link) (Fset_file_modes, Fset_file_times, Ffile_newer_than_file_p) (Fverify_visited_file_modtime, Fset_visited_file_modtime): Check that files being written to do not lie in /assets. * src/sfntfont-android.c (GET_SCANLINE_BUFFER) (sfntfont_android_u255to256, sfntfont_android_over_8888_1) (sfntfont_android_over_8888, sfntfont_android_composite_bitmap): Optimize on 64-bit ARM devices. (sfntfont_android_put_glyphs): Optimize away memset if background need not be filled. --- doc/emacs/android.texi | 21 ++-- java/org/gnu/emacs/EmacsThread.java | 1 + lisp/loadup.el | 1 + lisp/ls-lisp.el | 2 +- src/android.c | 168 ++++++++++++++++++++++++++++++- src/android.h | 1 + src/androidfont.c | 85 ++++++++++++---- src/dired.c | 13 +++ src/fileio.c | 50 ++++++++- src/sfntfont-android.c | 196 ++++++++++++++++++++++++++++++++++-- 10 files changed, 493 insertions(+), 45 deletions(-) (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index ba5d2ca3e09..223e9dbf8ef 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -99,21 +99,26 @@ this varies by device. Emacs exposes a special directory on Android systems: the name of the directory is @file{/assets}, and it contains the @file{etc}, @file{lisp} and @file{info} directories which are normally installed -in @file{/usr/share/emacs} directory on GNU and Unix systems. +in @file{/usr/share/emacs} directory on GNU and Unix systems. On +Android systems, the Lisp emulation of @command{ls} (@pxref{ls in +Lisp}) is also enabled by default, as the @command{ls} binary which +comes with the system varies by manufacturer and usually does not +support all of the features required by Emacs. One copy of +@command{ls} shipped with some Android devices is even known to lack +support for the @code{-l} flag. @cindex limitations of the /assets directory -This directory exists because Android does not extract the contents of -application packages on to the file system while unpacking them, but -instead requires programs like Emacs to access its contents using a -special ``asset manager'' interface. Here are the peculiarities that -result from such an implementation: + This directory exists because Android does not extract the contents +of application packages on to the file system while unpacking them, +but instead requires programs like Emacs to access its contents using +a special ``asset manager'' interface. Here are the peculiarities +that result from such an implementation: @itemize @bullet @item Subprocesses (such as @command{ls}) can not run from the -@file{/assets} directory, so Dired, and functions such as -@code{list-directory} do not work. +@file{/assets} directory. @item There are no @file{.} and @file{..} directories inside the diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index f9bc132f354..21d8612703a 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -29,6 +29,7 @@ public class EmacsThread extends Thread public EmacsThread (EmacsService service, boolean startDashQ) { + super ("Emacs main thread"); this.startDashQ = startDashQ; } diff --git a/lisp/loadup.el b/lisp/loadup.el index 3b48d5fe1b5..b9ae8212d2d 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -312,6 +312,7 @@ (if (featurep 'android) (progn + (load "ls-lisp") (load "term/common-win") (load "term/android-win"))) diff --git a/lisp/ls-lisp.el b/lisp/ls-lisp.el index 81ff14932c6..8f5948cefb9 100644 --- a/lisp/ls-lisp.el +++ b/lisp/ls-lisp.el @@ -184,7 +184,7 @@ if emulation is GNU then default is `(links uid gid)'." :group 'ls-lisp) (defcustom ls-lisp-use-insert-directory-program - (not (memq system-type '(ms-dos windows-nt))) + (not (memq system-type '(ms-dos windows-nt android))) "Non-nil causes ls-lisp to revert back to using `insert-directory-program'. This is useful on platforms where ls-lisp is dumped into Emacs, such as Microsoft Windows, but you would still like to use a program to list diff --git a/src/android.c b/src/android.c index 3f6e6f4576b..a51d0518484 100644 --- a/src/android.c +++ b/src/android.c @@ -852,6 +852,26 @@ android_scan_directory_tree (char *file, size_t *limit_return) 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 @@ -899,8 +919,15 @@ android_fstat (int fd, struct stat *statb) 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. */ + 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, @@ -908,11 +935,40 @@ android_fstatat (int dirfd, const char *restrict pathname, { 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); @@ -923,7 +979,7 @@ android_fstatat (int dirfd, const char *restrict pathname, memset (statbuf, 0, sizeof *statbuf); /* Fill in the stat buffer. */ - statbuf->st_mode = S_IFREG; + statbuf->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; statbuf->st_size = AAsset_getLength (asset_desc); /* Close the asset. */ @@ -1118,7 +1174,8 @@ android_open (const char *filename, int oflag, int mode) /* Fill in some information that will be reported to callers of android_fstat, among others. */ - android_table[fd].statb.st_mode = S_IFREG; + android_table[fd].statb.st_mode + = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;; /* Owned by root. */ android_table[fd].statb.st_uid = 0; @@ -4023,8 +4080,21 @@ struct android_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. */ @@ -4034,7 +4104,7 @@ android_opendir (const char *name) struct android_dir *dir; char *asset_dir; const char *asset_name; - size_t limit; + size_t limit, length; asset_name = android_get_asset_name (name); @@ -4052,10 +4122,19 @@ android_opendir (const char *name) 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. */ @@ -4069,6 +4148,9 @@ android_opendir (const char *name) errno = EACCES; } + dir->next = android_dirs; + android_dirs = dir; + return dir; } @@ -4086,6 +4168,26 @@ android_opendir (const char *name) 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 * @@ -4152,8 +4254,29 @@ android_readdir (struct android_dir *dir) 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. */ @@ -4161,6 +4284,43 @@ android_closedir (struct android_dir *dir) 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 diff --git a/src/android.h b/src/android.h index 52b3412fb0e..eee3a7a6498 100644 --- a/src/android.h +++ b/src/android.h @@ -101,6 +101,7 @@ extern void android_window_updated (android_window, unsigned long); struct android_dir; extern struct android_dir *android_opendir (const char *); +extern int android_dirfd (struct android_dir *); extern struct dirent *android_readdir (struct android_dir *); extern void android_closedir (struct android_dir *); diff --git a/src/androidfont.c b/src/androidfont.c index b0a9f994a85..bec5a59ca77 100644 --- a/src/androidfont.c +++ b/src/androidfont.c @@ -384,6 +384,41 @@ androidfont_get_cache (struct frame *frame) return font_cache; } +/* Initialize the Java side of the font driver if it has not already + been initialized. This is only done whenever necessary because the + font driver otherwise uses a lot of memory, as it has to keep every + typeface open. */ + +static void +androidfont_check_init (void) +{ + jmethodID method; + jobject old; + + if (font_driver) + return; + + /* Log a loud message. This font driver really should not be + used. */ + __android_log_print (ANDROID_LOG_WARN, __func__, + "The Android font driver is being used." + " Please investigate why this is so."); + + method = font_driver_class.create_font_driver; + + /* Initialize the font driver on the Java side. */ + font_driver + = (*android_java_env)->CallStaticObjectMethod (android_java_env, + font_driver_class.class, + method); + android_exception_check (); + + old = font_driver; + font_driver + = (*android_java_env)->NewGlobalRef (android_java_env, font_driver); + ANDROID_DELETE_LOCAL_REF (old); +} + /* Return a local reference to an instance of EmacsFontDriver$FontSpec with the same values as FONT. */ @@ -539,6 +574,9 @@ androidfont_list (struct frame *f, Lisp_Object font_spec) Lisp_Object value, entity; struct androidfont_entity *info; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + spec = androidfont_from_lisp (font_spec); array = (*android_java_env)->CallObjectMethod (android_java_env, font_driver, @@ -595,6 +633,9 @@ androidfont_match (struct frame *f, Lisp_Object font_spec) Lisp_Object entity; struct androidfont_entity *info; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + spec = androidfont_from_lisp (font_spec); result = (*android_java_env)->CallObjectMethod (android_java_env, font_driver, @@ -635,6 +676,9 @@ androidfont_draw (struct glyph_string *s, int from, int to, int rc; jobject gcontext, drawable; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + verify (sizeof (unsigned int) == sizeof (jint)); info = (struct androidfont_info *) s->font; @@ -683,6 +727,9 @@ androidfont_open_font (struct frame *f, Lisp_Object font_entity, jobject old; jint value; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + if (XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX)) != 0) pixel_size = XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX)); else if (pixel_size == 0) @@ -778,6 +825,9 @@ androidfont_close_font (struct font *font) struct androidfont_info *info; int i; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + info = (struct androidfont_info *) font; /* Free the font metrics cache if it exists. */ @@ -805,6 +855,9 @@ androidfont_has_char (Lisp_Object font, int c) struct androidfont_info *info; struct androidfont_entity *entity; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + if (FONT_ENTITY_P (font)) { entity = (struct androidfont_entity *) XFONT_ENTITY (font); @@ -830,6 +883,9 @@ androidfont_encode_char (struct font *font, int c) { struct androidfont_info *info; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + info = (struct androidfont_info *) font; return (*android_java_env)->CallIntMethod (android_java_env, @@ -891,6 +947,9 @@ androidfont_text_extents (struct font *font, const unsigned int *code, jobject metrics_object; short value; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + info = (struct androidfont_info *) font; if (nglyphs == 1 @@ -968,6 +1027,9 @@ androidfont_list_family (struct frame *f) jsize i, length; const char *family; + /* Maybe initialize the font driver. */ + androidfont_check_init (); + family_array = (*android_java_env)->CallObjectMethod (android_java_env, font_driver, @@ -1042,33 +1104,14 @@ syms_of_androidfont (void) void init_androidfont (void) { - jmethodID method; - jobject old; - android_init_font_driver (); android_init_font_spec (); android_init_font_metrics (); android_init_font_object (); android_init_integer (); - method = font_driver_class.create_font_driver; - - /* Initialize the font driver on the Java side. */ - font_driver - = (*android_java_env)->CallStaticObjectMethod (android_java_env, - font_driver_class.class, - method); - - if (!font_driver) - memory_full (0); - - old = font_driver; - font_driver - = (*android_java_env)->NewGlobalRef (android_java_env, font_driver); - ANDROID_DELETE_LOCAL_REF (old); - - if (!font_driver) - memory_full (0); + /* The Java font driver is not initialized here because it uses a lot + of memory. */ } void diff --git a/src/dired.c b/src/dired.c index 57d79b84463..ced08a35643 100644 --- a/src/dired.c +++ b/src/dired.c @@ -116,6 +116,9 @@ open_directory (Lisp_Object dirname, Lisp_Object encoded_dirname, int *fdp) d = opendir (name); #else d = android_opendir (name); + + if (d) + fd = android_dirfd (d); #endif opendir_errno = errno; #else @@ -216,6 +219,9 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, Lisp_Object encoded_dirfilename = ENCODE_FILE (dirfilename); int fd; + + /* Keep in mind that FD is not always a real file descriptor on + Android. */ emacs_dir *d = open_directory (dirfilename, encoded_dirfilename, &fd); /* Unfortunately, we can now invoke expand-file-name and @@ -881,6 +887,13 @@ file_name_completion_dirp (int fd, struct dirent *dp, ptrdiff_t len) char *subdir_name = SAFE_ALLOCA (len + 2); memcpy (subdir_name, dp->d_name, len); strcpy (subdir_name + len, "/"); + +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + /* Check if subdir_name lies in the assets directory. */ + if (android_file_access_p (subdir_name, F_OK)) + return true; +#endif + bool dirp = faccessat (fd, subdir_name, F_OK, AT_EACCESS) == 0; SAFE_FREE (); return dirp; diff --git a/src/fileio.c b/src/fileio.c index b5a79312d88..218c2b01cd2 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -144,6 +144,25 @@ static bool a_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t, static bool e_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t, struct coding_system *); + + +/* Check that ENCODED does not lie on any special directory whose + contents are read only. Signal a `file-error' if it does. */ + +static void +check_mutable_filename (Lisp_Object encoded) +{ +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + if (!strcmp (SSDATA (encoded), "/assets") + || !strncmp (SSDATA (encoded), "/assets/", + sizeof "/assets/" - 1)) + xsignal2 (Qfile_error, + build_string ("File lies on read-" + "only directory"), + encoded); +#endif +} + /* Test whether FILE is accessible for AMODE. Return true if successful, false (setting errno) otherwise. */ @@ -2215,6 +2234,7 @@ permissions. */) encoded_file = ENCODE_FILE (file); encoded_newname = ENCODE_FILE (newname); + check_mutable_filename (encoded_newname); #ifdef WINDOWSNT if (NILP (ok_if_already_exists) @@ -2474,6 +2494,8 @@ DEFUN ("delete-directory-internal", Fdelete_directory_internal, encoded_dir = ENCODE_FILE (directory); dir = SSDATA (encoded_dir); + check_mutable_filename (encoded_dir); + if (rmdir (dir) != 0) report_file_error ("Removing directory", directory); @@ -2513,6 +2535,7 @@ With a prefix argument, TRASH is nil. */) return call1 (Qmove_file_to_trash, filename); encoded_file = ENCODE_FILE (filename); + check_mutable_filename (encoded_file); if (unlink (SSDATA (encoded_file)) != 0 && errno != ENOENT) report_file_error ("Removing old name", filename); @@ -2670,6 +2693,8 @@ This is what happens in interactive use with M-x. */) encoded_file = ENCODE_FILE (file); encoded_newname = ENCODE_FILE (newname); + check_mutable_filename (encoded_file); + check_mutable_filename (encoded_newname); bool plain_rename = (case_only_rename || (!NILP (ok_if_already_exists) @@ -2781,6 +2806,8 @@ This is what happens in interactive use with M-x. */) encoded_file = ENCODE_FILE (file); encoded_newname = ENCODE_FILE (newname); + check_mutable_filename (encoded_file); + check_mutable_filename (encoded_newname); if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0) return Qnil; @@ -2834,6 +2861,8 @@ This happens for interactive use with M-x. */) encoded_target = ENCODE_FILE (target); encoded_linkname = ENCODE_FILE (linkname); + check_mutable_filename (encoded_target); + check_mutable_filename (encoded_linkname); if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0) return Qnil; @@ -3565,6 +3594,8 @@ Interactively, prompt for FILENAME, and read MODE with command from GNU Coreutils. */) (Lisp_Object filename, Lisp_Object mode, Lisp_Object flag) { + Lisp_Object encoded; + CHECK_FIXNUM (mode); int nofollow = symlink_nofollow_flag (flag); Lisp_Object absname = Fexpand_file_name (filename, @@ -3576,7 +3607,9 @@ command from GNU Coreutils. */) if (!NILP (handler)) return call4 (handler, Qset_file_modes, absname, mode, flag); - char *fname = SSDATA (ENCODE_FILE (absname)); + encoded = ENCODE_FILE (absname); + check_mutable_filename (encoded); + char *fname = SSDATA (encoded); mode_t imode = XFIXNUM (mode) & 07777; if (fchmodat (AT_FDCWD, fname, imode, nofollow) != 0) report_file_error ("Doing chmod", absname); @@ -3648,6 +3681,7 @@ TIMESTAMP is in the format of `current-time'. */) return call4 (handler, Qset_file_times, absname, timestamp, flag); Lisp_Object encoded_absname = ENCODE_FILE (absname); + check_mutable_filename (encoded_absname); if (utimensat (AT_FDCWD, SSDATA (encoded_absname), ts, nofollow) != 0) { @@ -3680,6 +3714,7 @@ otherwise, if FILE2 does not exist, the answer is t. */) (Lisp_Object file1, Lisp_Object file2) { struct stat st1, st2; + Lisp_Object encoded; CHECK_STRING (file1); CHECK_STRING (file2); @@ -3696,8 +3731,11 @@ otherwise, if FILE2 does not exist, the answer is t. */) if (!NILP (handler)) return call3 (handler, Qfile_newer_than_file_p, absname1, absname2); + encoded = ENCODE_FILE (absname1); + check_mutable_filename (encoded); + int err1; - if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname1)), &st1, 0) == 0) + if (emacs_fstatat (AT_FDCWD, SSDATA (encoded), &st1, 0) == 0) err1 = 0; else { @@ -5853,6 +5891,7 @@ See Info node `(elisp)Modification Time' for more details. */) return call2 (handler, Qverify_visited_file_modtime, buf); filename = ENCODE_FILE (BVAR (b, filename)); + check_mutable_filename (filename); mtime = (emacs_fstatat (AT_FDCWD, SSDATA (filename), &st, 0) == 0 ? get_stat_mtime (&st) @@ -5913,7 +5952,7 @@ in `current-time' or an integer flag as returned by `visited-file-modtime'. */) error ("An indirect buffer does not have a visited file"); else { - register Lisp_Object filename; + register Lisp_Object filename, encoded; struct stat st; Lisp_Object handler; @@ -5926,7 +5965,10 @@ in `current-time' or an integer flag as returned by `visited-file-modtime'. */) /* The handler can find the file name the same way we did. */ return call2 (handler, Qset_visited_file_modtime, Qnil); - if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (filename)), &st, 0) + encoded = ENCODE_FILE (filename); + check_mutable_filename (encoded); + + if (emacs_fstatat (AT_FDCWD, SSDATA (encoded), &st, 0) == 0) { current_buffer->modtime = get_stat_mtime (&st); diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 37fd81953f6..5c9835fb13e 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -22,8 +22,14 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include +#include + +#ifdef __aarch64__ +#include +#endif #include +#include #include "androidterm.h" #include "sfntfont.h" @@ -63,6 +69,8 @@ static size_t max_scanline_buffer_size; /* Return a temporary buffer for storing scan lines. Set BUFFER to the buffer upon success. */ +#ifndef __aarch64__ + #define GET_SCANLINE_BUFFER(buffer, height, stride) \ do \ { \ @@ -94,6 +102,39 @@ static size_t max_scanline_buffer_size; } \ } while (false); +#else + +#define GET_SCANLINE_BUFFER(buffer, height, stride) \ + do \ + { \ + size_t _size; \ + void *_temp; \ + \ + if (INT_MULTIPLY_WRAPV (height, stride, &_size)) \ + memory_full (SIZE_MAX); \ + \ + if (_size > scanline_buffer.buffer_size) \ + { \ + if (posix_memalign (&_temp, 16, _size)) \ + memory_full (_size); \ + free (scanline_buffer.buffer_data); \ + (buffer) \ + = scanline_buffer.buffer_data \ + = _temp; \ + scanline_buffer.buffer_size = _size; \ + } \ + else if (_size <= scanline_buffer.buffer_size) \ + (buffer) = scanline_buffer.buffer_data; \ + /* This is unreachable but clang says it is. */ \ + else \ + emacs_abort (); \ + \ + max_scanline_buffer_size \ + = max (_size, max_scanline_buffer_size); \ + } while (false); + +#endif + /* Scale each of the four packed bytes in P in the low 16 bits of P by @@ -149,6 +190,122 @@ sfntfont_android_blend (unsigned int src, unsigned int dst) return both + src; } +#ifdef __aarch64__ + +/* Like U255TO256, but operates on vectors. */ + +static uint16x8_t +sfntfont_android_u255to256 (uint8x8_t in) +{ + return vaddl_u8 (vshr_n_u8 (in, 7), in); +} + +/* Use processor features to efficiently composite four pixels at SRC + to DST. */ + +static void +sfntfont_android_over_8888_1 (unsigned int *src, unsigned int *dst) +{ + uint8x8_t alpha; + uint16x8_t alpha_c16, v1, v3, v4; + uint8x8_t b, g, r, a, v2, v5; + uint8x8x4_t _src, _dst; + + /* Pull in src and dst. + + This loads bytes, not words, so little endian ABGR becomes + RGBA. */ + _src = vld4_u8 ((const uint8_t *) src); + _dst = vld4_u8 ((const uint8_t *) dst); + + /* Load constants. */ + v4 = vdupq_n_u16 (256); + v5 = vdup_n_u8 (0); + + /* Load src alpha. */ + alpha = _src.val[3]; + + /* alpha_c16 = 256 - 255TO256 (alpha). */ + alpha_c16 = sfntfont_android_u255to256 (alpha); + alpha_c16 = vsubq_u16 (v4, alpha_c16); + + /* Cout = Csrc + Cdst * alpha_c. */ + v1 = vaddl_u8 (_dst.val[2], v5); + v2 = _src.val[2]; + v3 = vmulq_u16 (v1, alpha_c16); + b = vqadd_u8 (v2, vshrn_n_u16 (v3, 8)); + + v1 = vaddl_u8 (_dst.val[1], v5); + v2 = _src.val[1]; + v3 = vmulq_u16 (v1, alpha_c16); + g = vqadd_u8 (v2, vshrn_n_u16 (v3, 8)); + + v1 = vaddl_u8 (_dst.val[0], v5); + v2 = _src.val[0]; + v3 = vmulq_u16 (v1, alpha_c16); + r = vqadd_u8 (v2, vshrn_n_u16 (v3, 8)); + +#if 0 + /* Aout = Asrc + Adst * alpha_c. */ + v1 = vaddl_u8 (_dst.val[3], v5); + v2 = _src.val[3]; + v3 = vmulq_u16 (v1, alpha_c16); + a = vqadd_u8 (v2, vshrn_n_u16 (v3, 8)); +#else + /* We know that Adst is always 1, so Asrc + Adst * (1 - Asrc) is + always 1. */ + a = vdup_n_u8 (255); +#endif + + /* Store back in dst. */ + _dst.val[0] = r; + _dst.val[1] = g; + _dst.val[2] = b; + _dst.val[3] = a; + vst4_u8 ((uint8_t *) dst, _dst); +} + +/* Use processor features to efficiently composite the buffer at SRC + to DST. Composite at most MAX - SRC words. + + If either SRC or DST are not yet properly aligned, value is 1. + Otherwise, value is 0, and *X is incremented to the start of any + trailing data which could not be composited due to data alignment + constraints. */ + +static int +sfntfont_android_over_8888 (unsigned int *src, unsigned int *dst, + unsigned int *max, unsigned int *x) +{ + size_t i; + ptrdiff_t how_much; + void *s, *d; + + /* Figure out how much can be composited by this loop. */ + how_much = (max - src) & ~7; + + /* Return if there is not enough to vectorize. */ + if (!how_much) + return 1; + + /* Now increment *X by that much so the containing loop can process + the remaining pixels one-by-one. */ + + *x += how_much; + + for (i = 0; i < how_much; i += 8) + { + s = (src + i); + d = (dst + i); + + sfntfont_android_over_8888_1 (s, d); + } + + return 0; +} + +#endif + /* Composite the bitmap described by BUFFER, STRIDE and TEXT_RECTANGLE onto the native-endian ABGR8888 bitmap described by DEST and BITMAP_INFO. RECT is the subset of the bitmap to composite. */ @@ -163,7 +320,7 @@ sfntfont_android_composite_bitmap (unsigned char *restrict buffer, { unsigned int *src_row; unsigned int *dst_row; - unsigned int i, src_y, x, src_x, max_x, dst_x; + unsigned int i, src_y, x, src_x, max_x, dst_x, lim_x; if ((intptr_t) dest & 3 || bitmap_info->stride & 3) /* This shouldn't be possible as Android is supposed to align the @@ -196,9 +353,24 @@ sfntfont_android_composite_bitmap (unsigned char *restrict buffer, src_x = x + (rect->x - text_rectangle->x); dst_x = x + rect->x; - dst_row[dst_x] - = sfntfont_android_blend (src_row[src_x], - dst_row[dst_x]); + /* This is the largest value of src_x. */ + lim_x = max_x + (rect->x - text_rectangle->x); + +#ifdef __aarch64__ + if (!sfntfont_android_over_8888 (src_row + src_x, + dst_row + dst_x, + src_row + lim_x, + &x)) + { + /* Decrement X by one so the for loop can increment + it again. */ + x--; + continue; + } +#endif + dst_row[dst_x] + = sfntfont_android_blend (src_row[src_x], + dst_row[dst_x]); } } } @@ -308,11 +480,21 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, gui_union_rectangles (&background, &text_rectangle, &text_rectangle); - /* Allocate enough to hold text_rectangle.height, aligned to 8 - bytes. Then fill it with the background. */ + /* Allocate enough to hold text_rectangle.height, aligned to 8 (or + 16) bytes. Then fill it with the background. */ +#ifndef __aarch64__ stride = ((text_rectangle.width * sizeof *buffer) + 7) & ~7; +#else + stride = ((text_rectangle.width * sizeof *buffer) + 15) & ~15; +#endif GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride); - memset (buffer, 0, text_rectangle.height * stride); + + /* Try to optimize out this memset if the background rectangle + contains the whole text rectangle. */ + + if (!with_background || memcmp (&background, &text_rectangle, + sizeof text_rectangle)) + memset (buffer, 0, text_rectangle.height * stride); if (with_background) { -- cgit v1.3 From 56e55a80080f78754db6f385d574d17c3631ee30 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 24 Jan 2023 17:31:16 +0800 Subject: 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. --- INSTALL.android | 37 +++++- build-aux/ndk-build-helper-1.mk | 2 +- configure.ac | 3 +- cross/ndk-build/ndk-build-shared-library.mk | 23 +++- cross/ndk-build/ndk-build-static-library.mk | 14 ++- cross/ndk-build/ndk-resolve.mk | 47 ++++++++ doc/emacs/android.texi | 45 ++++--- java/Makefile.in | 3 +- java/README | 8 +- java/org/gnu/emacs/EmacsApplication.java | 54 ++++++++- java/org/gnu/emacs/EmacsNative.java | 4 + java/org/gnu/emacs/EmacsPreferencesActivity.java | 27 +++++ java/org/gnu/emacs/EmacsService.java | 25 ++++ java/org/gnu/emacs/EmacsThread.java | 19 ++- lisp/loadup.el | 146 ++++++++++++++--------- lisp/net/browse-url.el | 22 ++++ m4/ndk-build.m4 | 2 +- src/android.c | 61 ++++++++++ src/android.h | 4 + src/androidselect.c | 20 ++++ src/emacs.c | 29 +++++ src/pdumper.c | 18 +++ src/sfntfont-android.c | 7 +- 23 files changed, 518 insertions(+), 102 deletions(-) create mode 100644 cross/ndk-build/ndk-resolve.mk (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/INSTALL.android b/INSTALL.android index 9abea74a55d..79366425125 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -148,6 +148,14 @@ work: (You must add LOCAL_EXPORT_CFLAGS := -I$(LOCAL_PATH) before its Android.mk includes $(BUILD_STATIC_LIBRARY)) +In addition, some Emacs dependencies provide `ndk-build' support +themselves: + + libjansson - https://github.com/akheron/jansson + (You must add LOCAL_EXPORT_INCLUDES := $(LOCAL_C_INCLUDES) before + its Android.mk includes $(BUILD_SHARED_LIBRARY), then copy + android/jansson_config.h to android/jansson_private_config.h.) + We anticipate that most untested non-trivial ndk-build dependencies will need adjustments in Emacs to work, as the Emacs build system which emulates ndk-build is in an extremely early state. @@ -173,7 +181,7 @@ one awk script in build-awx, run during configure: build-aux/ndk-module-extract.awk -six Makefiles in cross/ndk-build, +seven Makefiles in cross/ndk-build, cross/ndk-build/ndk-build-shared-library.mk cross/ndk-build/ndk-build-static-library.mk @@ -181,6 +189,7 @@ six Makefiles in cross/ndk-build, cross/ndk-build/ndk-clear-vars.mk cross/ndk-build/ndk-prebuilt-shared-library.mk cross/ndk-build/ndk-prebuilt-static-library.mk + cross/ndk-build/ndk-resolve.mk and finally, two more Makefiles in cross/ndk-build, generated by configure: @@ -373,11 +382,27 @@ them. The name under which the module is linked is the same as the Make target found on the sixth line of output from build-aux/ndk-build-helper.mk. -However, none of the Makefiles in -cross/ndk-build/ndk-build-shared-library.mk perform any kind of -dependency resolution! Instead, they only define rules to build -individual modules, leaving dependency resolution up to the Makefiles -in the `build-aux' directory. +While the rules defined by the Makefiles in cross/ndk-build do not +have their dependencies as prerequisites (with the assumption that the +ndk-build.m4 and the Makefiles in build-aux have already added all of +the necessary targets to ndk-build.mk), dependency resolution is still +performed, as the CFLAGS and includes from dependencies must be +appended to the module's CFLAGS. + +This is done by including cross/ndk-build/ndk-resolve.mk each time a +shared or static library module is going to be built. How is this +done? + +First, ndk-resolve.mk saves the LOCAL_PATH, LOCAL_STATIC_LIBRARIES, +LOCAL_SHARED_LIBRARIES, LOCAL_EXPORT_CFLAGS and +LOCAL_EXPORT_C_INCLUDES from the module. + +Next, ndk-resolve loops through the dependencies the module has +specified, appending its CFLAGS and includes to the command line for +the current module. + +Then, that process is repeated for each such dependency which has not +already been resolved, until all dependencies have been resolved. libpng is a very simple module, providing only a single shared object module. This module is named libpng_emacs.so and is eventually built diff --git a/build-aux/ndk-build-helper-1.mk b/build-aux/ndk-build-helper-1.mk index fb8e4da2f0a..d20f7d4472f 100644 --- a/build-aux/ndk-build-helper-1.mk +++ b/build-aux/ndk-build-helper-1.mk @@ -33,7 +33,7 @@ NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES) $(info Building $(build_kind)) $(info $(LOCAL_MODULE)) -$(info $(addprefix $(ANDROID_MODULE_DIRECTORY)?,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) +$(info $(addprefix $(ANDROID_MODULE_DIRECTORY)/,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) $(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES),-I$(dir))) $(info $(LOCAL_EXPORT_CFLAGS)) diff --git a/configure.ac b/configure.ac index 9df4a6a073f..f3a4b4301a4 100644 --- a/configure.ac +++ b/configure.ac @@ -1045,6 +1045,7 @@ package will likely install on older systems but crash on startup.]) passthrough="$passthrough --with-png=$with_png" passthrough="$passthrough --with-webp=$with_webp" passthrough="$passthrough --with-gif=$with_gif" + passthrough="$passthrough --with-json=$with_json" AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ ANDROID_SDK="$android_sdk" android_abi=$android_abi \ @@ -1112,6 +1113,7 @@ if test "$ANDROID" = "yes"; then with_png=no with_webp=no with_gif=no + with_json=no fi with_xml2=no @@ -1121,7 +1123,6 @@ if test "$ANDROID" = "yes"; then with_libsystemd=no with_cairo=no with_imagemagick=no - with_json=no with_tree_sitter=no with_xft=no with_harfbuzz=no diff --git a/cross/ndk-build/ndk-build-shared-library.mk b/cross/ndk-build/ndk-build-shared-library.mk index 0d90d89e55d..c4fb523a0c1 100644 --- a/cross/ndk-build/ndk-build-shared-library.mk +++ b/cross/ndk-build/ndk-build-shared-library.mk @@ -44,11 +44,13 @@ ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename $(1 endef -NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(addprefix $(LOCAL_PATH),$(LOCAL_C_INCLUDES))) -NDK_CFLAGS_$(LOCAL_MODULE) ::= -fPIC -iquote $(LOCAL_EXPORT_CFLAGS) $(LOCAL_PATH) $(LOCAL_CFLAGS) -NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) -NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDFLAGS) -ALL_OBJECT_FILES_$(LOCAL_MODULE) := +# Make sure to not add a prefix to local includes that already specify +# $(LOCAL_PATH). +NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(LOCAL_C_INCLUDES)) +NDK_CFLAGS_$(LOCAL_MODULE) += -fPIC -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS$(NDK_BUILD_ARCH)) +NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) +NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS) +ALL_OBJECT_FILES$(LOCAL_MODULE) := ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm) NDK_CFLAGS ::= -marm @@ -74,8 +76,17 @@ endif LOCAL_MODULE_FILENAME := $(LOCAL_MODULE_FILENAME).so +# Record this module's dependencies and exported includes and CFLAGS, +# and then add that of its dependencies. + +include ndk-resolve.mk + # Then define rules to build all objects. -ALL_SOURCE_FILES = $(LOCAL_SRC_FILES) +ALL_SOURCE_FILES = $(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES_$(NDK_BUILD_ARCH)) + +# This defines all dependencies. +ALL_OBJECT_FILES$(LOCAL_MODULE) = + $(foreach source,$(ALL_SOURCE_FILES),$(eval $(call single-object-target,$(source)))) # Now define the rule to build the shared library. diff --git a/cross/ndk-build/ndk-build-static-library.mk b/cross/ndk-build/ndk-build-static-library.mk index 1423cf243cf..2eddb215429 100644 --- a/cross/ndk-build/ndk-build-static-library.mk +++ b/cross/ndk-build/ndk-build-static-library.mk @@ -43,11 +43,10 @@ endif ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename $(1))) endef -NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(addprefix $(LOCAL_PATH),$(LOCAL_C_INCLUDES))) -NDK_CFLAGS_$(LOCAL_MODULE) ::= -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) -NDK_ASFLAGS_$(LOCAL_MODULE) ::= $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) -NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) -NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDFLAGS) +NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(LOCAL_C_INCLUDES)) +NDK_CFLAGS_$(LOCAL_MODULE) += -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) +NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) +NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS) ALL_OBJECT_FILES$(LOCAL_MODULE) := ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm) @@ -70,6 +69,11 @@ endif LOCAL_MODULE_FILENAME := $(LOCAL_MODULE_FILENAME).a +# Record this module's dependencies and exported includes and CFLAGS, +# and then add that of its dependencies. + +include ndk-resolve.mk + # Then define rules to build all objects. ALL_SOURCE_FILES = $(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES_$(NDK_BUILD_ARCH)) diff --git a/cross/ndk-build/ndk-resolve.mk b/cross/ndk-build/ndk-resolve.mk new file mode 100644 index 00000000000..29dd5c9908d --- /dev/null +++ b/cross/ndk-build/ndk-resolve.mk @@ -0,0 +1,47 @@ +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +# Save information. +NDK_LOCAL_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH) +NDK_LOCAL_STATIC_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES) +NDK_LOCAL_SHARED_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_SHARED_LIBRARIES) +NDK_LOCAL_EXPORT_CFLAGS_$(LOCAL_MODULE) := $(LOCAL_EXPORT_CFLAGS) +NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE) := $(LOCAL_EXPORT_C_INCLUDES) + +# List of all dependencies resolved for this module thus far. +# Used to avoid infinite recursion. +NDK_RESOLVED_$(LOCAL_MODULE) := + +define ndk-resolve + +ifeq ($(patsubst $(1),,$(NDK_RESOLVED$(LOCAL_MODULE))),$(NDK_RESOLVED$(LOCAL_MODULE))) +NDK_RESOLVED$(LOCAL_MODULE) += $(1) +NDK_CFLAGS_$(LOCAL_MODULE) += $(NDK_LOCAL_EXPORT_CFLAGS_$(1)) +NDK_CFLAGS_$(LOCAL_MODULE) += $(addprefix -I,$(NDK_LOCAL_EXPORT_C_INCLUDES_$(1))) + +$$(foreach module,$$(NDK_LOCAL_STATIC_LIBRARIES_$(1)),$$(eval $$(call ndk-resolve,$$(module)))) +$$(foreach module,$$(NDK_LOCAL_SHARED_LIBRARIES_$(1)),$$(eval $$(call ndk-resolve,$$(module)))) +endif + +endef + +$(foreach module,$(LOCAL_SHARED_LIBRARIES),$(eval $(call ndk-resolve,$(module)))) +$(foreach module,$(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES),$(eval $(call ndk-resolve,$(module)))) diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index e4498da401d..cda0eadf226 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -63,18 +63,18 @@ package manager. Instead, Emacs is compiled for Android on a different operating system, with the resulting binaries packaged into an archive, that is then transferred to the device and unpacked. -After being unpacked, Emacs instructs the system to display an + After being unpacked, Emacs instructs the system to display an application icon on the desktop. Emacs then starts up once the application icon is clicked. @cindex ``adb logcat'' -During startup, Emacs will display messages in the system log buffer; -reading that buffer requires the Android Debug Bridge (@code{adb}) -utility to be installed on another computer; it cannot be read on the -computer running Android itself. + During startup, Emacs will display messages in the system log +buffer; reading that buffer requires the Android Debug Bridge +(@code{adb}) utility to be installed on another computer; it cannot be +read on the computer running Android itself. -After enabling the ``USB Debugging'' feature on the Android system, + After enabling the ``USB Debugging'' feature on the Android system, and connecting it via USB to another system with the @code{adb} utility installed, the log can be viewed by running the following command on that other system: @@ -84,14 +84,31 @@ $ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)" @end example @cindex emacs -Q, android -Since Android has no command line, there is normally no way to specify -command-line arguments. However, Emacs can be started with the -equivalent of the @code{--quick} option (@pxref{Initial Options}) -through a special preferences screen, which can be accessed through -the Emacs ``app info'' page in the system settings application. - -Consult the manufacturer of your device for more details, as how to do -this varies by device. + Since Android has no command line, there is normally no way to +specify command-line arguments when starting Emacs. However, Emacs +can be started with the equivalent of the @code{--quick} option +(@pxref{Initial Options}) through a special preferences screen, which +can be accessed through the Emacs ``app info'' page in the system +settings application. + + Consult the manufacturer of your device for more details, as how to +do this varies by device. + +@cindex dumping, android + The first time any given copy of Emacs starts on a device, it spends +a while loading the preloaded Lisp files which normally come with +Emacs. This produces a ``dump file'' (@pxref{Initial Options}) in the +files directory, containing an identifier unique to this copy of +Emacs. + + The next time that same copy of Emacs starts up, it simply loads the +preloaded Lisp files contained in that dump file, greatly improving +start up time. + + However, if by some unforseen circumstance the dump file is +corrupted, Emacs can crash. If that happens, the dump file stored in +the Emacs files directory can be erased through the same preferences +screen. @node Android File System @section What files Emacs can access under Android diff --git a/java/Makefile.in b/java/Makefile.in index d27775ea3db..b5e0cd7bb56 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -148,8 +148,7 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done - $(foreach module,$(NDK_BUILD_SHARED), \ - cp -f $(module) install_temp/lib/$(ANDROID_ABI)) + cp -f $(NDK_BUILD_SHARED) install_temp/lib/$(ANDROID_ABI) # Package everything. Specifying the assets on this command line is # necessary for AAssetManager_getNextFileName to work on old versions # of Android. diff --git a/java/README b/java/README index 50c2332ce95..05edf7744de 100644 --- a/java/README +++ b/java/README @@ -1,5 +1,11 @@ This directory holds the Java sources of the port of GNU Emacs to -Android-like systems. +Android-like systems, along with files needed to create an application +package out of them. + +`emacs.keystore' is the signing key used to build Emacs. It is kept +here, and we encourage all people redistributing Emacs to use this +key. It holds no security value, and otherwise it will be impossible +to install different builds of Emacs on top of each other. Please keep the Java code indented with tabs and formatted according to the rules for C code in the GNU coding standards. Always use diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java index 125da05cfd4..87085c32d62 100644 --- a/java/org/gnu/emacs/EmacsApplication.java +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -19,9 +19,59 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import java.io.File; +import java.io.FileFilter; + import android.app.Application; +import android.util.Log; -public class EmacsApplication extends Application +public class EmacsApplication extends Application implements FileFilter { - /* This class currently does nothing. */ + private static final String TAG = "EmacsApplication"; + + /* The name of the dump file to use. */ + public static String dumpFileName; + + @Override + public boolean + accept (File file) + { + return (!file.isDirectory () + && file.getName ().endsWith (".pdmp")); + } + + @Override + public void + onCreate () + { + File filesDirectory; + File[] allFiles; + String wantedDumpFile; + int i; + + wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () + + ".pdmp"); + + Log.d (TAG, "onCreate: looking for " + wantedDumpFile); + + /* Obtain a list of all files ending with ``.pdmp''. Then, look + for a file named ``emacs-.pdmp'' and delete the + rest. */ + filesDirectory = getFilesDir (); + allFiles = filesDirectory.listFiles (this); + + /* Now try to find the right dump file. */ + for (i = 0; i < allFiles.length; ++i) + { + if (allFiles[i].getName ().equals (wantedDumpFile)) + dumpFileName = allFiles[i].getAbsolutePath (); + else + /* Delete this outdated dump file. */ + allFiles[i].delete (); + } + + Log.d (TAG, "onCreate: found " + dumpFileName); + + super.onCreate (); + } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 962538bef7b..9636561a524 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -25,6 +25,10 @@ import android.content.res.AssetManager; public class EmacsNative { + /* Obtain the fingerprint of this build of Emacs. The fingerprint + can be used to determine the dump file name. */ + public static native String getFingerprint (); + /* Set certain parameters before initializing Emacs. This proves that libemacs.so is being loaded from Java code. diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index 0db983984fd..ed1db68f732 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -19,6 +19,8 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import java.io.File; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -93,6 +95,31 @@ public class EmacsPreferencesActivity extends Activity }); layout.addView (textView); + textView = new TextView (this); + textView.setPadding (8, 20, 20, 8); + + params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + textView.setLayoutParams (params); + textView.setText ("Erase dump file"); + textView.setOnClickListener (new View.OnClickListener () { + @Override + public void + onClick (View view) + { + String wantedDumpFile; + File file; + + wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () + + ".pdmp"); + file = new File (getFilesDir (), wantedDumpFile); + + if (file.exists ()) + file.delete (); + } + }); + layout.addView (textView); + super.onCreate (savedInstanceState); } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 95f21b211a3..4db1ea5359f 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -43,6 +43,8 @@ import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; +import android.net.Uri; + import android.os.Build; import android.os.Looper; import android.os.IBinder; @@ -504,4 +506,27 @@ public class EmacsService extends Service EmacsService.class)); } } + + /* Ask the system to open the specified URL. + Value is NULL upon success, or a string describing the error + upon failure. */ + + public String + browseUrl (String url) + { + Intent intent; + + try + { + intent = new Intent (Intent.ACTION_VIEW, Uri.parse (url)); + intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity (intent); + } + catch (Exception e) + { + return e.toString (); + } + + return null; + } }; diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 21d8612703a..5b76d11db4b 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -38,10 +38,23 @@ public class EmacsThread extends Thread { String args[]; - if (!startDashQ) - args = new String[] { "libandroid-emacs.so", }; + if (EmacsApplication.dumpFileName == null) + { + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", }; + else + args = new String[] { "libandroid-emacs.so", "-Q", }; + } else - args = new String[] { "libandroid-emacs.so", "-Q", }; + { + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", "--dump-file", + EmacsApplication.dumpFileName, }; + else + args = new String[] { "libandroid-emacs.so", "-Q", + "--dump-file", + EmacsApplication.dumpFileName, }; + } /* Run the native code now. */ EmacsNative.initEmacs (args); diff --git a/lisp/loadup.el b/lisp/loadup.el index b9ae8212d2d..8d2e4bbb7c2 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -550,66 +550,96 @@ lost after dumping"))) -(if dump-mode - (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp") - ((equal dump-mode "dump") "emacs") - ((equal dump-mode "bootstrap") "emacs") - ((equal dump-mode "pbootstrap") "bootstrap-emacs.pdmp") - (t (error "Unrecognized dump mode %s" dump-mode))))) - (when (and (featurep 'native-compile) - (equal dump-mode "pdump")) - ;; Don't enable this before bootstrap is completed, as the - ;; compiler infrastructure may not be usable yet. - (setq comp-enable-subr-trampolines t)) - (message "Dumping under the name %s" output) - (condition-case () - (delete-file output) - (file-error nil)) - ;; On MS-Windows, the current directory is not necessarily the - ;; same as invocation-directory. - (let (success) - (unwind-protect - (let ((tmp-dump-mode dump-mode) - (dump-mode nil) - (lexical-binding nil)) - (if (member tmp-dump-mode '("pdump" "pbootstrap")) - (dump-emacs-portable (expand-file-name output invocation-directory)) - (dump-emacs output (if (eq system-type 'ms-dos) - "temacs.exe" - "temacs")) - (message "%d pure bytes used" pure-bytes-used)) - (setq success t)) - (unless success - (ignore-errors - (delete-file output))))) - ;; Recompute NAME now, so that it isn't set when we dump. - (if (not (or (eq system-type 'ms-dos) - (eq system-type 'haiku) ;; BFS doesn't support hard links - ;; Don't bother adding another name if we're just - ;; building bootstrap-emacs. - (member dump-mode '("pbootstrap" "bootstrap")))) - (let ((name (format "emacs-%s.%d" emacs-version emacs-build-number)) - (exe (if (eq system-type 'windows-nt) ".exe" ""))) - (while (string-match "[^-+_.a-zA-Z0-9]+" name) - (setq name (concat (downcase (substring name 0 (match-beginning 0))) - "-" - (substring name (match-end 0))))) - (message "Adding name %s" (concat name exe)) - ;; When this runs on Windows, invocation-directory is not - ;; necessarily the current directory. - (add-name-to-file (expand-file-name (concat "emacs" exe) - invocation-directory) - (expand-file-name (concat name exe) - invocation-directory) - t) - (when (equal dump-mode "pdump") - (message "Adding name %s" (concat name ".pdmp")) - (add-name-to-file (expand-file-name "emacs.pdmp" +(if (eq system-type 'android) + (progn + ;; Dumping Emacs on Android works slightly differently from + ;; everywhere else. The first time Emacs starts, Emacs dumps + ;; itself to "emacs-%s.pdump", and then proceeds with loadup, + ;; where %s is replaced by the dump fingerprint. + ;; EmacsApplication.java removes any pdump files with a + ;; different build fingerprint upon being created, which happens + ;; the moment the Android system starts Emacs. Then, it passes + ;; the appropriate "--dump-file" to libemacs.so as it starts. + (let ((temp-dir (getenv "TEMP")) + (dump-file-name (format "%semacs-%s.pdmp" + (file-name-as-directory "~") + pdumper-fingerprint)) + (dump-temp-file-name (format "%s~emacs-%s.pdmp" + (file-name-as-directory "~") + pdumper-fingerprint))) + (unless (pdumper-stats) + (condition-case () + (progn + (dump-emacs-portable dump-temp-file-name) + ;; Move the dumped file to the actual dump file name. + (rename-file dump-temp-file-name dump-file-name) + ;; Continue with loadup. + nil) + (error nil))))) + (if dump-mode + (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp") + ((equal dump-mode "dump") "emacs") + ((equal dump-mode "bootstrap") "emacs") + ((equal dump-mode "pbootstrap") "bootstrap-emacs.pdmp") + (t (error "Unrecognized dump mode %s" dump-mode))))) + (when (and (featurep 'native-compile) + (equal dump-mode "pdump")) + ;; Don't enable this before bootstrap is completed, as the + ;; compiler infrastructure may not be usable yet. + (setq comp-enable-subr-trampolines t)) + (message "Dumping under the name %s" output) + (condition-case () + (delete-file output) + (file-error nil)) + ;; On MS-Windows, the current directory is not necessarily the + ;; same as invocation-directory. + (let (success) + (unwind-protect + (let ((tmp-dump-mode dump-mode) + (dump-mode nil) + (lexical-binding nil)) + (if (member tmp-dump-mode '("pdump" "pbootstrap")) + (dump-emacs-portable (expand-file-name output invocation-directory)) + (dump-emacs output (if (eq system-type 'ms-dos) + "temacs.exe" + "temacs")) + (message "%d pure bytes used" pure-bytes-used)) + (setq success t)) + (unless success + (ignore-errors + (delete-file output))))) + ;; Recompute NAME now, so that it isn't set when we dump. + (if (not (or (eq system-type 'ms-dos) + (eq system-type 'haiku) ;; BFS doesn't support hard links + ;; There's no point keeping old dumps around for + ;; the binary used to build Lisp on the build + ;; machine. + (featurep 'android) + ;; Don't bother adding another name if we're just + ;; building bootstrap-emacs. + (member dump-mode '("pbootstrap" "bootstrap")))) + (let ((name (format "emacs-%s.%d" emacs-version emacs-build-number)) + (exe (if (eq system-type 'windows-nt) ".exe" ""))) + (while (string-match "[^-+_.a-zA-Z0-9]+" name) + (setq name (concat (downcase (substring name 0 (match-beginning 0))) + "-" + (substring name (match-end 0))))) + (message "Adding name %s" (concat name exe)) + ;; When this runs on Windows, invocation-directory is not + ;; necessarily the current directory. + (add-name-to-file (expand-file-name (concat "emacs" exe) invocation-directory) - (expand-file-name (concat name ".pdmp") + (expand-file-name (concat name exe) invocation-directory) - t)))) - (kill-emacs))) + t) + (when (equal dump-mode "pdump") + (message "Adding name %s" (concat name ".pdmp")) + (add-name-to-file (expand-file-name "emacs.pdmp" + invocation-directory) + (expand-file-name (concat name ".pdmp") + invocation-directory) + t)))) + (kill-emacs)))) ;; This file must be loaded each time Emacs is run from scratch, e.g., temacs. ;; So run the startup code now. First, remove `-l loadup' from args. diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el index 0177d12f236..c2629b69d59 100644 --- a/lisp/net/browse-url.el +++ b/lisp/net/browse-url.el @@ -52,6 +52,7 @@ ;; browse-url-xdg-open freedesktop.org xdg-open ;; browse-url-kde KDE konqueror (kfm) ;; browse-url-elinks Elinks Don't know (tried with 0.12.GIT) +;; browse-url-default-android-browser Android 2.3.3 (should work on 2.2 too) ;; eww-browse-url Emacs Web Wowser ;; Browsers can cache web pages so it may be necessary to tell them to @@ -173,6 +174,9 @@ ,@(when (eq system-type 'darwin) (list '(function-item :tag "Default macOS browser" :value browse-url-default-macosx-browser))) + ,@(when (eq system-type 'android) + (list '(function-item :tag "Default Android browser" + :value browse-url-default-android-browser))) (function-item :tag "Default browser" :value browse-url-default-browser) (function :tag "Your own function") @@ -1057,6 +1061,8 @@ instead of `browse-url-new-window-flag'." 'browse-url-default-macosx-browser) ((featurep 'haiku) 'browse-url-default-haiku-browser) + ((eq system-type 'android) + 'browse-url-default-android-browser) ((browse-url-can-use-xdg-open) 'browse-url-xdg-open) ;;; ((executable-find browse-url-gnome-moz-program) 'browse-url-gnome-moz) ((executable-find browse-url-firefox-program) 'browse-url-firefox) @@ -1294,6 +1300,22 @@ Default to the URL around or before point." (function-put 'browse-url-default-haiku-browser 'browse-url-browser-kind 'external) +(declare-function android-browse-url "androidselect.c") + +;;;###autoload +(defun browse-url-default-android-browser (url &optional _new-window) + "Browse URL with the system default browser. +Default to the URL around or before point." + (interactive (browse-url-interactive-arg "URL: ")) + (setq url (browse-url-encode-url url)) + ;; Make sure the URL starts with an appropriate scheme. + (unless (string-match "\\(.+\\):/" url) + (setq url (concat "http://" url))) + (android-browse-url url)) + +(function-put 'browse-url-default-android-browser + 'browse-url-browser-kind 'external) + ;;;###autoload (defun browse-url-emacs (url &optional same-window) "Ask Emacs to load URL into a buffer and show it in another window. diff --git a/m4/ndk-build.m4 b/m4/ndk-build.m4 index a35900884d9..d3614a9f490 100644 --- a/m4/ndk-build.m4 +++ b/m4/ndk-build.m4 @@ -67,7 +67,7 @@ esac # This is a map between pkg-config style package names and Android # ones. -ndk_package_map="libwebpdemux:webpdemux libxml-2.0:libxml2" +ndk_package_map="libwebpdemux:webpdemux libxml-2.0:libxml2 jansson:libjansson" # Replace ndk_module with the appropriate Android module name if it is # found in ndk_package_map. diff --git a/src/android.c b/src/android.c index d788c6f19b4..c186b462360 100644 --- a/src/android.c +++ b/src/android.c @@ -33,6 +33,7 @@ along with GNU Emacs. If not, see . */ #include #include +#include #include "android.h" #include "androidgui.h" @@ -95,6 +96,7 @@ struct android_emacs_service jmethodID detect_mouse; jmethodID name_keysym; jmethodID sync; + jmethodID browse_url; }; struct android_emacs_pixmap @@ -1294,6 +1296,18 @@ android_get_home_directory (void) #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, @@ -1514,6 +1528,8 @@ android_init_emacs_service (void) 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 } @@ -4735,6 +4751,51 @@ android_project_image_nearest (struct android_image *image, +/* 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. */ diff --git a/src/android.h b/src/android.h index eee3a7a6498..ce0ccfc4338 100644 --- a/src/android.h +++ b/src/android.h @@ -107,6 +107,10 @@ extern void android_closedir (struct android_dir *); +/* Very miscellaneous functions. */ + +extern Lisp_Object android_browse_url (Lisp_Object); + #endif /* JNI functions should not be built when Emacs is stubbed out for the diff --git a/src/androidselect.c b/src/androidselect.c index 4a71b376e28..2e2f2d7e483 100644 --- a/src/androidselect.c +++ b/src/androidselect.c @@ -213,6 +213,25 @@ DEFUN ("android-clipboard-exists-p", Fandroid_clipboard_exists_p, return rc ? Qt : Qnil; } +DEFUN ("android-browse-url", Fandroid_browse_url, + Sandroid_browse_url, 1, 1, 0, + doc: /* Start the system web browser. +Then, point the web browser to URL, which should be a URL-encoded +URL with a scheme specified. Signal an error upon failure. */) + (Lisp_Object url) +{ + Lisp_Object value; + + CHECK_STRING (url); + value = android_browse_url (url); + + /* Signal an error upon failure. */ + if (!NILP (value)) + signal_error ("Error browsing URL", value); + + return Qnil; +} + void @@ -246,4 +265,5 @@ syms_of_androidselect (void) defsubr (&Sandroid_set_clipboard); defsubr (&Sandroid_get_clipboard); defsubr (&Sandroid_clipboard_exists_p); + defsubr (&Sandroid_browse_url); } diff --git a/src/emacs.c b/src/emacs.c index 02016e53c45..b0c19fe0070 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -744,6 +744,8 @@ argmatch (char **argv, int argc, const char *sstr, const char *lstr, } } +#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY + /* Find a name (absolute or relative) of the Emacs executable whose name (as passed into this program) is ARGV0. Called early in initialization by portable dumper loading code, so avoid Lisp and @@ -843,6 +845,8 @@ find_emacs_executable (char const *argv0, ptrdiff_t *candidate_size) #endif /* !WINDOWSNT */ } +#endif + #ifdef HAVE_PDUMPER static const char * @@ -875,6 +879,30 @@ dump_error_to_string (int result) static char * load_pdump (int argc, char **argv) { +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + char *dump_file = NULL; + int skip_args = 0, result; + + while (skip_args < argc - 1) + { + if (argmatch (argv, argc, "-dump-file", "--dump-file", 6, + &dump_file, &skip_args) + || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args)) + break; + skip_args++; + } + + if (!dump_file) + return argv[0]; + + result = pdumper_load (dump_file, argv[0]); + + if (result != PDUMPER_LOAD_SUCCESS) + fatal ("could not load dump file \"%s\": %s", + dump_file, dump_error_to_string (result)); + return argv[0]; +#else + const char *const suffix = ".pdmp"; int result; char *emacs_executable = argv[0]; @@ -1067,6 +1095,7 @@ load_pdump (int argc, char **argv) xfree (dump_file); return emacs_executable; +#endif } #endif /* HAVE_PDUMPER */ diff --git a/src/pdumper.c b/src/pdumper.c index eca64725211..dbdeae9a799 100644 --- a/src/pdumper.c +++ b/src/pdumper.c @@ -4072,10 +4072,12 @@ types. */) { eassert (initialized); +#ifndef HAVE_ANDROID if (! noninteractive) error ("Dumping Emacs currently works only in batch mode. " "If you'd like it to work interactively, please consider " "contributing a patch to Emacs."); +#endif if (will_dump_with_unexec_p ()) error ("This Emacs instance was started under the assumption " @@ -5842,6 +5844,10 @@ void syms_of_pdumper (void) { #ifdef HAVE_PDUMPER + unsigned char desired[sizeof fingerprint]; + int i; + char hexbuf[2 * sizeof fingerprint]; + defsubr (&Sdump_emacs_portable); defsubr (&Sdump_emacs_portable__sort_predicate); defsubr (&Sdump_emacs_portable__sort_predicate_copied); @@ -5854,5 +5860,17 @@ syms_of_pdumper (void) DEFSYM (Qdump_file_name, "dump-file-name"); DEFSYM (Qafter_pdump_load_hook, "after-pdump-load-hook"); defsubr (&Spdumper_stats); + + for (i = 0; i < sizeof fingerprint; i++) + desired[i] = fingerprint[i]; + + hexbuf_digest (hexbuf, desired, sizeof desired); + + DEFVAR_LISP ("pdumper-fingerprint", Vpdumper_fingerprint, + doc: /* The fingerprint of this Emacs binary. +It is a string that is supposed to be unique to each build of +Emacs. */); + Vpdumper_fingerprint = make_unibyte_string ((char *) hexbuf, + sizeof hexbuf); #endif /* HAVE_PDUMPER */ } diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 5c9835fb13e..bc8f1b35b84 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -320,7 +320,10 @@ sfntfont_android_composite_bitmap (unsigned char *restrict buffer, { unsigned int *src_row; unsigned int *dst_row; - unsigned int i, src_y, x, src_x, max_x, dst_x, lim_x; + unsigned int i, src_y, x, src_x, max_x, dst_x; +#ifdef __aarch64__ + unsigned int lim_x; +#endif if ((intptr_t) dest & 3 || bitmap_info->stride & 3) /* This shouldn't be possible as Android is supposed to align the @@ -353,10 +356,10 @@ sfntfont_android_composite_bitmap (unsigned char *restrict buffer, src_x = x + (rect->x - text_rectangle->x); dst_x = x + rect->x; +#ifdef __aarch64__ /* This is the largest value of src_x. */ lim_x = max_x + (rect->x - text_rectangle->x); -#ifdef __aarch64__ if (!sfntfont_android_over_8888 (src_row + src_x, dst_row + dst_x, src_row + lim_x, -- cgit v1.3 From 0900bfbcc57c555909cb75c38eb0ed26fb6964ef Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 25 Jan 2023 18:44:47 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Startup, Android Environment): Document that restrictions on starting Emacs have been lifted. * java/README: Document Java for Emacs developers and how the Android port works. * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication) (findDumpFile): New function. (onCreate): Factor out dump file finding functions to there. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Update function declarations. * java/org/gnu/emacs/EmacsNoninteractive.java (EmacsNoninteractive): New class. * java/org/gnu/emacs/EmacsService.java (EmacsService, getApkFile) (onCreate): Pass classpath to setEmacsParams. * java/org/gnu/emacs/EmacsThread.java (EmacsThread): Make run an override. * lisp/loadup.el: Don't dump on Android when noninteractive. * lisp/shell.el (shell--command-completion-data): Handle inaccessible directories. * src/Makefile.in (android-emacs): Link with gnulib. * src/android-emacs.c (main): Implement to launch app-process and then EmacsNoninteractive. * src/android.c (setEmacsParams): New argument `class_path'. Don't set stuff up when running noninteractive. * src/android.h (initEmacs): Likewise. * src/androidfont.c (init_androidfont): * src/androidselect.c (init_androidselect): Don't initialize when running noninteractive. * src/emacs.c (load_pdump): New argument `dump_file'. (android_emacs_init): Give new argument `dump_file' to `load_pdump'. * src/sfntfont-android.c (init_sfntfont_android): Don't initialize when running noninteractive. --- doc/emacs/android.texi | 18 +- java/README | 824 ++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsApplication.java | 39 +- java/org/gnu/emacs/EmacsNative.java | 16 +- java/org/gnu/emacs/EmacsNoninteractive.java | 164 ++++++ java/org/gnu/emacs/EmacsService.java | 48 +- java/org/gnu/emacs/EmacsThread.java | 22 +- lisp/loadup.el | 33 +- lisp/shell.el | 7 +- src/Makefile.in | 2 +- src/android-emacs.c | 83 ++- src/android.c | 114 +++- src/android.h | 8 +- src/androidfont.c | 3 + src/androidselect.c | 3 + src/emacs.c | 28 +- src/sfntfont-android.c | 3 + 17 files changed, 1306 insertions(+), 109 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsNoninteractive.java (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index cda0eadf226..e910e482ad8 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -102,8 +102,7 @@ files directory, containing an identifier unique to this copy of Emacs. The next time that same copy of Emacs starts up, it simply loads the -preloaded Lisp files contained in that dump file, greatly improving -start up time. +data contained in that dump file, greatly improving start up time. However, if by some unforseen circumstance the dump file is corrupted, Emacs can crash. If that happens, the dump file stored in @@ -192,12 +191,17 @@ installed. This means, instead of specifying @code{ctags} or @code{libctags.so} or @code{libemacsclient.so} on the commnd line instead when starting either of those programs in a subprocess. -@c TODO: remove this limitation. - In addition, the @file{/assets} directory containing Emacs start-up -files is inaccessible to processes not directly created by + The @file{/assets} directory containing Emacs start-up files is +supposed to be inaccessible to processes not directly created by @code{zygote}, the system service responsible for starting -applications. This makes it impossible to run Emacs in a subprocess -within itself. +applications. Since required Lisp is found in the @file{/assets} +directory, it would thus follow that it is not possible for Emacs to +start itself as a subprocess. A special binary named +@command{libandroid-emacs.so} is provided with Emacs, and does its +best to start Emacs, for the purpose of running Lisp in batch mode. +However, the approach it takes was devised by reading Android source +code, and is not sanctioned by the Android compatibility definition +documents, so your mileage may vary. @section Running Emacs in the background @cindex emacs killed, android diff --git a/java/README b/java/README index 05edf7744de..44f5a415162 100644 --- a/java/README +++ b/java/README @@ -10,3 +10,827 @@ to install different builds of Emacs on top of each other. Please keep the Java code indented with tabs and formatted according to the rules for C code in the GNU coding standards. Always use C-style comments. + +====================================================================== + +OVERVIEW OF JAVA + +Emacs developers do not know Java, and there is no reason they should +have to. Thus, the code in this directory is confined to what is +strictly necessary to support Emacs, and only uses a subset of Java +written in a way that is easily understandable to C programmers. + +Java is required because the entire Android runtime is based around +Java, and there is no way to write an Android program which runs +without Java. + +This text exists to prime other Emacs developers, already familar with +C, on the basic architecture of the Android port, and to teach them +how to read and write the Java code found in this directory. + +Java is an object oriented language with automatic memory management +compiled down to bytecode, which is then subject to interpretation by +a Java virtual machine. + +What that means, is that: + +struct emacs_window +{ + int some_fields; + int of_emacs_window; +}; + +static void +do_something_with_emacs_window (struct emacs_window *a, int n) +{ + a->some_fields = a->of_emacs_window + n; +} + +would be written: + +public class EmacsWindow +{ + public int someFields; + public int ofEmacsWindow; + + public void + doSomething (int n) + { + someFields = ofEmacsWindow + n; + } +} + +and instead of doing: + +do_something_with_emacs_window (my_window, 1); + +you say: + +myWindow.doSomething (1); + +In addition to functions associated with an object of a given class +(such as EmacsWindow), Java also has two other kinds of functions. + +The first are so-called ``static'' functions (the static means +something entirely different from what it does in C.) + +A static function, while still having to be defined within a class, +can be called without any object. Instead of the object, you write +the name of the Java class within which it is defined. For example, +the following C code: + +int +multiply_a_with_b_and_then_add_c (int a, int b, int c) +{ + return a * b + c; +} + +would be: + +public class EmacsSomething +{ + public static int + multiplyAWithBAndThenAddC (int a, int b, int c) + { + return a * b + c; + } +}; + +Then, instead of calling: + +int foo; + +foo = multiply_a_with_b_then_add_c (1, 2, 3); + +you say: + +int foo; + +foo = EmacsSomething.multiplyAWithBAndThenAddC (1, 2, 3); + +In Java, ``static'' does not mean that the function is only used +within its compilation unit! Instead, the ``private'' qualifier is +used to mean more or less the same thing: + +static void +this_procedure_is_only_used_within_this_file (void) +{ + do_something (); +} + +becomes + +public class EmacsSomething +{ + private static void + thisProcedureIsOnlyUsedWithinThisClass () + { + + } +} + +the other kind are called ``constructors''. They are functions that +must be called to allocate memory to hold a class: + +public class EmacsFoo +{ + int bar; + + public + EmacsFoo (int tokenA, int tokenB) + { + bar = tokenA + tokenB; + } +} + +now, the following statement: + +EmacsFoo foo; + +foo = new EmacsFoo (1, 2); + +becomes more or less equivalent to the following C code: + +struct emacs_foo +{ + int bar; +}; + +struct emacs_foo * +make_emacs_foo (int token_a, int token_b) +{ + struct emacs_foo *foo; + + foo = xmalloc (sizeof *foo); + foo->bar = token_a + token_b; + + return foo; +} + +/* ... */ + +struct emacs_foo *foo; + +foo = make_emacs_foo (1, 2); + +A class may have any number of constructors, or no constructors at +all, in which case the compiler inserts an empty constructor. + + + +Sometimes, you will see Java code that looks like this: + + allFiles = filesDirectory.listFiles (new FileFilter () { + @Override + public boolean + accept (File file) + { + return (!file.isDirectory () + && file.getName ().endsWith (".pdmp")); + } + }); + +This is Java's version of GCC's nested function extension. The major +difference is that the nested function may still be called even after +it goes out of scope, and always retains a reference to the class and +local variables around where it was called. + +Being an object-oriented language, Java also allows defining that a +class ``extends'' another class. The following C code: + +struct a +{ + long thirty_two; +}; + +struct b +{ + struct a a; + long long sixty_four; +}; + +extern void do_something (struct a *); + +void +my_function (struct b *b) +{ + do_something (&b->a); +} + +is roughly equivalent to the following Java code, split into two +files: + + A.java + +public class A +{ + int thirtyTwo; + + public void + doSomething () + { + etcEtcEtc (); + } +}; + + B.java + +public class B extends A +{ + long sixty_four; + + public static void + myFunction (B b) + { + b.doSomething (); + } +} + +the Java runtime has transformed the call to ``b.doSomething'' to +``((A) b).doSomething''. + +However, Java also allows overriding this behavior, by specifying the +@Override keyword: + +public class B extends A +{ + long sixty_four; + + @Override + public void + doSomething () + { + Something.doSomethingTwo (); + super.doSomething (); + } +} + +now, any call to ``doSomething'' on a ``B'' created using ``new B ()'' +will end up calling ``Something.doSomethingTwo'', before calling back +to ``A.doSomething''. This override also applies in reverse; that is +to say, even if you write: + + ((A) b).doSomething (); + +B's version of doSomething will still be called, if ``b'' was created +using ``new B ()''. + +This mechanism is used extensively throughout the Java language and +Android windowing APIs. + +Elsewhere, you will encounter Java code that defines arrays: + +public class EmacsFrobinicator +{ + public static void + emacsFrobinicate (int something) + { + int[] primesFromSomething; + + primesFromSomething = new int[numberOfPrimes]; + /* ... */ + } +} + +Java arrays are similar to C arrays in that they can not grow. But +they are very much unlike C arrays in that they are always references +(as opposed to decaying into pointers in various situations), and +contain information about their length. + +If another function named ``frobinicate1'' takes an array as an +argument, then it need not take the length of the array. + +Instead, it simply iterates over the array like so: + +int i, k; + +for (i = 0; i < array.length; ++i) + { + k = array[i]; + + Whatever.doSomethingWithK (k); + } + +The syntax used to define arrays is also slightly different. As +arrays are always references, there is no way for you to tell the +runtime to allocate an array of size N in a structure (class.) + +Instead, if you need an array of that size, you must declare a field +with the type of the array, and allocate the array inside the class's +constructor, like so: + +public class EmacsArrayContainer +{ + public int[] myArray; + + public + EmacsArrayContainer () + { + myArray = new array[10]; + } +} + +while in C, you could just have written: + +struct emacs_array_container +{ + int my_array[10]; +}; + +or, possibly even better, + +typedef int my_array[10]; + +Alas, Java has no equivalent of `typedef'. + +JAVA NATIVE INTERFACE + +Java also provides an interface for C code to interface with Java. + +C functions exported from a shared library become static Java +functions within a class, like so: + +public class EmacsNative +{ + /* Obtain the fingerprint of this build of Emacs. The fingerprint + can be used to determine the dump file name. */ + public static native String getFingerprint (); + + /* Set certain parameters before initializing Emacs. + + assetManager must be the asset manager associated with the + context that is loading Emacs. It is saved and remains for the + remainder the lifetime of the Emacs process. + + filesDir must be the package's data storage location for the + current Android user. + + libDir must be the package's data storage location for native + libraries. It is used as PATH. + + cacheDir must be the package's cache directory. It is used as + the `temporary-file-directory'. + + pixelDensityX and pixelDensityY are the DPI values that will be + used by Emacs. + + classPath must be the classpath of this app_process process, or + NULL. + + emacsService must be the EmacsService singleton, or NULL. */ + public static native void setEmacsParams (AssetManager assetManager, + String filesDir, + String libDir, + String cacheDir, + float pixelDensityX, + float pixelDensityY, + String classPath, + EmacsService emacsService); +} + +Where the corresponding C functions are located in android.c, and +loaded by the special invocation: + + static + { + System.loadLibrary ("emacs"); + }; + + +See http://docs.oracle.com/en/java/javase/19/docs/specs/jni/intro.html +for more details. + + + +OVERVIEW OF ANDROID + +When the Android system starts an application, it does not actually +call the application's ``main'' function. It may not even start the +application's process if one is already running. + +Instead, Android is organized around components. When the user opens +the ``Emacs'' icon, the Android system looks up and starts the +component associated with the ``Emacs'' icon. In this case, the +component is called an activity, and is declared in +the AndroidManifest.xml in this directory: + + + + + + + + + +This tells Android to start the activity defined in ``EmacsActivity'' +(defined in org/gnu/emacs/EmacsActivity.java), a class extending the +Android class ``Activity''. + +To do so, the Android system creates an instance of ``EmacsActivity'' +and the window system window associated with it, and eventually calls: + + Activity activity; + + activity.onCreate (...); + +But which ``onCreate'' is really called? +It is actually the ``onCreate'' defined in EmacsActivity.java, as +it overrides the ``onCreate'' defined in Android's own Activity class: + + @Override + public void + onCreate (Bundle savedInstanceState) + { + FrameLayout.LayoutParams params; + Intent intent; + +Then, this is what happens step-by-step within the ``onCreate'' +function: + + /* See if Emacs should be started with -Q. */ + intent = getIntent (); + EmacsService.needDashQ + = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q", + false); + +Here, Emacs obtains the intent (a request to start a component) which +was used to start Emacs, and sets a special flag if it contains a +request for Emacs to start with the ``-Q'' command-line argument. + + /* Set the theme to one without a title bar. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + setTheme (android.R.style.Theme_DeviceDefault_NoActionBar); + else + setTheme (android.R.style.Theme_NoTitleBar); + +Next, Emacs sets an appropriate theme for the activity's associated +window decorations. + + params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + + /* Make the frame layout. */ + layout = new FrameLayout (this); + layout.setLayoutParams (params); + + /* Set it as the content view. */ + setContentView (layout); + +Then, Emacs creates a ``FrameLayout'', a widget that holds a single +other widget, and makes it the activity's ``content view''. + +The activity itself is a ``FrameLayout'', so the ``layout parameters'' +here apply to the FrameLayout itself, and not its children. + + /* Maybe start the Emacs service if necessary. */ + EmacsService.startEmacsService (this); + +And after that, Emacs calls the static function ``startEmacsService'', +defined in the class ``EmacsService''. This starts the Emacs service +component if necessary. + + /* Add this activity to the list of available activities. */ + EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this); + + super.onCreate (savedInstanceState); + +Finally, Emacs registers that this activity is now ready to receive +top-level frames (windows) created from Lisp. + +Activities come and go, but Emacs has to stay running in the mean +time. Thus, Emacs also defines a ``service'', which is a long-running +component that the Android system allows to run in the background. + +Let us go back and review the definition of ``startEmacsService'': + + public static void + startEmacsService (Context context) + { + if (EmacsService.SERVICE == null) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + /* Start the Emacs service now. */ + context.startService (new Intent (context, + EmacsService.class)); + else + /* Display the permanant notification and start Emacs as a + foreground service. */ + context.startForegroundService (new Intent (context, + EmacsService.class)); + } + } + +If ``EmacsService.SERVICE'' does not yet exist, what this does is to +tell the ``context'' (the equivalent of an Xlib Display *) to start a +service defined by the class ``EmacsService''. Eventually, this +results in ``EmacsService.onCreate'' being called: + + @Override + public void + onCreate () + { + AssetManager manager; + Context app_context; + String filesDir, libDir, cacheDir, classPath; + double pixelDensityX; + double pixelDensityY; + +Here is what this function does, step-by-step: + + SERVICE = this; + +First, it sets the special static variable ``SERVICE'' to ``this'', +which is a pointer to the ``EmacsService' object that was created. + + handler = new Handler (Looper.getMainLooper ()); + +Next, it creates a ``Handler'' object for the ``main looper''. +This is a helper structure which allows executing code on the Android +user interface thread. + + manager = getAssets (); + app_context = getApplicationContext (); + metrics = getResources ().getDisplayMetrics (); + pixelDensityX = metrics.xdpi; + pixelDensityY = metrics.ydpi; + +Finally, it obtains: + + - the asset manager, which is used to retrieve assets packaged + into the Emacs application package. + + - the application context, used to obtain application specific + information. + + - the display metrics, and from them, the X and Y densities in dots + per inch. + +Then, inside a ``try'' block: + + try + { + /* Configure Emacs with the asset manager and other necessary + parameters. */ + filesDir = app_context.getFilesDir ().getCanonicalPath (); + libDir = getLibraryDirectory (); + cacheDir = app_context.getCacheDir ().getCanonicalPath (); + +It obtains the names of the Emacs home, shared library, and temporary +file directories. + + /* Now provide this application's apk file, so a recursive + invocation of app_process (through android-emacs) can + find EmacsNoninteractive. */ + classPath = getApkFile (); + +The name of the Emacs application package. + + Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir + + ", libDir = " + libDir + ", and classPath = " + classPath); + +Prints a debug message to the Android system log with this +information. + + EmacsNative.setEmacsParams (manager, filesDir, libDir, + cacheDir, (float) pixelDensityX, + (float) pixelDensityY, + classPath, this); + +And calls the native function ``setEmacsParams'' (defined in +android.c) to configure Emacs with this information. + + /* Start the thread that runs Emacs. */ + thread = new EmacsThread (this, needDashQ); + thread.start (); + +Then, it allocates an ``EmacsThread'' object, and starts that thread. +Inside that thread is where Emacs's C code runs. + + } + catch (IOException exception) + { + EmacsNative.emacsAbort (); + return; + +And here is the purpose of the ``try'' block. Functions related to +file names in Java will signal errors of various types upon failure. + +This ``catch'' block means that the Java virtual machine will abort +execution of the contents of the ``try'' block as soon as an error of +type ``IOException'' is encountered, and begin executing the contents +of the ``catch'' block. + +Any failure of that type here is a crash, and +``EmacsNative.emacsAbort'' is called to quickly abort the process to +get a useful backtrace. + } + } + +Now, let us look at the definition of the class ``EmacsThread'', found +in org/gnu/emacs/EmacsThread.java: + +public class EmacsThread extends Thread +{ + /* Whether or not Emacs should be started -Q. */ + private boolean startDashQ; + + public + EmacsThread (EmacsService service, boolean startDashQ) + { + super ("Emacs main thread"); + this.startDashQ = startDashQ; + } + + @Override + public void + run () + { + String args[]; + + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", }; + else + args = new String[] { "libandroid-emacs.so", "-Q", }; + + /* Run the native code now. */ + EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); + } +}; + +The class itself defines a single field, ``startDashQ'', a constructor +with an unused argument of the type ``EmacsService'' (which is useful +while debugging) and a flag ``startDashQ'', and a single function +``run'', overriding the same function in the class ``Thread''. + +When ``thread.start'' is called, the Java virtual machine creates a +new thread, and then calls the function ``run'' within that thread. + +This function then computes a suitable argument vector, and calls +``EmacsNative.initEmacs'' (defined in android.c), which then calls a +modified version of the regular Emacs ``main'' function. + +At that point, Emacs initialization proceeds as usual: +Vinitial_window_system is set, loadup.el calls `normal-top-level', +which calls `command-line', and finally +`window-system-initialization', which initializes the `android' +terminal interface as usual. + +What happens here is the same as on other platforms. Now, here is +what happens when the initial frame is created: Fx_create_frame calls +`android_create_frame_window' to create a top level window: + +static void +android_create_frame_window (struct frame *f) +{ + struct android_set_window_attributes attributes; + enum android_window_value_mask attribute_mask; + + attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f); + attribute_mask = ANDROID_CW_BACK_PIXEL; + + block_input (); + FRAME_ANDROID_WINDOW (f) + = android_create_window (FRAME_DISPLAY_INFO (f)->root_window, + f->left_pos, + f->top_pos, + FRAME_PIXEL_WIDTH (f), + FRAME_PIXEL_HEIGHT (f), + attribute_mask, &attributes); + unblock_input (); +} + +This calls the function `android_create_window' with some arguments +whose meanings are identical to the arguments to `XCreateWindow'. + +Here is the definition of `android_create_window', in android.c: + +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; + +What does it do? First, some context: + +At any time, there can be at most 65535 Java objects referred to by +the rest of Emacs through the Java native interface. Each such object +is assigned a ``handle'' (similar to an XID on X) and given a unique +type. The function `android_resolve_handle' returns the JNI `jobject' +associated with a given handle. + + parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW); + +Here, it is being used to look up the `jobject' associated with the +`parent' handle. + + prev_max_handle = max_handle; + window = android_alloc_id (); + +Next, `max_handle' is saved, and a new handle is allocated for +`window'. + + if (!window) + error ("Out of window handles!"); + +An error is signalled if Emacs runs out of available handles. + + if (!class) + { + class = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsWindow"); + assert (class != NULL); + +Then, if this initialization has not yet been completed, Emacs +proceeds to find the Java class named ``EmacsWindow''. + + constructor + = (*android_java_env)->GetMethodID (android_java_env, class, "", + "(SLorg/gnu/emacs/EmacsWindow;" + "IIIIZ)V"); + assert (constructor != NULL); + +And it tries to look up the constructor, which should take seven +arguments: + + S - a short. (the handle ID) + Lorg/gnu/Emacs/EmacsWindow; - an instance of the EmacsWindow + class. (the parent) + IIII - four ints. (the window geometry.) + Z - a boolean. (whether or not the + window is override-redirect; see + XChangeWindowAttributes.) + + old = class; + class = (*android_java_env)->NewGlobalRef (android_java_env, class); + (*android_java_env)->ExceptionClear (android_java_env); + ANDROID_DELETE_LOCAL_REF (old); + +Next, it saves a global reference to the class and deletes the local +reference. Global references will never be deallocated by the Java +virtual machine as long as they still exist. + + 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); + +Then, it creates an instance of the ``EmacsWindow'' class with the +appropriate arguments and previously determined constructor. + + if (!object) + { + (*android_java_env)->ExceptionClear (android_java_env); + + max_handle = prev_max_handle; + memory_full (0); + +If creating the object fails, Emacs clears the ``pending exception'' +and signals that it is out of memory. + } + + 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); + +Otherwise, it associates a new global reference to the object with the +handle, and deletes the local reference returned from the JNI +NewObject function. + + if (!android_handles[window].handle) + memory_full (0); + +If allocating the global reference fails, Emacs signals that it is out +of memory. + + android_change_window_attributes (window, value_mask, attrs); + return window; + +Otherwise, it applies the specified window attributes and returns the +handle of the new window. +} diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java index 87085c32d62..96328b99d1c 100644 --- a/java/org/gnu/emacs/EmacsApplication.java +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -22,27 +22,20 @@ package org.gnu.emacs; import java.io.File; import java.io.FileFilter; +import android.content.Context; + import android.app.Application; import android.util.Log; -public class EmacsApplication extends Application implements FileFilter +public class EmacsApplication extends Application { private static final String TAG = "EmacsApplication"; /* The name of the dump file to use. */ public static String dumpFileName; - @Override - public boolean - accept (File file) - { - return (!file.isDirectory () - && file.getName ().endsWith (".pdmp")); - } - - @Override - public void - onCreate () + public static void + findDumpFile (Context context) { File filesDirectory; File[] allFiles; @@ -52,13 +45,19 @@ public class EmacsApplication extends Application implements FileFilter wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () + ".pdmp"); - Log.d (TAG, "onCreate: looking for " + wantedDumpFile); - /* Obtain a list of all files ending with ``.pdmp''. Then, look for a file named ``emacs-.pdmp'' and delete the rest. */ - filesDirectory = getFilesDir (); - allFiles = filesDirectory.listFiles (this); + filesDirectory = context.getFilesDir (); + allFiles = filesDirectory.listFiles (new FileFilter () { + @Override + public boolean + accept (File file) + { + return (!file.isDirectory () + && file.getName ().endsWith (".pdmp")); + } + }); /* Now try to find the right dump file. */ for (i = 0; i < allFiles.length; ++i) @@ -69,9 +68,13 @@ public class EmacsApplication extends Application implements FileFilter /* Delete this outdated dump file. */ allFiles[i].delete (); } + } - Log.d (TAG, "onCreate: found " + dumpFileName); - + @Override + public void + onCreate () + { + findDumpFile (this); super.onCreate (); } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 9636561a524..a772b965301 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -29,8 +29,7 @@ public class EmacsNative can be used to determine the dump file name. */ public static native String getFingerprint (); - /* Set certain parameters before initializing Emacs. This proves - that libemacs.so is being loaded from Java code. + /* Set certain parameters before initializing Emacs. assetManager must be the asset manager associated with the context that is loading Emacs. It is saved and remains for the @@ -48,19 +47,26 @@ public class EmacsNative pixelDensityX and pixelDensityY are the DPI values that will be used by Emacs. - emacsService must be the emacsService singleton. */ + classPath must be the classpath of this app_process process, or + NULL. + + emacsService must be the EmacsService singleton, or NULL. */ public static native void setEmacsParams (AssetManager assetManager, String filesDir, String libDir, String cacheDir, float pixelDensityX, float pixelDensityY, + String classPath, EmacsService emacsService); /* Initialize Emacs with the argument array ARGV. Each argument must contain a NULL terminated string, or else the behavior is - undefined. */ - public static native void initEmacs (String argv[]); + undefined. + + DUMPFILE is the dump file to use, or NULL if Emacs is to load + loadup.el itself. */ + public static native void initEmacs (String argv[], String dumpFile); /* Abort and generate a native core dump. */ public static native void emacsAbort (); diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java new file mode 100644 index 00000000000..a3aefee5e0b --- /dev/null +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -0,0 +1,164 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +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 . */ + +package org.gnu.emacs; + +import android.os.Looper; +import android.os.Build; + +import android.content.Context; +import android.content.res.AssetManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/* Noninteractive Emacs. + + This is the class that libandroid-emacs.so starts. + libandroid-emacs.so figures out the system classpath, then starts + dalvikvm with the framework jars. + + At that point, dalvikvm calls main, which sets up the main looper, + creates an ActivityThread and attaches it to the main thread. + + Then, it obtains an application context for the LoadedApk in the + application thread. + + Finally, it obtains the necessary context specific objects and + initializes Emacs. */ + +@SuppressWarnings ("unchecked") +public class EmacsNoninteractive +{ + private static String + getLibraryDirectory (Context context) + { + int apiLevel; + + apiLevel = Build.VERSION.SDK_INT; + + if (apiLevel >= Build.VERSION_CODES.GINGERBREAD) + return context.getApplicationInfo().nativeLibraryDir; + else if (apiLevel >= Build.VERSION_CODES.DONUT) + return context.getApplicationInfo().dataDir + "/lib"; + + return "/data/data/" + context.getPackageName() + "/lib"; + } + + public static void + main (String[] args) + { + Object activityThread, loadedApk; + Class activityThreadClass, loadedApkClass, contextImplClass; + Class compatibilityInfoClass; + Method method; + Context context; + AssetManager assets; + String filesDir, libDir, cacheDir; + + Looper.prepare (); + context = null; + assets = null; + filesDir = libDir = cacheDir = null; + + try + { + /* Get the activity thread. */ + activityThreadClass = Class.forName ("android.app.ActivityThread"); + + /* Get the systemMain method. */ + method = activityThreadClass.getMethod ("systemMain"); + + /* Create and attach the activity thread. */ + activityThread = method.invoke (null); + + /* Now get an LoadedApk. */ + loadedApkClass = Class.forName ("android.app.LoadedApk"); + + /* Get a LoadedApk. How to do this varies by Android version. + On Android 2.3.3 and earlier, there is no + ``compatibilityInfo'' argument to getPackageInfo. */ + + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD) + { + method + = activityThreadClass.getMethod ("getPackageInfo", + String.class, + int.class); + loadedApk = method.invoke (activityThread, "org.gnu.emacs", + 0); + } + else + { + compatibilityInfoClass + = Class.forName ("android.content.res.CompatibilityInfo"); + + method + = activityThreadClass.getMethod ("getPackageInfo", + String.class, + compatibilityInfoClass, + int.class); + loadedApk = method.invoke (activityThread, "org.gnu.emacs", null, + 0); + } + + if (loadedApk == null) + throw new RuntimeException ("getPackageInfo returned NULL"); + + /* Now, get a context. */ + contextImplClass = Class.forName ("android.app.ContextImpl"); + method = contextImplClass.getDeclaredMethod ("createAppContext", + activityThreadClass, + loadedApkClass); + method.setAccessible (true); + context = (Context) method.invoke (null, activityThread, loadedApk); + + /* Don't actually start the looper or anything. Instead, obtain + an AssetManager. */ + assets = context.getAssets (); + + /* Now configure Emacs. The class path should already be set. */ + + filesDir = context.getFilesDir ().getCanonicalPath (); + libDir = getLibraryDirectory (context); + cacheDir = context.getCacheDir ().getCanonicalPath (); + } + catch (Exception e) + { + System.err.println ("Internal error: " + e); + System.err.println ("This means that the Android platform changed,"); + System.err.println ("and that Emacs needs adjustments in order to"); + System.err.println ("obtain required system internal resources."); + System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org."); + + System.exit (1); + } + + EmacsNative.setEmacsParams (assets, filesDir, + libDir, cacheDir, 0.0f, + 0.0f, null, null); + + /* Now find the dump file that Emacs should use, if it has already + been dumped. */ + EmacsApplication.findDumpFile (context); + + /* Start Emacs. */ + EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); + } +}; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 4db1ea5359f..91db76b08e3 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -41,6 +41,9 @@ import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.net.Uri; @@ -118,8 +121,38 @@ public class EmacsService extends Service return null; } + @SuppressWarnings ("deprecation") + private String + getApkFile () + { + PackageManager manager; + ApplicationInfo info; + + manager = getPackageManager (); + + try + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + info = manager.getApplicationInfo ("org.gnu.emacs", 0); + else + info = manager.getApplicationInfo ("org.gnu.emacs", + ApplicationInfoFlags.of (0)); + + /* Return an empty string upon failure. */ + + if (info.sourceDir != null) + return info.sourceDir; + + return ""; + } + catch (Exception e) + { + return ""; + } + } + @TargetApi (Build.VERSION_CODES.GINGERBREAD) - String + private String getLibraryDirectory () { int apiLevel; @@ -142,7 +175,7 @@ public class EmacsService extends Service { AssetManager manager; Context app_context; - String filesDir, libDir, cacheDir; + String filesDir, libDir, cacheDir, classPath; double pixelDensityX; double pixelDensityY; @@ -162,13 +195,18 @@ public class EmacsService extends Service libDir = getLibraryDirectory (); cacheDir = app_context.getCacheDir ().getCanonicalPath (); + /* Now provide this application's apk file, so a recursive + invocation of app_process (through android-emacs) can + find EmacsNoninteractive. */ + classPath = getApkFile (); + Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir - + " and libDir = " + libDir); + + ", libDir = " + libDir + ", and classPath = " + classPath); EmacsNative.setEmacsParams (manager, filesDir, libDir, cacheDir, (float) pixelDensityX, (float) pixelDensityY, - this); + classPath, this); /* Start the thread that runs Emacs. */ thread = new EmacsThread (this, needDashQ); @@ -491,8 +529,6 @@ public class EmacsService extends Service public static void startEmacsService (Context context) { - PendingIntent intent; - if (EmacsService.SERVICE == null) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 5b76d11db4b..f5e9d54044a 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -33,30 +33,18 @@ public class EmacsThread extends Thread this.startDashQ = startDashQ; } + @Override public void run () { String args[]; - if (EmacsApplication.dumpFileName == null) - { - if (!startDashQ) - args = new String[] { "libandroid-emacs.so", }; - else - args = new String[] { "libandroid-emacs.so", "-Q", }; - } + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", }; else - { - if (!startDashQ) - args = new String[] { "libandroid-emacs.so", "--dump-file", - EmacsApplication.dumpFileName, }; - else - args = new String[] { "libandroid-emacs.so", "-Q", - "--dump-file", - EmacsApplication.dumpFileName, }; - } + args = new String[] { "libandroid-emacs.so", "-Q", }; /* Run the native code now. */ - EmacsNative.initEmacs (args); + EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); } }; diff --git a/lisp/loadup.el b/lisp/loadup.el index 8d2e4bbb7c2..1747d1d960a 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -560,22 +560,23 @@ lost after dumping"))) ;; different build fingerprint upon being created, which happens ;; the moment the Android system starts Emacs. Then, it passes ;; the appropriate "--dump-file" to libemacs.so as it starts. - (let ((temp-dir (getenv "TEMP")) - (dump-file-name (format "%semacs-%s.pdmp" - (file-name-as-directory "~") - pdumper-fingerprint)) - (dump-temp-file-name (format "%s~emacs-%s.pdmp" - (file-name-as-directory "~") - pdumper-fingerprint))) - (unless (pdumper-stats) - (condition-case () - (progn - (dump-emacs-portable dump-temp-file-name) - ;; Move the dumped file to the actual dump file name. - (rename-file dump-temp-file-name dump-file-name) - ;; Continue with loadup. - nil) - (error nil))))) + (when (not noninteractive) + (let ((temp-dir (getenv "TEMP")) + (dump-file-name (format "%semacs-%s.pdmp" + (file-name-as-directory "~") + pdumper-fingerprint)) + (dump-temp-file-name (format "%s~emacs-%s.pdmp" + (file-name-as-directory "~") + pdumper-fingerprint))) + (unless (pdumper-stats) + (condition-case () + (progn + (dump-emacs-portable dump-temp-file-name) + ;; Move the dumped file to the actual dump file name. + (rename-file dump-temp-file-name dump-file-name) + ;; Continue with loadup. + nil) + (error nil)))))) (if dump-mode (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp") ((equal dump-mode "dump") "emacs") diff --git a/lisp/shell.el b/lisp/shell.el index 5cf108bfa3b..29f7d5c02d4 100644 --- a/lisp/shell.el +++ b/lisp/shell.el @@ -1332,7 +1332,12 @@ Returns t if successful." (while path-dirs (setq dir (file-name-as-directory (comint-directory (or (car path-dirs) "."))) comps-in-dir (and (file-accessible-directory-p dir) - (file-name-all-completions filenondir dir))) + (condition-case nil + (file-name-all-completions filenondir dir) + ;; Systems such as Android sometimes + ;; put inaccessible directories in + ;; PATH. + (permission-denied nil)))) ;; Go thru each completion found, to see whether it should be used. (while comps-in-dir (setq file (car comps-in-dir) diff --git a/src/Makefile.in b/src/Makefile.in index 29beb519abb..fe745770b9f 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -768,7 +768,7 @@ libemacs.so: $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(EMACSRES) \ android-emacs: libemacs.so android-emacs.o $(AM_V_CCLD)$(CC) -o $@ $(ALL_CFLAGS) $(LDFLAGS) \ - -L. "-l:libemacs.so" android-emacs.o + $(LIBEGNU_ARCHIVE) -L. "-l:libemacs.so" android-emacs.o endif ## The following oldxmenu-related rules are only (possibly) used if diff --git a/src/android-emacs.c b/src/android-emacs.c index d4fa14e39fb..c1f2a6f43bb 100644 --- a/src/android-emacs.c +++ b/src/android-emacs.c @@ -18,13 +18,88 @@ You should have received a copy of the GNU General Public License along with GNU Emacs. If not, see . */ #include -#include "android.h" +#include +#include +#include +#include -/* android-emacs is a wrapper around libemacs. It simply calls - android_emacs_init with the argv and argc given to it. */ +/* android-emacs is a wrapper around /system/bin/app_process(64). + It invokes app_process(64) with the right class path and then + starts org.gnu.emacs.EmacsNoninteractive. + + The main function in that class tries to load an activity thread + and obtain a context and asset manager before calling + android_emacs_init, which is required for Emacs to find required + preloaded Lisp. */ int main (int argc, char **argv) { - return android_emacs_init (argc, argv); + char **args; + int i; + char *bootclasspath, *emacs_class_path; + + /* Allocate enough to hold the arguments to app_process. */ + args = alloca ((10 + argc) * sizeof *args); + + /* Clear args. */ + memset (args, 0, (10 + argc) * sizeof *args); + + /* First, figure out what program to start. */ +#if defined __x86_64__ || defined __aarch64__ + args[0] = (char *) "/system/bin/app_process64"; +#else + args[0] = (char *) "/system/bin/app_process"; +#endif + + /* Next, obtain the boot class path. */ + bootclasspath = getenv ("BOOTCLASSPATH"); + + /* And the Emacs class path. */ + emacs_class_path = getenv ("EMACS_CLASS_PATH"); + + if (!bootclasspath) + { + fprintf (stderr, "The BOOTCLASSPATH environment variable" + " is not set. As a result, Emacs does not know" + " how to start app_process.\n" + "This is likely a change in the Android platform." + " Please report this to bug-gnu-emacs@gnu.org.\n"); + return 1; + } + + if (!emacs_class_path) + { + fprintf (stderr, "EMACS_CLASS_PATH not set." + " Please make sure Emacs is being started" + " from within a running copy of Emacs.\n"); + return 1; + } + + if (asprintf (&bootclasspath, "-Djava.class.path=%s:%s", + bootclasspath, emacs_class_path) < 0) + { + perror ("asprintf"); + return 1; + } + + args[1] = bootclasspath; + args[2] = (char *) "/system/bin"; + args[3] = (char *) "--nice-name=emacs"; + args[4] = (char *) "org.gnu.emacs.EmacsNoninteractive"; + + /* Arguments from here on are passed to main in + EmacsNoninteractive.java. */ + args[5] = argv[0]; + + /* Now copy the rest of the arguments over. */ + for (i = 1; i < argc; ++i) + args[5 + i] = argv[i]; + + /* Finally, try to start the app_process. */ + execvp (args[0], args); + + /* If exit fails, return an error indication. */ + perror ("exec"); + return 1; } diff --git a/src/android.c b/src/android.c index c186b462360..8c4442e3397 100644 --- a/src/android.c +++ b/src/android.c @@ -145,6 +145,10 @@ char *android_game_path; /* The directory used to store temporary files. */ char *android_cache_dir; +/* The list of archive files within which the Java virtual macine + looks for class files. */ +char *android_class_path; + /* The display's pixel densities. */ double android_pixel_density_x, android_pixel_density_y; @@ -1315,6 +1319,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, jobject cache_dir, jfloat pixel_density_x, jfloat pixel_density_y, + jobject class_path, jobject emacs_service_object) { int pipefd[2]; @@ -1372,19 +1377,30 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, 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 (emacs_service_object) + { + /* 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 (); + Notice that this function is called in one of two ways. The + first is when Emacs is being started as a GUI application by + the system, and the second is when Emacs is being started by + libandroid-emacs.so as an ordinary noninteractive Emacs. - if (dup2 (pipefd[1], 2) < 0) - emacs_abort (); - close (pipefd[1]); + In the second case, stderr is usually connected to a PTY, so + this is unnecessary. */ - if (pthread_create (&thread, NULL, android_run_debug_thread, - (void *) (intptr_t) pipefd[0])) - emacs_abort (); + 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. */ @@ -1430,6 +1446,23 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, (*env)->ReleaseStringUTFChars (env, (jstring) cache_dir, java_string); + if (class_path) + { + java_string = (*env)->GetStringUTFChars (env, (jstring) class_path, + NULL); + + if (!java_string) + emacs_abort (); + + android_class_path = strdup ((const char *) java_string); + + if (!android_files_dir) + emacs_abort (); + + (*env)->ReleaseStringUTFChars (env, (jstring) class_path, + java_string); + } + /* Calculate the site-lisp path. */ android_site_load_path = malloc (PATH_MAX + 1); @@ -1450,16 +1483,32 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, "Site-lisp directory: %s\n" "Files directory: %s\n" "Native code directory: %s\n" - "Game score path: %s\n", + "Game score path: %s\n" + "Class path: %s\n", android_site_load_path, android_files_dir, - android_lib_dir, android_game_path); + android_lib_dir, android_game_path, + (android_class_path + ? android_class_path + : "None")); + + if (android_class_path) + /* Set EMACS_CLASS_PATH to the class path where + EmacsNoninteractive can be found. */ + setenv ("EMACS_CLASS_PATH", android_class_path, 1); + + /* Set LD_LIBRARY_PATH to an appropriate value. */ + setenv ("LD_LIBRARY_PATH", android_lib_dir, 1); /* Make a reference to the Emacs service. */ - emacs_service = (*env)->NewGlobalRef (env, emacs_service_object); - if (!emacs_service) - emacs_abort (); + if (emacs_service_object) + { + emacs_service = (*env)->NewGlobalRef (env, emacs_service_object); + + if (!emacs_service) + emacs_abort (); + } /* Set up events. */ android_init_events (); @@ -1660,12 +1709,14 @@ android_init_emacs_window (void) } extern JNIEXPORT void JNICALL -NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv) +NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, + jobject dump_file_object) { char **c_argv; jsize nelements, i; jobject argument; const char *c_argument; + char *dump_file; android_java_env = env; @@ -1705,9 +1756,34 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv) __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); + /* Initialize the Android GUI as long as the service object was + set. */ + + if (emacs_service) + android_init_gui = true; + + /* Now see if a dump file has been specified and should be used. */ + dump_file = NULL; + + if (dump_file_object) + { + c_argument + = (*env)->GetStringUTFChars (env, (jstring) dump_file_object, + NULL); + + /* Copy the Java string data once. */ + dump_file = strdup (c_argument); + + /* Release the Java string data. */ + (*env)->ReleaseStringUTFChars (env, (jstring) dump_file_object, + c_argument); + } + + /* Delete local references to objects that are no longer needed. */ + ANDROID_DELETE_LOCAL_REF (argv); + ANDROID_DELETE_LOCAL_REF (dump_file_object); + + android_emacs_init (nelements, c_argv, dump_file); /* android_emacs_init should never return. */ emacs_abort (); } diff --git a/src/android.h b/src/android.h index ce0ccfc4338..6c20995e4a1 100644 --- a/src/android.h +++ b/src/android.h @@ -38,15 +38,11 @@ along with GNU Emacs. If not, see . */ #include "lisp.h" #endif -/* This must be used in every symbol declaration to export it to the - JNI Emacs wrapper. */ -#define ANDROID_EXPORT __attribute__ ((visibility ("default"))) - -extern bool ANDROID_EXPORT android_init_gui; -extern int ANDROID_EXPORT android_emacs_init (int, char **); +extern bool android_init_gui; #ifndef ANDROID_STUBIFY +extern int android_emacs_init (int, char **, char *); extern int android_select (int, fd_set *, fd_set *, fd_set *, struct timespec *); diff --git a/src/androidfont.c b/src/androidfont.c index bec5a59ca77..55f45758352 100644 --- a/src/androidfont.c +++ b/src/androidfont.c @@ -1104,6 +1104,9 @@ syms_of_androidfont (void) void init_androidfont (void) { + if (!android_init_gui) + return; + android_init_font_driver (); android_init_font_spec (); android_init_font_metrics (); diff --git a/src/androidselect.c b/src/androidselect.c index 2e2f2d7e483..4585d64b7e8 100644 --- a/src/androidselect.c +++ b/src/androidselect.c @@ -240,6 +240,9 @@ init_androidselect (void) jobject tem; jmethodID make_clipboard; + if (!android_init_gui) + return; + android_init_emacs_clipboard (); make_clipboard = clipboard_class.make_clipboard; diff --git a/src/emacs.c b/src/emacs.c index b0c19fe0070..a24f9960494 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -875,19 +875,23 @@ dump_error_to_string (int result) } } -/* This function returns the Emacs executable. */ +/* This function returns the Emacs executable. DUMP_FILE is ignored + outside of Android. Otherwise, it is the name of the dump file to + use, or NULL if Emacs should look for a ``--dump-file'' argument + instead. */ + static char * -load_pdump (int argc, char **argv) +load_pdump (int argc, char **argv, char *dump_file) { #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY - char *dump_file = NULL; int skip_args = 0, result; while (skip_args < argc - 1) { - if (argmatch (argv, argc, "-dump-file", "--dump-file", 6, - &dump_file, &skip_args) - || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args)) + if (argmatch (argv, argc, "-dump-file", "--dump-file", + 6, &dump_file, &skip_args) + || argmatch (argv, argc, "--", NULL, 2, NULL, + &skip_args)) break; skip_args++; } @@ -933,7 +937,7 @@ load_pdump (int argc, char **argv) /* Look for an explicitly-specified dump file. */ const char *path_exec = PATH_EXEC; - char *dump_file = NULL; + dump_file = NULL; int skip_args = 0; while (skip_args < argc - 1) { @@ -1276,7 +1280,7 @@ maybe_load_seccomp (int argc, char **argv) #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY int -android_emacs_init (int argc, char **argv) +android_emacs_init (int argc, char **argv, char *dump_file) #else int main (int argc, char **argv) @@ -1286,6 +1290,12 @@ main (int argc, char **argv) for pointers. */ void *stack_bottom_variable; int old_argc; +#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) + char *dump_file; + + /* This is just a dummy argument used to avoid extra defines. */ + dump_file = NULL; +#endif /* First, check whether we should apply a seccomp filter. This should come at the very beginning to allow the filter to protect @@ -1415,7 +1425,7 @@ main (int argc, char **argv) #ifdef HAVE_PDUMPER if (attempt_load_pdump) - initial_emacs_executable = load_pdump (argc, argv); + initial_emacs_executable = load_pdump (argc, argv, dump_file); #else ptrdiff_t bufsize; initial_emacs_executable = find_emacs_executable (argv[0], &bufsize); diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index bc8f1b35b84..ab90a2e4ecd 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -729,6 +729,9 @@ syms_of_sfntfont_android_for_pdumper (void) void init_sfntfont_android (void) { + if (!android_init_gui) + return; + /* Make sure to pick the right Sans Serif font depending on what version of Android the device is running. */ #if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL -- cgit v1.3 From 22f7ad1057e1a1e20933e0a1ff2a858ecd9e3fec Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 26 Jan 2023 19:54:38 +0800 Subject: Update Android port * INSTALL.android: Document how to install sqlite3. * build-aux/ndk-build-helper-1.mk (SYSTEM_LIBRARIES): * build-aux/ndk-build-helper-2.mk (SYSTEM_LIBRARIES): Add liblog and libandroid. * configure.ac (SQLITE3_LIBS, HAVE_SQLITE3) (HAVE_SQLITE3_LOAD_EXTENSION): Support on Android. (APKSIGNER): Look for this new required binary. * cross/ndk-build/ndk-build-shared-library.mk (objname): * cross/ndk-build/ndk-build-static-library.mk (objname): Avoid duplicate rules by prefixing objects with module type. * cross/ndk-build/ndk-build.mk.in (NDK_BUILD_SHARED): Fix definition. * cross/ndk-build/ndk-resolve.mk: (NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)): Handle new system libraries. * doc/emacs/android.texi (Android File System): Document Android 10 system restriction. * java/AndroidManifest.xml.in: Target Android 33, not 28. * java/Makefile.in (SIGN_EMACS_V2, APKSIGNER): New variables. ($(APK_NAME)): Make sure to apply a ``version 2 signature'' to the package as well. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New argument apiLevel. * java/org/gnu/emacs/EmacsNoninteractive.java (main): * java/org/gnu/emacs/EmacsThread.java (run): Pass API level. * m4/ndk-build.m4 (ndk_package_mape): Add package mapping for sqlite3. * src/Makefile.in (SQLITE3_CFLAGS): New substition. (EMACS_CFLAGS): Add that variable. * src/android.c (android_api_level): New variable. (initEmacs): Set it. (android_file_access_p): Make static. (android_hack_asset_fd): Adjust for restrictions in Android 29 and later. (android_close_on_exec): New function. (android_open): Adjust to not duplicate file descriptor even if CLOEXEC. (android_faccessat): Use fstatat at-func emulation. * src/android.h: Update prototypes. * src/dired.c (file_name_completion_dirp): * src/fileio.c (file_access_p, Faccess_file): Now that sys_faccessat takes care of everything, stop calling android_file_access_p. --- INSTALL.android | 18 +++ build-aux/ndk-build-helper-1.mk | 2 +- build-aux/ndk-build-helper-2.mk | 2 +- configure.ac | 69 ++++++--- cross/ndk-build/ndk-build-shared-library.mk | 7 +- cross/ndk-build/ndk-build-static-library.mk | 2 +- cross/ndk-build/ndk-build.mk.in | 2 +- cross/ndk-build/ndk-resolve.mk | 30 +++- doc/emacs/android.texi | 28 +++- java/AndroidManifest.xml.in | 2 +- java/Makefile.in | 9 +- java/org/gnu/emacs/EmacsNative.java | 7 +- java/org/gnu/emacs/EmacsNoninteractive.java | 3 +- java/org/gnu/emacs/EmacsThread.java | 5 +- m4/ndk-build.m4 | 1 + src/Makefile.in | 3 +- src/android.c | 220 ++++++++++++++++++++-------- src/android.h | 1 - src/dired.c | 6 - src/fileio.c | 12 -- 20 files changed, 305 insertions(+), 124 deletions(-) (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/INSTALL.android b/INSTALL.android index ed0bd0f68dc..06211e5ec93 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -165,6 +165,9 @@ work, along with what has to be patched to make them work: and apply the patch at the end of this file.) icu4c - https://android.googlesource.com/platform/external/icu/ (You must apply the patch at the end of this file.) + sqlite3 - https://android.googlesource.com/platform/external/sqlite/ + (You must apply the patch at the end of this file, and add the `dist' + directory to ``--with-ndk-path''.) Many of these dependencies have been migrated over to the ``Android.bp'' build system now used to build Android itself. @@ -592,3 +595,18 @@ index 8e5f757..44bb130 100644 LOCAL_MODULE_TAGS := optional LOCAL_MODULE := libicuuc LOCAL_RTTI_FLAG := -frtti + +PATCH FOR SQLITE3 + +diff --git a/dist/Android.mk b/dist/Android.mk +index bf277d2..36734d9 100644 +--- a/dist/Android.mk ++++ b/dist/Android.mk +@@ -141,6 +141,7 @@ include $(BUILD_HOST_EXECUTABLE) + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(common_src_files) + LOCAL_CFLAGS += $(minimal_sqlite_flags) ++LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH) + LOCAL_MODULE:= libsqlite_static_minimal + LOCAL_SDK_VERSION := 23 + include $(BUILD_STATIC_LIBRARY) diff --git a/build-aux/ndk-build-helper-1.mk b/build-aux/ndk-build-helper-1.mk index 4ffe0d423e4..9035b5ca59a 100644 --- a/build-aux/ndk-build-helper-1.mk +++ b/build-aux/ndk-build-helper-1.mk @@ -77,7 +77,7 @@ endef # dependencies can be ignored while building a shared library, as they # will be linked in to the resulting shared object file later. -SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ +SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ log liblog android libandroid $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module)))) $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES) $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_LIBRARIES)),$(eval $(call add-includes,$(module)))) diff --git a/build-aux/ndk-build-helper-2.mk b/build-aux/ndk-build-helper-2.mk index 9c9e7cf09a9..8f2f397e534 100644 --- a/build-aux/ndk-build-helper-2.mk +++ b/build-aux/ndk-build-helper-2.mk @@ -87,7 +87,7 @@ endef # Resolve additional dependencies based on LOCAL_STATIC_LIBRARIES and # LOCAL_SHARED_LIBRARIES. -SYSTEM_LIBRARIES = z libz libc c libdl dl libstdc++ stdc++ +SYSTEM_LIBRARIES = z libz libc c libdl dl libstdc++ stdc++ log liblog android libandroid $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES)),$(eval $(call add-a-name,$(module)))) $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module)))) diff --git a/configure.ac b/configure.ac index 95bb0cfca88..879e4ab74aa 100644 --- a/configure.ac +++ b/configure.ac @@ -752,6 +752,7 @@ ANDROID= JAVAC= AAPT= JARSIGNER= +APKSIGNER= ZIPALIGN= DX= ANDROID_JAR= @@ -763,6 +764,7 @@ android_makefiles="lib/Makefile lib/gnulib.mk lib-src/Makefile src/Makefile" AC_ARG_VAR([JAVAC], [Java compiler path. Used for Android.]) AC_ARG_VAR([JARSIGNER], [Java package signer path. Used for Android.]) +AC_ARG_VAR([APKSIGNER], [Android package signer path. Used for Android.]) AC_ARG_VAR([SDK_BULD_TOOLS], [Path to the Android SDK build tools.]) if test "$with_android" = "yes"; then @@ -792,6 +794,7 @@ the path to your Java compiler before configuring Emacs, like so: JAVAC=/opt/jdk/bin/javac ./configure --with-android]) fi + AC_CHECK_PROGS([JARSIGNER], [jarsigner]) if test "$JARSIGNER" = ""; then AC_MSG_ERROR([The Java package signing utility was not found. @@ -861,6 +864,12 @@ Android 13 (Tiramisu) or later.]) Please verify that the path to the SDK build tools you specified is correct]) fi + AC_PATH_PROGS([APKSIGNER], [apksigner], [], "${SDK_BUILD_TOOLS}:$PATH") + if test "$APKSIGNER" = ""; then + AC_MSG_ERROR([The Android package signing tool was not found. +Please verify that the path to the SDK build tools you specified is correct]) + fi + AC_PATH_PROGS([D8], [d8], [], "${SDK_BUILD_TOOLS}:$PATH") if test "D8" = ""; then AC_MSG_ERROR([The Android dexer was not found. @@ -1051,6 +1060,7 @@ package will likely install on older systems but crash on startup.]) passthrough="$passthrough --with-json=$with_json" passthrough="$passthrough --with-jpeg=$with_jpeg" passthrough="$passthrough --with-xml2=$with_xml2" + passthrough="$passthrough --with-sqlite3=$with_sqlite3" AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ ANDROID_SDK="$android_sdk" android_abi=$android_abi \ @@ -1120,10 +1130,10 @@ if test "$ANDROID" = "yes"; then with_json=no with_jpeg=no with_xml2=no + with_sqlite3=no fi with_rsvg=no - with_sqlite3=no with_lcms2=no with_libsystemd=no with_cairo=no @@ -3348,30 +3358,49 @@ fi ### Use -lsqlite3 if available, unless '--with-sqlite3=no' HAVE_SQLITE3=no +SQLITE3_LIBS= +SQLITE3_CFLAGS= if test "${with_sqlite3}" != "no"; then - AC_CHECK_LIB([sqlite3], [sqlite3_open_v2], - [HAVE_SQLITE3=yes], - [HAVE_SQLITE3=no]) - if test "$HAVE_SQLITE3" = "yes"; then - SQLITE3_LIBS=-lsqlite3 - AC_SUBST([SQLITE3_LIBS]) - LIBS="$SQLITE3_LIBS $LIBS" - AC_DEFINE([HAVE_SQLITE3], [1], - [Define to 1 if you have the libsqlite3 library (-lsqlite).]) - # Windows loads libsqlite dynamically - if test "${opsys}" = "mingw32"; then - SQLITE3_LIBS= + if test "${REALLY_ANDROID}" = "yes"; then + ndk_SEARCH_MODULE([sqlite3], [SQLITE3], [HAVE_SQLITE3=yes]) + + if test "$HAVE_SQLITE3" = "yes"; then + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $SQLITE3_CFLAGS" + AC_CHECK_DECL([sqlite3_open_v2], [HAVE_SQLITE=yes], + [HAVE_SQLITE3=no], [#include ]) + CFLAGS="$SAVE_CFLAGS" fi - AC_CHECK_LIB([sqlite3], [sqlite3_load_extension], - [HAVE_SQLITE3_LOAD_EXTENSION=yes], - [HAVE_SQLITE3_LOAD_EXTENSION=no]) - if test "$HAVE_SQLITE3_LOAD_EXTENSION" = "yes"; then - AC_DEFINE([HAVE_SQLITE3_LOAD_EXTENSION], [1], - [Define to 1 if sqlite3 supports loading extensions.]) + else + AC_CHECK_LIB([sqlite3], [sqlite3_open_v2], + [HAVE_SQLITE3=yes], + [HAVE_SQLITE3=no]) + if test "$HAVE_SQLITE3" = "yes"; then + SQLITE3_LIBS=-lsqlite3 + LIBS="$SQLITE3_LIBS $LIBS" + # Windows loads libsqlite dynamically + if test "${opsys}" = "mingw32"; then + SQLITE3_LIBS= + fi + AC_CHECK_LIB([sqlite3], [sqlite3_load_extension], + [HAVE_SQLITE3_LOAD_EXTENSION=yes], + [HAVE_SQLITE3_LOAD_EXTENSION=no]) + if test "$HAVE_SQLITE3_LOAD_EXTENSION" = "yes"; then + AC_DEFINE([HAVE_SQLITE3_LOAD_EXTENSION], [1], + [Define to 1 if sqlite3 supports loading extensions.]) + fi fi - fi + fi + + if test "$HAVE_SQLITE3" = "yes"; then + AC_DEFINE([HAVE_SQLITE3], [1], + [Define to 1 if you have the libsqlite3 library (-lsqlite).]) + fi fi +AC_SUBST([SQLITE3_LIBS]) +AC_SUBST([SQLITE3_CFLAGS]) + HAVE_IMAGEMAGICK=no if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes" || \ test "${HAVE_BE_APP}" = "yes" || test "${window_system}" = "pgtk"; then diff --git a/cross/ndk-build/ndk-build-shared-library.mk b/cross/ndk-build/ndk-build-shared-library.mk index a4b7b47f749..b69641ba9b0 100644 --- a/cross/ndk-build/ndk-build-shared-library.mk +++ b/cross/ndk-build/ndk-build-shared-library.mk @@ -20,7 +20,12 @@ # which actually builds targets. eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) -objname = $(1)-$(subst /,_,$(2).o) + +# Objects for shared libraries are prefixed with `-shared-' in +# addition to the name of the module, because a common practice in +# Android.mk files written by Google is to define two modules with the +# same name but of different types. +objname = $(1)-shared-$(subst /,_,$(2).o) # Here are the default flags to link shared libraries with. NDK_SO_DEFAULT_LDFLAGS := -lc -lm diff --git a/cross/ndk-build/ndk-build-static-library.mk b/cross/ndk-build/ndk-build-static-library.mk index 4d16d81330c..349b9242b1f 100644 --- a/cross/ndk-build/ndk-build-static-library.mk +++ b/cross/ndk-build/ndk-build-static-library.mk @@ -20,7 +20,7 @@ # which actually builds targets. eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) -objname = $(1)-$(subst /,_,$(2).o) +objname = $(1)-static-$(subst /,_,$(2).o) define single-object-target diff --git a/cross/ndk-build/ndk-build.mk.in b/cross/ndk-build/ndk-build.mk.in index 798dcf6c19e..5b0aa82856d 100644 --- a/cross/ndk-build/ndk-build.mk.in +++ b/cross/ndk-build/ndk-build.mk.in @@ -38,7 +38,7 @@ NDK_BUILD_MODULES := $(call uniqify,$(NDK_BUILD_MODULES)) # requires the C++ standard library. ifneq ($(NDK_BUILD_ANY_CXX_MODULE),) -NDK_BUILD_SHARED += $(NDK_BUILD_ANY_CXX_SHARED) +NDK_BUILD_SHARED += $(NDK_BUILD_CXX_SHARED) endif define subr-1 diff --git a/cross/ndk-build/ndk-resolve.mk b/cross/ndk-build/ndk-resolve.mk index 910be8dab53..c2e281c53ba 100644 --- a/cross/ndk-build/ndk-resolve.mk +++ b/cross/ndk-build/ndk-resolve.mk @@ -43,32 +43,50 @@ NDK_CFLAGS_$(LOCAL_MODULE) += $(addprefix -I,$(NDK_LOCAL_EXPORT_C_INCLUDES_$(1)) # If the module happens to be zlib, then add -lz to the shared library # flags. -ifneq ($(strip $(1)),libz) +ifeq ($(strip $(1)),libz) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lz endif -ifneq ($(strip $(1)),z) +ifeq ($(strip $(1)),z) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lz endif # Likewise for libdl. -ifneq ($(strip $(1)),libdl) +ifeq ($(strip $(1)),libdl) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -ldl endif -ifneq ($(strip $(1)),dl) +ifeq ($(strip $(1)),dl) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -ldl endif # Likewise for libstdc++. -ifneq ($(strip $(1)),libstdc++) +ifeq ($(strip $(1)),libstdc++) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lstdc++ endif -ifneq ($(strip $(1)),dl) +ifeq ($(strip $(1)),dl) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lstdc++ endif +# Likewise for liblog. +ifeq ($(strip $(1)),liblog) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -llog +endif + +ifeq ($(strip $(1)),log) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -llog +endif + +# Likewise for libandroid. +ifeq ($(strip $(1)),libandroid) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -landroid +endif + +ifeq ($(strip $(1)),android) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -landroid +endif + ifneq ($(2),) ifneq ($(findstring lib,$(1)),) NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) += $(1).a diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index e910e482ad8..98d7f1e1d9e 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -160,13 +160,35 @@ The @dfn{app library} directory. This is automatically appended to @item The @dfn{external storage} directory. This is accessible to Emacs -when the user grants the @code{Files and media} permission to Emacs -via system settings. +when the user grants the ``Files and Media'' permission to Emacs via +system settings. @end itemize -The external storage directory is found at @file{/sdcard}; the other + The external storage directory is found at @file{/sdcard}; the other directories are not found at any fixed location. +@cindex file system limitations, Android 10 + On Android 10 and later, the Android system restricts applications +from accessing files in the @file{/sdcard} directory using +file-related system calls such as @code{open} and @code{readdir}. + + This restriction is known as ``Scoped Storage'', and supposedly +makes the system more secure. Unfortunately, it also means that Emacs +cannot access files in those directories, despite holding the +necessary permissions. Thankfully, the Open Handset Alliance's +version of Android allows this restriction to be disabled on a +per-program basis; the corresponding option in the system settings +panel is: + +@indentedblock +System -> Developer Options -> App Compatibility Changes -> Emacs -> +DEFAULT_SCOPED_STORAGE +@end indentedblock + + After you disable this setting and grant Emacs the ``Files and +Media'' permission, it will be able to access files under +@file{/sdcard} as usual. + @node Android Environment @section Running Emacs under Android diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index c4a9d1f5177..527ce74c474 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -53,7 +53,7 @@ along with GNU Emacs. If not, see . --> + android:targetSdkVersion="33"/> = ANDROID_MAX_ASSET_FD || fd < 0) { - /* Too bad. You lose. */ + /* Too bad. Pretend this is an out of memory error. */ errno = ENOMEM; if (fd >= 0) @@ -1713,7 +1791,7 @@ android_init_emacs_window (void) extern JNIEXPORT void JNICALL NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, - jobject dump_file_object) + jobject dump_file_object, jint api_level) { char **c_argv; jsize nelements, i; @@ -1732,6 +1810,9 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, /* Trick GCC into not optimizing this variable away. */ unused_pointer = buffer; + /* Set the Android API level. */ + android_api_level = api_level; + android_java_env = env; nelements = (*env)->GetArrayLength (env, argv); @@ -4214,34 +4295,47 @@ android_window_updated (android_window window, unsigned long serial) -#if __ANDROID_API__ >= 16 +/* When calling the system's faccessat, make sure to clear the flag + AT_EACCESS. -/* 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. + it with zero here. This isn't caught during configuration as Emacs + is being cross compiled. This replacement is only done when building for Android 16 or later, because earlier versions use the gnulib replacement that - lacks these issues. */ + lacks these issues. + + This is unnecessary on earlier API versions, as gnulib's + rpl_faccessat will be used instead, which lacks these problems. */ + +/* Like faccessat, except it also understands DIRFD opened using + android_dirfd. */ int android_faccessat (int dirfd, const char *pathname, int mode, int flags) { - return faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); -} + const char *asset; -#else /* __ANDROID_API__ < 16 */ + if (dirfd != AT_FDCWD) + dirfd + = android_lookup_asset_directory_fd (dirfd, &pathname, + pathname); -/* This is unnecessary on earlier API versions, as gnulib's - rpl_faccessat will be used instead. */ + /* Check if pathname is actually an asset. If that is the case, + simply fall back to android_file_access_p. */ -int -android_faccessat (int dirfd, const char *pathname, int mode, int flags) -{ - return faccessat (dirfd, pathname, mode, flags); -} + if (dirfd == AT_FDCWD + && asset_manager + && (asset = android_get_asset_name (pathname))) + return !android_file_access_p (asset, mode); +#if __ANDROID_API__ >= 16 + return faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); +#else + return faccessat (dirfd, pathname, mode, flags); #endif +} @@ -4468,10 +4562,10 @@ android_closedir (struct android_dir *dir) 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. */ +/* Subroutine used by android_fstatat and android_faccessat. If DIRFD + belongs to an open asset directory and FILE is a relative file + name, then return AT_FDCWD and the absolute file name of the + directory prepended to FILE in *PATHNAME. Else, return DIRFD. */ int android_lookup_asset_directory_fd (int dirfd, diff --git a/src/android.h b/src/android.h index 33fad512d4a..8234dbb07c0 100644 --- a/src/android.h +++ b/src/android.h @@ -46,7 +46,6 @@ extern int android_emacs_init (int, char **, char *); extern int android_select (int, fd_set *, fd_set *, fd_set *, struct timespec *); -extern bool android_file_access_p (const char *, int); extern int android_open (const char *, int, int); extern char *android_user_full_name (struct passwd *); extern int android_fstat (int, struct stat *); diff --git a/src/dired.c b/src/dired.c index b38416e981a..93487d552e2 100644 --- a/src/dired.c +++ b/src/dired.c @@ -888,12 +888,6 @@ file_name_completion_dirp (int fd, struct dirent *dp, ptrdiff_t len) memcpy (subdir_name, dp->d_name, len); strcpy (subdir_name + len, "/"); -#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY - /* Check if subdir_name lies in the assets directory. */ - if (android_file_access_p (subdir_name, F_OK)) - return true; -#endif - bool dirp = sys_faccessat (fd, subdir_name, F_OK, AT_EACCESS) == 0; SAFE_FREE (); diff --git a/src/fileio.c b/src/fileio.c index 6f25506dbc2..71f83ea6daf 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -182,12 +182,6 @@ file_access_p (char const *file, int amode) } #endif -#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY - /* FILE may be some kind of special Android file. */ - if (android_file_access_p (file, amode)) - return true; -#endif - if (sys_faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0) return true; @@ -3018,12 +3012,6 @@ If there is no error, returns nil. */) encoded_filename = ENCODE_FILE (absname); -#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY - /* FILE may be some kind of special Android file. */ - if (android_file_access_p (SSDATA (encoded_filename), R_OK)) - return Qnil; -#endif - if (sys_faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, AT_EACCESS) != 0) report_file_error (SSDATA (string), filename); -- cgit v1.3 From 88afd96e36e62017c9c1f2229e2748b6dfbdb39a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 17 Feb 2023 16:27:00 +0800 Subject: 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. --- INSTALL.android | 15 +- configure.ac | 23 +- java/Makefile.in | 14 +- java/debug.sh | 8 +- java/org/gnu/emacs/EmacsApplication.java | 1 + java/org/gnu/emacs/EmacsService.java | 25 +- java/org/gnu/emacs/EmacsThread.java | 9 +- src/android-asset.h | 423 +++++++++++++++++++++++++++++++ src/android.c | 72 +++++- src/process.c | 2 +- src/sfntfont-android.c | 2 +- src/sfntfont.c | 2 +- src/textconv.c | 2 +- 13 files changed, 574 insertions(+), 24 deletions(-) create mode 100644 src/android-asset.h (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/INSTALL.android b/INSTALL.android index a8d73331493..ab39a55eaf3 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -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 diff --git a/configure.ac b/configure.ac index b4fed263cf5..a4a16ec8062 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]) 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 \ diff --git a/java/Makefile.in b/java/Makefile.in index fc0d23980e4..92c03469c69 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -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`; \ diff --git a/java/debug.sh b/java/debug.sh index cbef7518984..30e5a94eee5 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -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 diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java index 96328b99d1c..6a065165eb1 100644 --- a/java/org/gnu/emacs/EmacsApplication.java +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -49,6 +49,7 @@ public class EmacsApplication extends Application for a file named ``emacs-.pdmp'' and delete the rest. */ filesDirectory = context.getFilesDir (); + allFiles = filesDirectory.listFiles (new FileFilter () { @Override public boolean diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2acb3ead086..4d373937ab0 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -180,11 +180,11 @@ public class EmacsService extends Service 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 @@ public class EmacsService extends Service 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) diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 2724d838d41..30484710651 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -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); diff --git a/src/android-asset.h b/src/android-asset.h new file mode 100644 index 00000000000..b0e83bbf424 --- /dev/null +++ b/src/android-asset.h @@ -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 . */ + +#include + +/* 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); +} diff --git a/src/android.c b/src/android.c index 8f446224dab..09b9001b45d 100644 --- a/src/android.c +++ b/src/android.c @@ -32,6 +32,9 @@ along with GNU Emacs. If not, see . */ #include #include +/* Old NDK versions lack MIN and MAX. */ +#include + #include #include @@ -49,8 +52,13 @@ bool android_init_gui; #ifndef ANDROID_STUBIFY +#if __ANDROID_API__ >= 9 #include #include +#else +#include "android-asset.h" +#endif + #include #include @@ -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; diff --git a/src/process.c b/src/process.c index e7ccb2c604e..bdaaba70fea 100644 --- a/src/process.c +++ b/src/process.c @@ -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 */ diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index c28a911bfba..8324185cc6f 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -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]) { }, }; diff --git a/src/sfntfont.c b/src/sfntfont.c index f9344067f1a..20c109f2401 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -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); diff --git a/src/textconv.c b/src/textconv.c index 5090b0a33b6..8f5a4987d5a 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -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; -- cgit v1.3 From b776feb7f2737fb6b3fca05ae3b786dc67a2a9ae Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 13 Mar 2023 13:25:02 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Startup): Document changes to emacsclient wrapper. * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity) (startEmacsClient): Open EmacsActivity if the service is not running. * java/org/gnu/emacs/EmacsService.java (onCreate): * java/org/gnu/emacs/EmacsThread.java (EmacsThread, run): Pass any file to open to Emacs. * lisp/term/android-win.el (handle-args-function): Implement. --- doc/emacs/android.texi | 5 +++++ java/org/gnu/emacs/EmacsOpenActivity.java | 15 +++++++++++++++ java/org/gnu/emacs/EmacsService.java | 7 +++++-- java/org/gnu/emacs/EmacsThread.java | 29 +++++++++++++++++++++++++---- lisp/term/android-win.el | 8 ++++---- 5 files changed, 54 insertions(+), 10 deletions(-) (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index d50acda7710..15415f12570 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -133,6 +133,11 @@ file, it invokes @command{emacsclient} with the options and the name of the file being opened. Then, upon success, the focus is transferred to any open Emacs frame. + However, if Emacs is not running at the time the wrapper is opened, +it starts Emacs and gives it the file to open as an argument. Note +that if that Emacs in turn does not start the Emacs server, subsequent +attempts to open the file with the wrapper will fail. + @cindex /content directory, android Some files are given to Emacs as ``content identifiers'', which the system provides access to outside the normal filesystem APIs. Emacs diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index e8fb24d53d8..f402e25c7fb 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -72,6 +72,7 @@ public final class EmacsOpenActivity extends Activity DialogInterface.OnCancelListener { private static final String TAG = "EmacsOpenActivity"; + public static String fileToOpen; private class EmacsClientThread extends Thread { @@ -317,6 +318,20 @@ public final class EmacsOpenActivity extends Activity Process process; EmacsClientThread thread; File file; + Intent intent; + + /* If the Emacs service is not running, then start Emacs and make + it open this file. */ + + if (EmacsService.SERVICE == null) + { + fileToOpen = fileName; + intent = new Intent (EmacsOpenActivity.this, + EmacsActivity.class); + finish (); + startActivity (intent); + return; + } libDir = EmacsService.getLibraryDirectory (this); builder = new ProcessBuilder (libDir + "/libemacsclient.so", diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 9c48c56ca26..33436892caa 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -215,7 +215,8 @@ public final class EmacsService extends Service classPath = getApkFile (); Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir - + ", libDir = " + libDir + ", and classPath = " + classPath); + + ", libDir = " + libDir + ", and classPath = " + classPath + + "; fileToOpen = " + EmacsOpenActivity.fileToOpen); /* Start the thread that runs Emacs. */ thread = new EmacsThread (this, new Runnable () { @@ -228,7 +229,9 @@ public final class EmacsService extends Service (float) pixelDensityY, classPath, EmacsService.this); } - }, needDashQ); + }, needDashQ, + /* If any file needs to be opened, open it now. */ + EmacsOpenActivity.fileToOpen); thread.start (); } catch (IOException exception) diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 30484710651..d175fe332b5 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -20,24 +20,32 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import java.lang.Thread; +import java.util.Arrays; import android.os.Build; +import android.util.Log; public class EmacsThread extends Thread { + private static final String TAG = "EmacsThread"; + /* Whether or not Emacs should be started -Q. */ private boolean startDashQ; /* Runnable run to initialize Emacs. */ private Runnable paramsClosure; + /* Whether or not to open a file after starting Emacs. */ + private String fileToOpen; + public EmacsThread (EmacsService service, Runnable paramsClosure, - boolean startDashQ) + boolean startDashQ, String fileToOpen) { super ("Emacs main thread"); this.startDashQ = startDashQ; this.paramsClosure = paramsClosure; + this.fileToOpen = fileToOpen; } @Override @@ -46,14 +54,27 @@ public class EmacsThread extends Thread { String args[]; - if (!startDashQ) - args = new String[] { "libandroid-emacs.so", }; + if (fileToOpen == null) + { + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", }; + else + args = new String[] { "libandroid-emacs.so", "-Q", }; + } else - args = new String[] { "libandroid-emacs.so", "-Q", }; + { + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", + fileToOpen, }; + else + args = new String[] { "libandroid-emacs.so", "-Q", + fileToOpen, }; + } paramsClosure.run (); /* Run the native code now. */ + Log.d (TAG, "run: " + Arrays.toString (args)); EmacsNative.initEmacs (args, EmacsApplication.dumpFileName, Build.VERSION.SDK_INT); } diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el index 94fe4d6489b..fc393681ac4 100644 --- a/lisp/term/android-win.el +++ b/lisp/term/android-win.el @@ -56,10 +56,10 @@ DISPLAY is ignored on Android." (cl-defmethod frame-creation-function (params &context (window-system android)) (x-create-frame-with-faces params)) -(cl-defmethod handle-args-function (_ignored &context (window-system android)) - ;; Nothing to do here: Android has no command line to provide - ;; arguments on. - (ignore)) +(cl-defmethod handle-args-function (args &context (window-system android)) + ;; Android has no command line to provide arguments on. + ;; However, call x-handle-args to handle file name args. + (x-handle-args args)) ;;; Selection support. -- cgit v1.3 From 0eb1f4e57125117006f109a5549082008fc9fbb1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 26 May 2023 15:20:39 +0800 Subject: Allow starting Emacs --debug-init on Android * doc/emacs/android.texi (Android Troubleshooting): Document `debug-init' option. * java/AndroidManifest.xml.in (EmacsLauncherPreferencesActivity): New activity. Export on systems older than Android 7.0. * java/org/gnu/emacs/EmacsActivity.java (onCreate): Adjust for string startup argument. * java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java: New file. * java/org/gnu/emacs/EmacsPreferencesActivity.java (EmacsPreferencesActivity): Don't make final. (startEmacsQ): Give start-up argument as an argument, not as a boolean. (startEmacsDebugInit): New function. (onCreate): Register new listener; make final. * java/org/gnu/emacs/EmacsService.java (onCreate): Pass extraStartupArgument. * java/org/gnu/emacs/EmacsThread.java (EmacsThread): Rename startDashQ to extraStartupArgument. (run): Adjust accordingly. * java/res/values-v24/bool.xml: * java/res/values/bool.xml: * java/res/values/strings.xml: New files. * java/res/xml/preferences.xml: Add new option. Move string resources around. --- doc/emacs/android.texi | 14 ++++--- java/AndroidManifest.xml.in | 17 +++++++++ java/org/gnu/emacs/EmacsActivity.java | 8 ++-- .../emacs/EmacsLauncherPreferencesActivity.java | 31 ++++++++++++++++ java/org/gnu/emacs/EmacsPreferencesActivity.java | 41 +++++++++++++++++---- java/org/gnu/emacs/EmacsService.java | 7 +++- java/org/gnu/emacs/EmacsThread.java | 19 ++++++---- java/res/drawable/emacs_wrench.png | Bin 0 -> 24996 bytes java/res/values-v24/bool.xml | 22 +++++++++++ java/res/values/bool.xml | 1 + java/res/values/strings.xml | 39 ++++++++++++++++++++ java/res/xml/preferences.xml | 12 +++--- 12 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java create mode 100644 java/res/drawable/emacs_wrench.png create mode 100644 java/res/values-v24/bool.xml create mode 100644 java/res/values/strings.xml (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index d7fadd69e4b..d94b91c7ab7 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -608,16 +608,20 @@ to provide that style. @cindex troubleshooting, android @cindex emacs -Q, android +@cindex emacs --debug-init, android Since Android has no command line, there is normally no way to specify command-line arguments when starting Emacs. This is very nasty when you make a mistake in your Emacs initialization files that prevents Emacs from starting up at all, as the system normally prevents other programs from accessing Emacs's home directory. - - However, Emacs can be started with the equivalent of the -@code{--quick} option (@pxref{Initial Options}) through a special -preferences screen, which can be accessed through the Emacs ``app -info'' page in the system settings application. +@xref{Initial Options}. + + However, Emacs can be started with the equivalent of either the +option @code{--quick}, or @code{--debug-init}, through a special +preferences screen. Under Android 7.0 and later, this can be accessed +through the Emacs ``app info'' page in the system settings program; on +older systems, this is displayed as a separate icon on the desktop +labeled ``Emacs options''. Consult the manufacturer of your device for more details, as how to do this varies by device. diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index f7f834e7582..082c4c9373e 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -180,6 +180,23 @@ along with GNU Emacs. If not, see . --> + + + + + + + + + + . */ + +package org.gnu.emacs; + +/* This class only exists because EmacsPreferencesActivity is already + defined as an activity, the system wants a new class in order to + define a new activity, and only activities can be enabled or + disabled per the API level of the host. */ + +public final class EmacsLauncherPreferencesActivity + extends EmacsPreferencesActivity +{ + +} diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index 70934fa4bd4..7e67cc3679b 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -42,10 +42,11 @@ import android.preference.*; Unfortunately, there is no alternative that looks the same way. */ @SuppressWarnings ("deprecation") -public final class EmacsPreferencesActivity extends PreferenceActivity +public class EmacsPreferencesActivity extends PreferenceActivity { - /* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, and - tell the system to EmacsActivity with some parameters later. */ + /* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, + and tell the system to start EmacsActivity with some parameters + later. */ private void startEmacsQ () @@ -55,7 +56,24 @@ public final class EmacsPreferencesActivity extends PreferenceActivity intent = new Intent (this, EmacsActivity.class); intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.putExtra ("org.gnu.emacs.START_DASH_Q", true); + intent.putExtra ("org.gnu.emacs.STARTUP_ARGUMENT", "--quick"); + startActivity (intent); + System.exit (0); + } + + /* Restart Emacs with `--debug-init'. Call EmacsThread.exit to kill + Emacs now, and tell the system to EmacsActivity with some + parameters later. */ + + private void + startEmacsDebugInit () + { + Intent intent; + + intent = new Intent (this, EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra ("org.gnu.emacs.STARTUP_ARGUMENT", "--debug-init"); startActivity (intent); System.exit (0); } @@ -89,7 +107,7 @@ public final class EmacsPreferencesActivity extends PreferenceActivity } @Override - public void + public final void onCreate (Bundle savedInstanceState) { Preference tem; @@ -111,7 +129,6 @@ public final class EmacsPreferencesActivity extends PreferenceActivity items. */ tem = findPreference ("start_quick"); - listener = new Preference.OnPreferenceClickListener () { @Override public boolean @@ -123,9 +140,19 @@ public final class EmacsPreferencesActivity extends PreferenceActivity }; tem.setOnPreferenceClickListener (listener); + tem = findPreference ("start_debug_init"); + listener = new Preference.OnPreferenceClickListener () { + @Override + public boolean + onPreferenceClick (Preference preference) + { + startEmacsDebugInit (); + return true; + } + }; + tem.setOnPreferenceClickListener (listener); tem = findPreference ("erase_dump"); - listener = new Preference.OnPreferenceClickListener () { @Override public boolean diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 30ef71540a9..bb17d27bcf8 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -79,7 +79,10 @@ public final class EmacsService extends Service { public static final String TAG = "EmacsService"; public static volatile EmacsService SERVICE; - public static boolean needDashQ; + + /* If non-NULL, an extra argument to pass to + `android_emacs_init'. */ + public static String extraStartupArgument; private EmacsThread thread; private Handler handler; @@ -231,7 +234,7 @@ public final class EmacsService extends Service (float) pixelDensityY, classPath, EmacsService.this); } - }, needDashQ, + }, extraStartupArgument, /* If any file needs to be opened, open it now. */ EmacsOpenActivity.fileToOpen); thread.start (); diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index d175fe332b5..468c6530af0 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -29,8 +29,9 @@ public class EmacsThread extends Thread { private static final String TAG = "EmacsThread"; - /* Whether or not Emacs should be started -Q. */ - private boolean startDashQ; + /* Whether or not Emacs should be started with an additional + argument, and that additional argument if non-NULL. */ + private String extraStartupArgument; /* Runnable run to initialize Emacs. */ private Runnable paramsClosure; @@ -40,10 +41,10 @@ public class EmacsThread extends Thread public EmacsThread (EmacsService service, Runnable paramsClosure, - boolean startDashQ, String fileToOpen) + String extraStartupArgument, String fileToOpen) { super ("Emacs main thread"); - this.startDashQ = startDashQ; + this.extraStartupArgument = extraStartupArgument; this.paramsClosure = paramsClosure; this.fileToOpen = fileToOpen; } @@ -56,18 +57,20 @@ public class EmacsThread extends Thread if (fileToOpen == null) { - if (!startDashQ) + if (extraStartupArgument == null) args = new String[] { "libandroid-emacs.so", }; else - args = new String[] { "libandroid-emacs.so", "-Q", }; + args = new String[] { "libandroid-emacs.so", + extraStartupArgument, }; } else { - if (!startDashQ) + if (extraStartupArgument != null) args = new String[] { "libandroid-emacs.so", fileToOpen, }; else - args = new String[] { "libandroid-emacs.so", "-Q", + args = new String[] { "libandroid-emacs.so", + extraStartupArgument, fileToOpen, }; } diff --git a/java/res/drawable/emacs_wrench.png b/java/res/drawable/emacs_wrench.png new file mode 100644 index 00000000000..50572d3bed1 Binary files /dev/null and b/java/res/drawable/emacs_wrench.png differ diff --git a/java/res/values-v24/bool.xml b/java/res/values-v24/bool.xml new file mode 100644 index 00000000000..37f07992995 --- /dev/null +++ b/java/res/values-v24/bool.xml @@ -0,0 +1,22 @@ + + + + false + diff --git a/java/res/values/bool.xml b/java/res/values/bool.xml index d37eab745c0..2b253824e29 100644 --- a/java/res/values/bool.xml +++ b/java/res/values/bool.xml @@ -19,4 +19,5 @@ along with GNU Emacs. If not, see . --> false + true diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml new file mode 100644 index 00000000000..36a47be6c84 --- /dev/null +++ b/java/res/values/strings.xml @@ -0,0 +1,39 @@ + + + + + Restart Emacs with -Q + + + Restart Emacs, but do not load site lisp or init files. + + + Restart Emacs with --debug-init + + + Restart Emacs, and display the debugger should an error occur while loading initialization files. + + + Delete dump file + + + Remove the dumped state created when Emacs was installed. + + diff --git a/java/res/xml/preferences.xml b/java/res/xml/preferences.xml index f0c3abb52e7..d52d28816e5 100644 --- a/java/res/xml/preferences.xml +++ b/java/res/xml/preferences.xml @@ -19,10 +19,12 @@ along with GNU Emacs. If not, see . --> - + android:title="@string/start_quick_title" + android:summary="@string/start_quick_caption"/> + + android:title="@string/erase_dump_title" + android:summary="@string/erase_dump_caption"/> -- cgit v1.3 From 327d2d013130ec745880b44573dcda3a6620faba Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 27 May 2023 16:30:12 +0800 Subject: Add extra thread-related checking * java/org/gnu/emacs/EmacsService.java (EmacsService) (checkEmacsThread): New function. (fillPolygon, drawRectangle, drawLine, drawPoint, copyArea) (clearArea): * java/org/gnu/emacs/EmacsThread.java (EmacsThread): * java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers): Call where appropriate. --- java/org/gnu/emacs/EmacsService.java | 30 ++++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsThread.java | 2 +- java/org/gnu/emacs/EmacsView.java | 5 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index bb17d27bcf8..d2e52ed5e62 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -100,6 +100,10 @@ public final class EmacsService extends Service information. */ public static final boolean DEBUG_IC = false; + /* Flag that says whether or not to perform extra checks on threads + performing drawing calls. */ + private static final boolean DEBUG_THREADS = false; + /* Return the directory leading to the directory in which native library files are stored on behalf of CONTEXT. */ @@ -309,10 +313,29 @@ public final class EmacsService extends Service syncRunnable (runnable); } + + + public static void + checkEmacsThread () + { + if (DEBUG_THREADS) + { + if (Thread.currentThread () instanceof EmacsThread) + return; + + throw new RuntimeException ("Emacs thread function" + + " called from other thread!"); + } + } + + /* These drawing functions must only be called from the Emacs + thread. */ + public void fillRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { + checkEmacsThread (); EmacsFillRectangle.perform (drawable, gc, x, y, width, height); } @@ -321,6 +344,7 @@ public final class EmacsService extends Service fillPolygon (EmacsDrawable drawable, EmacsGC gc, Point points[]) { + checkEmacsThread (); EmacsFillPolygon.perform (drawable, gc, points); } @@ -328,6 +352,7 @@ public final class EmacsService extends Service drawRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { + checkEmacsThread (); EmacsDrawRectangle.perform (drawable, gc, x, y, width, height); } @@ -336,6 +361,7 @@ public final class EmacsService extends Service drawLine (EmacsDrawable drawable, EmacsGC gc, int x, int y, int x2, int y2) { + checkEmacsThread (); EmacsDrawLine.perform (drawable, gc, x, y, x2, y2); } @@ -344,6 +370,7 @@ public final class EmacsService extends Service drawPoint (EmacsDrawable drawable, EmacsGC gc, int x, int y) { + checkEmacsThread (); EmacsDrawPoint.perform (drawable, gc, x, y); } @@ -353,6 +380,7 @@ public final class EmacsService extends Service int srcX, int srcY, int width, int height, int destX, int destY) { + checkEmacsThread (); EmacsCopyArea.perform (srcDrawable, gc, dstDrawable, srcX, srcY, width, height, destX, destY); @@ -361,6 +389,7 @@ public final class EmacsService extends Service public void clearWindow (EmacsWindow window) { + checkEmacsThread (); window.clearWindow (); } @@ -368,6 +397,7 @@ public final class EmacsService extends Service clearArea (EmacsWindow window, int x, int y, int width, int height) { + checkEmacsThread (); window.clearArea (x, y, width, height); } diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 468c6530af0..1343b70bf5a 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -25,7 +25,7 @@ import java.util.Arrays; import android.os.Build; import android.util.Log; -public class EmacsThread extends Thread +public final class EmacsThread extends Thread { private static final String TAG = "EmacsThread"; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index eb1d88ae242..09bc9d719d3 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -338,6 +338,7 @@ public final class EmacsView extends ViewGroup public void damageRect (Rect damageRect) { + EmacsService.checkEmacsThread (); damageRegion.union (damageRect); } @@ -351,6 +352,10 @@ public final class EmacsView extends ViewGroup Rect damageRect; Bitmap bitmap; + /* Make sure this function is called only from the Emacs + thread. */ + EmacsService.checkEmacsThread (); + damageRect = null; /* Now see if there is a damage region. */ -- cgit v1.3 From 7f073df53374bc1b96ac07e949af11b8af06b94b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 9 Jun 2023 09:27:04 +0800 Subject: Fix crash starting Emacs to open file * java/org/gnu/emacs/EmacsThread.java (run): Correct check against extraStartupArgument when an initial file is specified. --- java/org/gnu/emacs/EmacsThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 1343b70bf5a..c003ea95c50 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -65,7 +65,7 @@ public final class EmacsThread extends Thread } else { - if (extraStartupArgument != null) + if (extraStartupArgument == null) args = new String[] { "libandroid-emacs.so", fileToOpen, }; else -- cgit v1.3 From 9cf166db63b7383a94dc47a6a5251c0dbe1dae9b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 31 Jul 2023 14:18:12 +0800 Subject: Initialize Android API level earlier * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsNoninteractive.java (main): * java/org/gnu/emacs/EmacsService.java (run): * java/org/gnu/emacs/EmacsThread.java (run): * src/android.c (initEmacs, setEmacsParams): Set `android_api_level' within setEmacsParams, not in initEmacs. * src/androidvfs.c: Pacify compiler warnings. --- java/org/gnu/emacs/EmacsNative.java | 14 +++++++------- java/org/gnu/emacs/EmacsNoninteractive.java | 6 +++--- java/org/gnu/emacs/EmacsService.java | 3 ++- java/org/gnu/emacs/EmacsThread.java | 4 +--- src/android.c | 12 +++++++----- src/androidvfs.c | 14 ++++++++++++++ 6 files changed, 34 insertions(+), 19 deletions(-) (limited to 'java/org/gnu/emacs/EmacsThread.java') diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index ea200037218..7d72a9f192e 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -66,7 +66,9 @@ public final class EmacsNative classPath must be the classpath of this app_process process, or NULL. - emacsService must be the EmacsService singleton, or NULL. */ + emacsService must be the EmacsService singleton, or NULL. + + apiLevel is the version of Android being run. */ public static native void setEmacsParams (AssetManager assetManager, String filesDir, String libDir, @@ -75,18 +77,16 @@ public final class EmacsNative float pixelDensityY, float scaledDensity, String classPath, - EmacsService emacsService); + EmacsService emacsService, + int apiLevel); /* Initialize Emacs with the argument array ARGV. Each argument must contain a NULL terminated string, or else the behavior is undefined. DUMPFILE is the dump file to use, or NULL if Emacs is to load - loadup.el itself. - - APILEVEL is the version of Android being used. */ - public static native void initEmacs (String argv[], String dumpFile, - int apiLevel); + loadup.el itself. */ + public static native void initEmacs (String argv[], String dumpFile); /* Abort and generate a native core dump. */ public static native void emacsAbort (); diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index aa6fa41ba97..1c7513e1cc9 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -190,14 +190,14 @@ public final class EmacsNoninteractive EmacsNative.setEmacsParams (assets, filesDir, libDir, cacheDir, 0.0f, - 0.0f, 0.0f, null, null); + 0.0f, 0.0f, null, null, + Build.VERSION.SDK_INT); /* Now find the dump file that Emacs should use, if it has already been dumped. */ EmacsApplication.findDumpFile (context); /* Start Emacs. */ - EmacsNative.initEmacs (args, EmacsApplication.dumpFileName, - Build.VERSION.SDK_INT); + EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index e714f75fdf2..3c1bb0855f4 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -291,7 +291,8 @@ public final class EmacsService extends Service cacheDir, (float) pixelDensityX, (float) pixelDensityY, (float) scaledDensity, - classPath, EmacsService.this); + classPath, EmacsService.this, + Build.VERSION.SDK_INT); } }, extraStartupArgument, /* If any file needs to be opened, open it now. */ diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index c003ea95c50..5307015b46f 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -22,7 +22,6 @@ package org.gnu.emacs; import java.lang.Thread; import java.util.Arrays; -import android.os.Build; import android.util.Log; public final class EmacsThread extends Thread @@ -78,7 +77,6 @@ public final class EmacsThread extends Thread /* Run the native code now. */ Log.d (TAG, "run: " + Arrays.toString (args)); - EmacsNative.initEmacs (args, EmacsApplication.dumpFileName, - Build.VERSION.SDK_INT); + EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); } }; diff --git a/src/android.c b/src/android.c index 8c0232a51f8..f60ff5acb54 100644 --- a/src/android.c +++ b/src/android.c @@ -1281,7 +1281,8 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, jfloat pixel_density_y, jfloat scaled_density, jobject class_path, - jobject emacs_service_object) + jobject emacs_service_object, + jint api_level) { JNI_STACK_ALIGNMENT_PROLOGUE; @@ -1289,6 +1290,10 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, pthread_t thread; const char *java_string; + /* Set the Android API level early, as it is used by + `android_vfs_init'. */ + android_api_level = api_level; + /* This function should only be called from the main thread. */ android_pixel_density_x = pixel_density_x; @@ -1771,7 +1776,7 @@ android_init_emacs_cursor (void) JNIEXPORT void JNICALL NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, - jobject dump_file_object, jint api_level) + jobject dump_file_object) { /* android_emacs_init is not main, so GCC is not nice enough to add the stack alignment prologue. @@ -1788,9 +1793,6 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, const char *c_argument; char *dump_file; - /* Set the Android API level. */ - android_api_level = api_level; - android_java_env = env; nelements = (*env)->GetArrayLength (env, argv); diff --git a/src/androidvfs.c b/src/androidvfs.c index 9acc8f2b139..eeef5ea5db0 100644 --- a/src/androidvfs.c +++ b/src/androidvfs.c @@ -5976,6 +5976,14 @@ android_saf_new_opendir (struct android_vnode *vnode) /* Semaphore posted upon the completion of an SAF operation. */ static sem_t saf_completion_sem; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#else /* GNUC */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif /* __clang__ */ + JNIEXPORT jint JNICALL NATIVE_NAME (safSyncAndReadInput) (JNIEnv *env, jobject object) { @@ -6010,6 +6018,12 @@ NATIVE_NAME (safPostRequest) (JNIEnv *env, jobject object) sem_post (&saf_completion_sem); } +#ifdef __clang__ +#pragma clang diagnostic pop +#else /* GNUC */ +#pragma GCC diagnostic pop +#endif /* __clang__ */ + /* Root vnode. This vnode represents the root inode, and is a regular -- cgit v1.3