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/AndroidManifest.xml | 54 ++++ java/Makefile.in | 150 +++++++++ java/README | 6 + java/debug.sh | 242 +++++++++++++++ java/emacs.keystore | Bin 0 -> 2776 bytes java/org/gnu/emacs/EmacsActivity.java | 146 +++++++++ java/org/gnu/emacs/EmacsApplication.java | 27 ++ java/org/gnu/emacs/EmacsCopyArea.java | 131 ++++++++ java/org/gnu/emacs/EmacsDrawLine.java | 137 +++++++++ java/org/gnu/emacs/EmacsDrawPoint.java | 30 ++ java/org/gnu/emacs/EmacsDrawRectangle.java | 127 ++++++++ java/org/gnu/emacs/EmacsDrawable.java | 33 ++ java/org/gnu/emacs/EmacsFillPolygon.java | 150 +++++++++ java/org/gnu/emacs/EmacsFillRectangle.java | 129 ++++++++ java/org/gnu/emacs/EmacsFontDriver.java | 150 +++++++++ java/org/gnu/emacs/EmacsGC.java | 116 +++++++ java/org/gnu/emacs/EmacsHandleObject.java | 62 ++++ java/org/gnu/emacs/EmacsNative.java | 72 +++++ java/org/gnu/emacs/EmacsPaintQueue.java | 138 +++++++++ java/org/gnu/emacs/EmacsPaintReq.java | 33 ++ java/org/gnu/emacs/EmacsPixmap.java | 102 ++++++ java/org/gnu/emacs/EmacsSdk7FontDriver.java | 460 ++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsService.java | 384 +++++++++++++++++++++++ java/org/gnu/emacs/EmacsSurfaceView.java | 83 +++++ java/org/gnu/emacs/EmacsThread.java | 44 +++ java/org/gnu/emacs/EmacsView.java | 211 +++++++++++++ java/org/gnu/emacs/EmacsWindow.java | 336 ++++++++++++++++++++ 27 files changed, 3553 insertions(+) create mode 100644 java/AndroidManifest.xml create mode 100644 java/Makefile.in create mode 100644 java/README create mode 100755 java/debug.sh create mode 100644 java/emacs.keystore create mode 100644 java/org/gnu/emacs/EmacsActivity.java create mode 100644 java/org/gnu/emacs/EmacsApplication.java create mode 100644 java/org/gnu/emacs/EmacsCopyArea.java create mode 100644 java/org/gnu/emacs/EmacsDrawLine.java create mode 100644 java/org/gnu/emacs/EmacsDrawPoint.java create mode 100644 java/org/gnu/emacs/EmacsDrawRectangle.java create mode 100644 java/org/gnu/emacs/EmacsDrawable.java create mode 100644 java/org/gnu/emacs/EmacsFillPolygon.java create mode 100644 java/org/gnu/emacs/EmacsFillRectangle.java create mode 100644 java/org/gnu/emacs/EmacsFontDriver.java create mode 100644 java/org/gnu/emacs/EmacsGC.java create mode 100644 java/org/gnu/emacs/EmacsHandleObject.java create mode 100644 java/org/gnu/emacs/EmacsNative.java create mode 100644 java/org/gnu/emacs/EmacsPaintQueue.java create mode 100644 java/org/gnu/emacs/EmacsPaintReq.java create mode 100644 java/org/gnu/emacs/EmacsPixmap.java create mode 100644 java/org/gnu/emacs/EmacsSdk7FontDriver.java create mode 100644 java/org/gnu/emacs/EmacsService.java create mode 100644 java/org/gnu/emacs/EmacsSurfaceView.java create mode 100644 java/org/gnu/emacs/EmacsThread.java create mode 100644 java/org/gnu/emacs/EmacsView.java create mode 100644 java/org/gnu/emacs/EmacsWindow.java (limited to 'java') diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml new file mode 100644 index 00000000000..75aa5bdf409 --- /dev/null +++ b/java/AndroidManifest.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/Makefile.in b/java/Makefile.in new file mode 100644 index 00000000000..e9fcc625cb2 --- /dev/null +++ b/java/Makefile.in @@ -0,0 +1,150 @@ +### @configure_input@ + +# 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 . + +top_builddir = @top_builddir@ + +-include ${top_builddir}/src/verbose.mk + +SHELL = @SHELL@ +JAVAC = @JAVAC@ +AAPT = @AAPT@ +DX = @DX@ +ZIPALIGN = @ZIPALIGN@ +JARSIGNER = @JARSIGNER@ +ANDROID_JAR = @ANDROID_JAR@ +ANDROID_ABI = @ANDROID_ABI@ + +WARN_JAVAFLAGS = -Xlint:deprecation +JAVAFLAGS = -classpath "$(ANDROID_JAR):." -target 1.7 -source 1.7 \ + $(WARN_JAVAFLAGS) + +SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 + +JAVA_FILES = $(shell find . -type f -name *.java) +CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) + +# How this stuff works. + +# emacs.apk depends on emacs.apk-in, which is simply a ZIP archive +# containing the following files: +# lib/$(ANDROID_ABI)/libemacs.so +# lib/$(ANDROID_ABI)/libandroid-emacs.so +# lib/$(ANDROID_ABI)/libctags.so +# lib/$(ANDROID_ABI)/libhexl.so +# lib/$(ANDROID_ABI)/libmovemail.so +# lib/$(ANDROID_ABI)/librcs2log.so +# lib/$(ANDROID_ABI)/libebrowse.so +# assets/info/ +# assets/etc/ +# assets/lisp/ + +.PHONY: emacs.apk-in all +all: emacs.apk + +# Binaries to cross-compile. +CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \ + ../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ + ../xcompile/lib-src/ebrowse + +# Libraries to cross-compile. +CROSS_LIBS = ../xcompile/src/libemacs.so + +.PHONY: $(CROSS_BINS) $(CROSS_LIBS) + +../xcompile/src/android-emacs ../xcompile/src/libemacs.so: + make -C ../xcompile src/$(notdir $@) + +../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ +../xcompile/lib-src/ctags ../xcompile/lib-src/ebrowse &: + make -C ../xcompile lib-src/$(notdir $@) + +emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml +# Make the working directory for this stuff + rm -rf install_temp + mkdir -p install_temp/lib/$(ANDROID_ABI) + mkdir -p install_temp/assets/etc + mkdir -p install_temp/assets/lisp + mkdir -p install_temp/assets/info +# Install architecture independents to assets/etc and assets/lisp + cp -r $(top_builddir)/lisp install_temp/assets + cp -r $(top_builddir)/etc install_temp/assets +# Remove undesirable files from those directories. + for subdir in `find install_temp -type d -print`; do \ + chmod a+rx $${subdir} ; \ + rm -rf $${subdir}/.gitignore ; \ + rm -rf $${subdir}/.DS_Store ; \ + rm -rf $${subdir}/#* ; \ + rm -rf $${subdir}/.#* ; \ + rm -rf $${subdir}/*~ ; \ + rm -rf $${subdir}/*.orig ; \ + rm -rf $${subdir}/ChangeLog* ; \ + rm -rf $${subdir}/[mM]akefile*[.-]in ; \ + rm -rf $${subdir}/Makefile; \ + done +# Install architecture dependents to lib/$(ANDROID_ABI). This +# perculiar naming scheme is required to make Android preserve these +# binaries upon installation. + for file in $(CROSS_BINS); do \ + if [ -x $$file ]; then \ + filename=`basename $$file`; \ + cp -f $$file install_temp/lib/$(ANDROID_ABI)/lib$${filename}.so; \ + fi \ + done + for file in $(CROSS_LIBS); do \ + if [ -x $$file ]; then \ + cp -f $$file install_temp/lib/$(ANDROID_ABI); \ + fi \ + done +# Package everything. + $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml + pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd + pushd install_temp; $(AAPT) add ../$@ `find assets -type f`; popd + rm -rf install_temp + +.SUFFIXES: .java .class +.java.class &: + $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< + +# N.B. that find must be called all over again in case javac generated +# nested classes. + +classes.dex: $(CLASS_FILES) + $(AM_V_DX) $(DX) --classpath $(ANDROID_JAR) \ + $(subst $$,\$$,$(shell find . -type f -name *.class)) + +# When emacs.keystore expires, regenerate it with: +# +# keytool -genkey -v -keystore emacs.keystore -alias "Emacs keystore" \ +# -keyalg RSA -sigalg SHA1withRSA -keysize 2048 -validity 100000 + +.PHONY: clean maintainer-clean + +emacs.apk: classes.dex emacs.apk-in emacs.keystore + cp -f emacs.apk-in $@.unaligned + $(AAPT) add $@.unaligned classes.dex + $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore" + $(ZIPALIGN) -f 4 $@.unaligned $@ + rm -f $@.unaligned + +clean: + rm -f emacs.apk emacs.apk-in *.dex *.unaligned *.class + rm -rf install-temp + find . -name '*.class' -delete + +maintainer-clean: clean diff --git a/java/README b/java/README new file mode 100644 index 00000000000..50c2332ce95 --- /dev/null +++ b/java/README @@ -0,0 +1,6 @@ +This directory holds the Java sources of the port of GNU Emacs to +Android-like systems. + +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. diff --git a/java/debug.sh b/java/debug.sh new file mode 100755 index 00000000000..dd710dc31af --- /dev/null +++ b/java/debug.sh @@ -0,0 +1,242 @@ +#!/bin/bash +### Run Emacs under GDB or JDB on Android. + +## 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 . + +set -m +oldpwd=`pwd` +cd `dirname $0` + +devices=`adb devices | grep device | awk -- '/device\y/ { print $1 }' -` +device= +progname=$0 +package=org.gnu.emacs +activity=org.gnu.emacs.EmacsActivity +gdb_port=5039 +jdb_port=64013 +jdb=no + +while [ $# -gt 0 ]; do + case "$1" in + ## This option specifies the serial number of a device to use. + "--device" ) + device="$2" + if [ -z device ]; then + echo "You must specify an argument to --device" + exit 1 + fi + ;; + "--help" ) + echo "Usage: $progname [options] -- [gdb options]" + echo "" + echo " --device DEVICE run Emacs on the specified device" + 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 " --help print this message" + echo "" + echo "Available devices:" + for device in $devices; do + echo " " $device + done + echo "" + exit 0 + ;; + "--jdb" ) + jdb=yes + ;; + "--port" ) + gdb_port=$1 + ;; + "--" ) + shift + gdbargs=$@ + break; + ;; + * ) + echo "$progname: Unrecognized argument $1" + exit 1 + ;; + esac + shift +done + +if [ -z $devices ]; then + echo "No devices are available." + exit 1 +fi + +if [ -z $device ]; then + device=$devices +fi + +if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z device ]; then + echo "Multiple devices are available. Please pick one using" + echo "--device and try again." +fi + +echo "Looking for $package on device $device" + +# Find the application data directory +app_data_dir=`adb -s $device shell run-as $package sh -c 'pwd 2> /dev/null'` + +if [ -z $app_data_dir ]; then + echo "The data directory for the package $package was not found." + echo "Is it installed?" +fi + +echo "Found application data directory at $app_data_dir..." + +# Find which PIDs are associated with org.gnu.emacs +package_uid=`adb -s $device shell run-as $package id -u` + +if [ -z $package_uid ]; then + echo "Failed to obtain UID of packages named $package" + exit 1 +fi + +# First, run ps -u $package_uid -o PID,CMD to fetch the list of +# process IDs. +package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` + +# Next, remove lines matching "ps" itself. +package_pids=`awk -- '{ + if (!match ($0, /(PID|ps)/)) + print $1 +}' <<< $package_pids` + +# Finally, kill each existing process. +for pid in $package_pids; do + echo "Killing existing process $pid..." + adb -s $device shell run-as $package kill -9 $pid &> /dev/null +done + +# Now run the main activity. This must be done as the adb user and +# not as the package user. +echo "Starting activity $activity and attaching debugger" + +# Exit if the activity could not be started. +adb -s $device shell am start -D "$package/$activity" +if [ ! $? ]; then + exit 1; +fi + +# Now look for processes matching the package again. +package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` + +# Next, remove lines matching "ps" itself. +package_pids=`awk -- '{ + if (!match ($0, /(PID|ps)/)) + print $1 +}' <<< $package_pids` + +pid=$package_pids +num_pids=`wc -w <<< "$package_pids"` + +if [ $num_pids -gt 1 ]; then + echo "More than one process was started:" + echo "" + adb -s $device shell run-as $package ps -u $package_uid | awk -- '{ + if (!match ($0, /ps/)) + print $0 + }' + echo "" + printf "Which one do you want to attach to? " + read pid +elif [ -z $package_pids ]; then + echo "No processes were found to attach to." + exit 1 +fi + +# Start JDB to make the wait dialog disappear. +echo "Attaching JDB to unblock the application." +adb -s $device forward --remove-all +adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" + +if [ ! $? ]; then + echo "Failed to forward jdwp:$pid to $jdb_port!" + echo "Perhaps you need to specify a different port with --port?" + exit 1; +fi + +jdb_command="jdb -connect \ + com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port" + +if [ $jdb = "yes" ]; then + # Just start JDB and then exit + $jdb_command + exit 1 +fi + +exec 4<> /tmp/file-descriptor-stamp + +# Now run JDB with IO redirected to file descriptor 4 in a subprocess. +$jdb_command <&4 >&4 & + +character= +# Next, wait until the prompt is found. +while read -n1 -u 4 character; do + if [ "$character" = ">" ]; then + echo "JDB attached successfully" + break; + fi +done + +# Now start gdbserver on the device asynchronously. + +echo "Attaching gdbserver to $pid on $device..." +exec 5<> /tmp/file-descriptor-stamp +adb -s $device shell run-as $package /system/bin/gdbserver --once \ + "+debug.$package_uid.socket" --attach $pid >&5 & + +# Wait until gdbserver successfully runs. +line= +while read -u 5 line; do + case "$line" in + *Attached* ) + break; + ;; + *error* | *Error* | failed ) + echo $line + exit 1 + ;; + * ) + ;; + esac +done + +# Send EOF to JDB to make it go away. This will also cause Android to +# allow Emacs to continue executing. +echo "Making JDB go away..." +echo "exit" >&4 +read -u 4 line +echo "JDB has gone away with $line" + +# Forward the gdb server port here. +adb -s $device forward "tcp:$gdb_port" \ + "localfilesystem:$app_data_dir/debug.$package_uid.socket" +if [ ! $? ]; then + echo "Failed to forward $app_data_dir/debug.$package_uid.socket" + echo "to $gdb_port! Perhaps you need to specify a different port" + echo "with --port?" + exit 1; +fi + +# Finally, start gdb with any extra arguments needed. +cd "$oldpwd" +gdb --eval-command "" --eval-command "target remote localhost:$gdb_port" $gdbargs diff --git a/java/emacs.keystore b/java/emacs.keystore new file mode 100644 index 00000000000..76d80b6db65 Binary files /dev/null and b/java/emacs.keystore differ diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java new file mode 100644 index 00000000000..cacd5f13e60 --- /dev/null +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -0,0 +1,146 @@ +/* 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.IllegalStateException; +import java.util.List; +import java.util.ArrayList; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; + +public class EmacsActivity extends Activity +{ + public static final String TAG = "EmacsActivity"; + + /* List of all activities that do not have an associated + EmacsWindow. */ + public static List availableActivities; + + /* The currently attached EmacsWindow, or null if none. */ + private EmacsWindow window; + + /* The frame layout associated with the activity. */ + private FrameLayout layout; + + static + { + /* Set up the list of available activities. */ + availableActivities = new ArrayList (); + }; + + public void + attachChild (EmacsWindow child) + { + if (window != null) + throw new IllegalStateException ("trying to attach window when one" + + " already exists"); + + /* Record and attach the view. */ + window = child; + layout.addView (window.view); + + /* Remove the objects from the lists of what is available. */ + EmacsService.availableChildren.remove (child); + availableActivities.remove (this); + + /* Now set child->activity. */ + child.setActivity (this); + } + + /* Make this activity available for future windows to attach + again. */ + + public void + makeAvailable () + { + window = null; + + for (EmacsWindow iterWindow + : EmacsService.availableChildren) + { + synchronized (iterWindow) + { + if (!iterWindow.isDestroyed ()) + attachChild (iterWindow); + + return; + } + } + + availableActivities.add (this); + } + + @Override + public void + onCreate (Bundle savedInstanceState) + { + FrameLayout.LayoutParams params; + + 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); + + /* Make the activity available before starting the + service. */ + makeAvailable (); + + if (EmacsService.SERVICE == null) + /* Start the Emacs service now. */ + startService (new Intent (this, EmacsService.class)); + + super.onCreate (savedInstanceState); + } + + @Override + public void + onStop () + { + /* The activity is no longer visible. If there is a window + attached, detach it. */ + + if (window != null) + { + layout.removeView (window.view); + + /* Notice that the window is already available too. But do + not call noticeAvailableChild; that might assign it to some + other activity, which behaves badly. */ + EmacsService.availableChildren.add (window); + window = null; + } + + /* Finally, remove this activity from the list of available + activities. */ + availableActivities.remove (this); + super.onStop (); + } +}; diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java new file mode 100644 index 00000000000..125da05cfd4 --- /dev/null +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -0,0 +1,27 @@ +/* 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.app.Application; + +public class EmacsApplication extends Application +{ + /* This class currently does nothing. */ +}; diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java new file mode 100644 index 00000000000..f34d1ecde01 --- /dev/null +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -0,0 +1,131 @@ +/* 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.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Xfermode; + +public class EmacsCopyArea implements EmacsPaintReq +{ + private int src_x, src_y, dest_x, dest_y, width, height; + private EmacsDrawable destination, source; + private EmacsGC immutableGC; + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsCopyArea (EmacsDrawable destination, EmacsDrawable source, + int src_x, int src_y, int width, int height, + int dest_x, int dest_y, EmacsGC immutableGC) + { + this.destination = destination; + this.source = source; + this.src_x = src_x; + this.src_y = src_y; + this.width = width; + this.height = height; + this.dest_x = dest_x; + this.dest_y = dest_y; + this.immutableGC = immutableGC; + } + + @Override + public Rect + getRect () + { + return new Rect (dest_x, dest_y, dest_x + width, + dest_y + height); + } + + @Override + public EmacsDrawable + getDrawable () + { + return destination; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int alu; + Bitmap bitmap; + Paint maskPaint; + Canvas maskCanvas; + Bitmap maskBitmap; + Rect rect, srcRect; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + bitmap = source.getBitmap (); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + if (immutableGC.clip_mask == null) + canvas.drawBitmap (bitmap, new Rect (src_x, src_y, + src_x + width, + src_y + height), + rect, paint); + else + { + maskPaint = new Paint (); + srcRect = new Rect (0, 0, rect.width (), + rect.height ()); + maskBitmap + = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, + true); + + if (maskBitmap == null) + return; + + maskPaint.setXfermode (srcInAlu); + maskCanvas = new Canvas (maskBitmap); + maskCanvas.drawBitmap (bitmap, new Rect (src_x, src_y, + src_x + width, + src_y + height), + srcRect, maskPaint); + canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + } + } +} diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java new file mode 100644 index 00000000000..6389031bbfa --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -0,0 +1,137 @@ +/* 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.Math; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Xfermode; + +public class EmacsDrawLine implements EmacsPaintReq +{ + private int x, y, x2, y2; + private EmacsDrawable drawable; + private EmacsGC immutableGC; + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsDrawLine (EmacsDrawable drawable, int x, int y, + int x2, int y2, EmacsGC immutableGC) + { + this.drawable = drawable; + this.x = x; + this.y = y; + this.x2 = x2; + this.y2 = y2; + this.immutableGC = immutableGC; + } + + @Override + public Rect + getRect () + { + return new Rect (Math.min (x, x2 + 1), + Math.min (y, y2 + 1), + Math.max (x2 + 1, x), + Math.max (y2 + 1, y)); + } + + @Override + public EmacsDrawable + getDrawable () + { + return drawable; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int alu; + Paint maskPaint; + Canvas maskCanvas; + Bitmap maskBitmap; + Rect rect, srcRect; + int width, height; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + width = rect.width (); + height = rect.height (); + + paint.setStyle (Paint.Style.STROKE); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + if (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawLine ((float) x, (float) y, + (float) x2, (float) y2, + paint); + } + else + { + maskPaint = new Paint (); + maskBitmap + = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, + true); + + if (maskBitmap == null) + return; + + maskPaint.setXfermode (srcInAlu); + maskPaint.setColor (immutableGC.foreground | 0xff000000); + maskCanvas = new Canvas (maskBitmap); + srcRect = new Rect (0, 0, maskBitmap.getWidth (), + maskBitmap.getHeight ()); + maskCanvas.drawLine (0.0f, 0.0f, (float) Math.abs (x - x2), + (float) Math.abs (y - y2), maskPaint); + canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + } + + paint.setXfermode (null); + } +} diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java b/java/org/gnu/emacs/EmacsDrawPoint.java new file mode 100644 index 00000000000..772757ff424 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawPoint.java @@ -0,0 +1,30 @@ +/* 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; + +public class EmacsDrawPoint extends EmacsDrawRectangle +{ + public + EmacsDrawPoint (EmacsDrawable drawable, int x, int y, + EmacsGC immutableGC) + { + super (drawable, x, y, 1, 1, immutableGC); + } +} diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java new file mode 100644 index 00000000000..462bf7c85b5 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -0,0 +1,127 @@ +/* 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.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Xfermode; + +public class EmacsDrawRectangle implements EmacsPaintReq +{ + private int x, y, width, height; + private EmacsDrawable drawable; + private EmacsGC immutableGC; + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsDrawRectangle (EmacsDrawable drawable, int x, int y, + int width, int height, + EmacsGC immutableGC) + { + this.drawable = drawable; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.immutableGC = immutableGC; + } + + @Override + public Rect + getRect () + { + return new Rect (x, y, x + width, y + height); + } + + @Override + public EmacsDrawable + getDrawable () + { + return drawable; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int alu; + Paint maskPaint; + Canvas maskCanvas; + Bitmap maskBitmap; + Rect rect, srcRect; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + + paint.setStyle (Paint.Style.STROKE); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + if (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawRect (rect, paint); + } + else + { + maskPaint = new Paint (); + maskBitmap + = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, + true); + + if (maskBitmap == null) + return; + + maskPaint.setXfermode (srcInAlu); + maskPaint.setColor (immutableGC.foreground | 0xff000000); + maskCanvas = new Canvas (maskBitmap); + srcRect = new Rect (0, 0, maskBitmap.getWidth (), + maskBitmap.getHeight ()); + maskCanvas.drawRect (srcRect, maskPaint); + canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + } + + paint.setXfermode (null); + } +} diff --git a/java/org/gnu/emacs/EmacsDrawable.java b/java/org/gnu/emacs/EmacsDrawable.java new file mode 100644 index 00000000000..19062137213 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawable.java @@ -0,0 +1,33 @@ +/* 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.graphics.Rect; +import android.graphics.Bitmap; +import android.graphics.Canvas; + +public interface EmacsDrawable +{ + public Canvas lockCanvas (); + public void unlockCanvas (); + public void damageRect (Rect damageRect); + public Bitmap getBitmap (); + public boolean isDestroyed (); +}; diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java new file mode 100644 index 00000000000..3198c7f07c4 --- /dev/null +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -0,0 +1,150 @@ +/* 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.Math; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Xfermode; + +public class EmacsFillPolygon implements EmacsPaintReq +{ + private EmacsDrawable drawable; + private EmacsGC immutableGC; + private Path path; + + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsFillPolygon (EmacsDrawable drawable, Point points[], + EmacsGC immutableGC) + { + int i; + + this.drawable = drawable; + this.immutableGC = immutableGC; + + /* Build the path from the given array of points. */ + path = new Path (); + + if (points.length >= 1) + { + path.moveTo (points[0].x, points[0].y); + + for (i = 1; i < points.length; ++i) + path.lineTo (points[i].x, points[i].y); + + path.close (); + } + } + + @Override + public Rect + getRect () + { + RectF rect; + + rect = new RectF (0, 0, 0, 0); + path.computeBounds (rect, true); + + return new Rect ((int) Math.floor (rect.left), + (int) Math.floor (rect.top), + (int) Math.ceil (rect.right), + (int) Math.ceil (rect.bottom)); + } + + @Override + public EmacsDrawable + getDrawable () + { + return drawable; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int alu; + Paint maskPaint; + Canvas maskCanvas; + Bitmap maskBitmap; + Rect rect; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + paint.setStyle (Paint.Style.FILL); + + if (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawPath (path, paint); + } + else + { + maskPaint = new Paint (); + maskBitmap = immutableGC.clip_mask.bitmap; + maskBitmap = maskBitmap.copy (Bitmap.Config.ARGB_8888, + true); + + if (maskBitmap == null) + return; + + maskPaint.setXfermode (srcInAlu); + maskPaint.setColor (immutableGC.foreground | 0xff000000); + maskCanvas = new Canvas (maskBitmap); + path.offset (-rect.left, -rect.top, null); + maskCanvas.drawPath (path, maskPaint); + canvas.drawBitmap (maskBitmap, new Rect (0, 0, rect.width (), + rect.height ()), + rect, paint); + } + } +} diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java new file mode 100644 index 00000000000..7246c13de7f --- /dev/null +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -0,0 +1,129 @@ +/* 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.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Xfermode; + +import android.util.Log; + +public class EmacsFillRectangle implements EmacsPaintReq +{ + private int x, y, width, height; + private EmacsDrawable drawable; + private EmacsGC immutableGC; + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsFillRectangle (EmacsDrawable drawable, int x, int y, + int width, int height, + EmacsGC immutableGC) + { + this.drawable = drawable; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.immutableGC = immutableGC; + } + + @Override + public Rect + getRect () + { + return new Rect (x, y, x + width, y + height); + } + + @Override + public EmacsDrawable + getDrawable () + { + return drawable; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int alu; + Paint maskPaint; + Canvas maskCanvas; + Bitmap maskBitmap; + Rect rect, srcRect; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + + paint.setStyle (Paint.Style.FILL); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + if (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawRect (rect, paint); + } + else + { + maskPaint = new Paint (); + maskBitmap + = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, + true); + + if (maskBitmap == null) + return; + + maskPaint.setXfermode (srcInAlu); + maskPaint.setColor (immutableGC.foreground | 0xff000000); + maskCanvas = new Canvas (maskBitmap); + srcRect = new Rect (0, 0, maskBitmap.getWidth (), + maskBitmap.getHeight ()); + maskCanvas.drawRect (srcRect, maskPaint); + canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + } + + paint.setXfermode (null); + } +} diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java new file mode 100644 index 00000000000..f419e71059d --- /dev/null +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -0,0 +1,150 @@ +/* Font backend 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.util.List; + +public abstract class EmacsFontDriver +{ + /* Font weights. */ + public static final int THIN = 0; + public static final int ULTRA_LIGHT = 40; + public static final int LIGHT = 50; + public static final int SEMI_LIGHT = 55; + public static final int REGULAR = 80; + public static final int MEDIUM = 100; + public static final int SEMI_BOLD = 180; + public static final int BOLD = 200; + public static final int EXTRA_BOLD = 205; + public static final int BLACK = 210; + public static final int ULTRA_HEAVY = 250; + + /* Font slants. */ + public static final int REVERSE_OBLIQUE = 0; + public static final int REVERSE_ITALIC = 10; + public static final int NORMAL = 100; + public static final int ITALIC = 200; + public static final int OBLIQUE = 210; + + /* Font widths. */ + public static final int ULTRA_CONDENSED = 50; + public static final int EXTRA_CONDENSED = 63; + public static final int CONDENSED = 75; + public static final int SEMI_CONDENSED = 87; + public static final int UNSPECIFIED = 100; + public static final int SEMI_EXPANDED = 113; + public static final int EXPANDED = 125; + public static final int EXTRA_EXPANDED = 150; + public static final int ULTRA_EXPANDED = 200; + + /* Font spacings. */ + public static final int PROPORTIONAL = 0; + public static final int DUAL = 90; + public static final int MONO = 100; + public static final int CHARCELL = 110; + + public class FontSpec + { + /* The fields below mean the same as they do in enum + font_property_index in font.h. */ + + public String foundry; + public String family; + public String adstyle; + public String registry; + public Integer width; + public Integer weight; + public Integer slant; + public Integer size; + public Integer spacing; + public Integer avgwidth; + + @Override + public String + toString () + { + return ("foundry: " + foundry + + " family: " + family + + " adstyle: " + adstyle + + " registry: " + registry + + " width: " + width + + " weight: " + weight + + " slant: " + slant + + " spacing: " + spacing + + " avgwidth: " + avgwidth); + } + }; + + public class FontMetrics + { + public short lbearing; + public short rbearing; + public short width; + public short ascent; + public short descent; + } + + public class FontEntity extends FontSpec + { + /* No extra fields here. */ + }; + + public abstract class FontObject extends FontSpec + { + public int minWidth; + public int maxWidth; + public int pixelSize; + public int height; + public int spaceWidth; + public int averageWidth; + public int ascent; + public int descent; + public int underlineThickness; + public int underlinePosition; + public int baselineOffset; + public int relativeCompose; + public int defaultAscent; + public int encodingCharset; + public int repertoryCharset; + + public + FontObject () + { + encodingCharset = -1; + repertoryCharset = -1; + } + }; + + /* These mean the same as they do in struct font_driver. */ + public abstract FontEntity[] list (FontSpec fontSpec); + public abstract FontEntity match (FontSpec fontSpec); + public abstract String[] listFamilies (); + public abstract FontObject openFont (FontEntity fontEntity, int pixelSize); + public abstract int hasChar (FontSpec font, char charCode); + public abstract void textExtents (FontObject font, int code[], + FontMetrics fontMetrics[]); + public abstract int encodeChar (FontObject fontObject, char charCode); + + public static EmacsFontDriver + createFontDriver () + { + return new EmacsSdk7FontDriver (); + } +}; diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java new file mode 100644 index 00000000000..0becb04519d --- /dev/null +++ b/java/org/gnu/emacs/EmacsGC.java @@ -0,0 +1,116 @@ +/* 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.graphics.Rect; + +/* X like graphics context structures. Keep the enums in synch with + androidgui.h! */ + +public class EmacsGC extends EmacsHandleObject +{ + public static final int GC_COPY = 0; + public static final int GC_XOR = 1; + + public static final int GC_FILL_SOLID = 0; + public static final int GC_FILL_OPAQUE_STIPPLED = 1; + + public int function, fill_style; + public int foreground, background; + public int clip_x_origin, clip_y_origin; + public int ts_origin_x, ts_origin_y; + public Rect clip_rects[]; + public EmacsPixmap clip_mask, stipple; + private boolean dirty; + private EmacsGC immutableGC; + + /* The following fields are only set on immutable GCs. */ + + public + EmacsGC (short handle) + { + /* For historical reasons the C code has an extra layer of + indirection above this GC handle. struct android_gc is the GC + used by Emacs code, while android_gcontext is the type of the + handle. */ + super (handle); + + fill_style = GC_FILL_SOLID; + function = GC_COPY; + foreground = 0; + background = 0xffffffff; + } + + public + EmacsGC (EmacsGC source) + { + super ((short) 0); + + int i; + + function = source.function; + fill_style = source.fill_style; + foreground = source.foreground; + background = source.background; + clip_x_origin = source.clip_x_origin; + clip_y_origin = source.clip_y_origin; + clip_rects = source.clip_rects; + clip_mask = source.clip_mask; + stipple = source.stipple; + ts_origin_x = source.ts_origin_x; + ts_origin_y = source.ts_origin_y; + + /* Offset all the clip rects by ts_origin_x and ts_origin_y. */ + + if ((ts_origin_x != 0 || ts_origin_y != 0) + && clip_rects != null) + { + clip_rects = new Rect[clip_rects.length]; + + for (i = 0; i < clip_rects.length; ++i) + { + clip_rects[i] = new Rect (source.clip_rects[i]); + clip_rects[i].offset (ts_origin_x, + ts_origin_y); + } + } + } + + /* Mark this GC as dirty. This means immutableGC will return a new + copy of this GC the next time it is called. */ + + public void + markDirty () + { + dirty = true; + } + + public EmacsGC + immutableGC () + { + if (immutableGC == null || dirty) + { + immutableGC = new EmacsGC (this); + dirty = false; + } + + return immutableGC; + }; +}; diff --git a/java/org/gnu/emacs/EmacsHandleObject.java b/java/org/gnu/emacs/EmacsHandleObject.java new file mode 100644 index 00000000000..a57a3bbdfa9 --- /dev/null +++ b/java/org/gnu/emacs/EmacsHandleObject.java @@ -0,0 +1,62 @@ +/* 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.util.List; +import java.util.ArrayList; +import java.lang.Object; +import java.lang.IllegalStateException; + +/* This defines something that is a so-called ``handle''. Handles + must be created by C code, and will remain existing until + destroyHandle is called. C code then refers to the handle by a + number which maps into the Java object representing the handle. + + All handle operations must be done from the Emacs thread. */ + +public abstract class EmacsHandleObject +{ + /* Whether or not this handle has been destroyed. */ + volatile boolean destroyed; + + /* The handle associated with this object. */ + public short handle; + + public + EmacsHandleObject (short handle) + { + this.handle = handle; + } + + public void + destroyHandle () throws IllegalStateException + { + synchronized (this) + { + destroyed = true; + } + } + + public boolean + isDestroyed () + { + return destroyed; + } +}; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java new file mode 100644 index 00000000000..6550e6fa2a1 --- /dev/null +++ b/java/org/gnu/emacs/EmacsNative.java @@ -0,0 +1,72 @@ +/* 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.System; + +import android.content.res.AssetManager; + +public class EmacsNative +{ + /* Set certain parameters before initializing Emacs. This proves + that libemacs.so is being loaded from Java code. + + 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. + + emacsService must be the emacsService singleton. */ + public static native void setEmacsParams (AssetManager assetManager, + String filesDir, + String libDir, + 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[]); + + /* Abort and generate a native core dump. */ + public static native void emacsAbort (); + + /* Send an ANDROID_CONFIGURE_NOTIFY event. */ + public static native void sendConfigureNotify (short window, long time, + int x, int y, int width, + int height); + + /* Send an ANDROID_KEY_PRESS event. */ + public static native void sendKeyPress (short window, long time, int state, + int keyCode); + + /* Send an ANDROID_KEY_RELEASE event. */ + public static native void sendKeyRelease (short window, long time, int state, + int keyRelease); + + static + { + System.loadLibrary ("emacs"); + }; +}; diff --git a/java/org/gnu/emacs/EmacsPaintQueue.java b/java/org/gnu/emacs/EmacsPaintQueue.java new file mode 100644 index 00000000000..5af5868d3b9 --- /dev/null +++ b/java/org/gnu/emacs/EmacsPaintQueue.java @@ -0,0 +1,138 @@ +/* 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.util.LinkedList; +import java.util.List; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + +public class EmacsPaintQueue +{ + /* Queue of paint operations. This is modified from the Emacs + thread, and entire paint queues are periodically flushed to the + application thread where it is executed. */ + private List paintOperations; + + /* Number of operations in this queue. */ + public int numRequests; + + public + EmacsPaintQueue () + { + paintOperations = new LinkedList (); + } + + public void + run () + { + EmacsDrawable drawable, last; + Canvas canvas; + EmacsGC gc, lastGC; + int i; + Paint paint; + Rect rect, offsetRect, copyRect; + + canvas = null; + last = null; + gc = null; + paint = new Paint (); + + for (EmacsPaintReq req : paintOperations) + { + drawable = req.getDrawable (); + + synchronized (req) + { + /* Ignore graphics requests for drawables that have been + destroyed. */ + if (drawable.isDestroyed ()) + continue; + } + + canvas = drawable.lockCanvas (); + + if (canvas == null) + /* No canvas is currently available. */ + continue; + + lastGC = gc; + gc = req.getGC (); + rect = req.getRect (); + + if (gc.clip_rects == null) + { + /* No clipping is applied. Just draw and continue. */ + canvas.save (); + req.paintTo (canvas, paint, gc); + canvas.restore (); + drawable.damageRect (rect); + continue; + } + + if (gc.clip_rects != null && gc.clip_rects.length > 0) + { + canvas.save (); + + if (gc.clip_rects.length == 1) + { + /* There is only a single clip rect, which is simple + enough. */ + canvas.clipRect (gc.clip_rects[0]); + req.paintTo (canvas, paint, gc); + } + else + { + /* There are multiple clip rects. Android doesn't let + programs use RegionOp.UNION on the clip rectangle, + so Emacs must iterate over each intersection and + paint it manually. This seems inefficient but + thankfully Emacs never seems to use more than one + clip rect. */ + + for (i = 0; i < gc.clip_rects.length; ++i) + { + copyRect = new Rect (gc.clip_rects[i]); + + if (copyRect.intersect (rect)) + { + canvas.save (); + canvas.clipRect (copyRect); + req.paintTo (canvas, paint, gc); + canvas.restore (); + } + } + } + + drawable.damageRect (rect); + canvas.restore (); + } + } + } + + public void + appendPaintOperation (EmacsPaintReq req) + { + paintOperations.add (req); + numRequests++; + } +}; diff --git a/java/org/gnu/emacs/EmacsPaintReq.java b/java/org/gnu/emacs/EmacsPaintReq.java new file mode 100644 index 00000000000..5b14b005093 --- /dev/null +++ b/java/org/gnu/emacs/EmacsPaintReq.java @@ -0,0 +1,33 @@ +/* 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.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + +public interface EmacsPaintReq +{ + public EmacsDrawable getDrawable (); + public EmacsGC getGC (); + public void paintTo (Canvas canvas, Paint paint, + EmacsGC immutableGC); + public Rect getRect (); +}; diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java new file mode 100644 index 00000000000..ccd5f1e0043 --- /dev/null +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -0,0 +1,102 @@ +/* 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.IllegalArgumentException; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; + +/* Drawable backed by bitmap. */ + +public class EmacsPixmap extends EmacsHandleObject + implements EmacsDrawable +{ + /* The depth of the bitmap. This is not actually used, just defined + in order to be consistent with X. */ + public int depth, width, height; + + /* The bitmap itself. */ + public Bitmap bitmap; + + /* The canvas used to draw to BITMAP. */ + public Canvas canvas; + + public + EmacsPixmap (short handle, int colors[], int width, + int height, int depth) + { + super (handle); + + if (depth != 1 && depth != 24) + throw new IllegalArgumentException ("Invalid depth specified" + + " for pixmap: " + depth); + + switch (depth) + { + case 1: + bitmap = Bitmap.createBitmap (colors, width, height, + Bitmap.Config.ALPHA_8); + break; + + case 24: + bitmap = Bitmap.createBitmap (colors, width, height, + Bitmap.Config.ARGB_8888); + bitmap.setHasAlpha (false); + break; + } + + this.width = width; + this.height = height; + this.depth = depth; + } + + @Override + public Canvas + lockCanvas () + { + if (canvas == null) + canvas = new Canvas (bitmap); + + return canvas; + } + + @Override + public void + unlockCanvas () + { + + } + + @Override + public void + damageRect (Rect damageRect) + { + + } + + @Override + public Bitmap + getBitmap () + { + return bitmap; + } +}; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java new file mode 100644 index 00000000000..5a8cdbfc75b --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -0,0 +1,460 @@ +/* Font backend 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.io.File; +import java.io.IOException; + +import java.util.LinkedList; +import java.util.List; + +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; + +import android.util.Log; + +public class EmacsSdk7FontDriver extends EmacsFontDriver +{ + private static final String TOFU_STRING = "\uDB3F\uDFFD"; + private static final String EM_STRING = "m"; + private static final String TAG = "EmacsSdk7FontDriver"; + + private class Sdk7Typeface + { + /* The typeface and paint. */ + public Typeface typeface; + public Paint typefacePaint; + public String familyName; + public int slant, width, weight, spacing; + + public + Sdk7Typeface (String fileName, Typeface typeface) + { + String style, testString; + int index, measured, i; + float[] widths; + + slant = NORMAL; + weight = REGULAR; + width = UNSPECIFIED; + spacing = PROPORTIONAL; + + typefacePaint = new Paint (); + typefacePaint.setTypeface (typeface); + + /* For the calls to measureText below. */ + typefacePaint.setTextSize (10.0f); + + /* Parse the file name into some useful data. First, strip off + the extension. */ + fileName = fileName.split ("\\.", 2)[0]; + + /* Next, split the file name by dashes. Everything before the + last dash is part of the family name. */ + index = fileName.lastIndexOf ("-"); + + if (index > 0) + { + style = fileName.substring (index + 1, fileName.length ()); + familyName = fileName.substring (0, index); + + /* Look for something describing the weight. */ + if (style.contains ("Thin")) + weight = THIN; + else if (style.contains ("UltraLight")) + weight = ULTRA_LIGHT; + else if (style.contains ("SemiLight")) + weight = SEMI_LIGHT; + else if (style.contains ("Light")) + weight = LIGHT; + else if (style.contains ("Medium")) + weight = MEDIUM; + else if (style.contains ("SemiBold")) + weight = SEMI_BOLD; + else if (style.contains ("ExtraBold")) + weight = EXTRA_BOLD; + else if (style.contains ("Bold")) + weight = BOLD; + else if (style.contains ("Black")) + weight = BLACK; + else if (style.contains ("UltraHeavy")) + weight = ULTRA_HEAVY; + + /* And the slant. */ + if (style.contains ("ReverseOblique")) + slant = OBLIQUE; + else if (style.contains ("ReverseItalic")) + slant = REVERSE_ITALIC; + else if (style.contains ("Italic")) + slant = ITALIC; + else if (style.contains ("Oblique")) + slant = OBLIQUE; + + /* Finally, the width. */ + if (style.contains ("UltraCondensed")) + width = ULTRA_CONDENSED; + else if (style.contains ("ExtraCondensed")) + width = EXTRA_CONDENSED; + else if (style.contains ("SemiCondensed")) + width = SEMI_CONDENSED; + else if (style.contains ("Condensed")) + width = CONDENSED; + else if (style.contains ("SemiExpanded")) + width = SEMI_EXPANDED; + else if (style.contains ("ExtraExpanded")) + width = EXTRA_EXPANDED; + else if (style.contains ("UltraExpanded")) + width = ULTRA_EXPANDED; + else if (style.contains ("Expanded")) + width = EXPANDED; + + /* Guess the spacing information. */ + testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + widths = new float[testString.length ()]; + + measured = typefacePaint.getTextWidths (testString, + 0, testString.length (), + widths); + spacing = MONO; + for (i = 0; i < measured; ++i) + { + if (i != 0 && widths[i - 1] != widths[i]) + /* This isn't a monospace font. */ + spacing = PROPORTIONAL; + } + } + else + familyName = fileName; + + Log.d (TAG, "Initialized new typeface " + familyName); + } + + @Override + public String + toString () + { + return ("Sdk7Typeface (" + + String.valueOf (familyName) + ", " + + String.valueOf (slant) + ", " + + String.valueOf (width) + ", " + + String.valueOf (weight) + ", " + + String.valueOf (spacing) + ")"); + } + }; + + private class Sdk7FontEntity extends FontEntity + { + /* The typeface. */ + public Sdk7Typeface typeface; + + public + Sdk7FontEntity (Sdk7Typeface typeface) + { + float width; + + foundry = "Google"; + family = typeface.familyName; + adstyle = null; + weight = typeface.weight; + slant = typeface.slant; + spacing = typeface.spacing; + width = typeface.width; + + this.typeface = typeface; + } + }; + + private class Sdk7FontObject extends FontObject + { + /* The typeface. */ + public Sdk7Typeface typeface; + + /* The text size. */ + public int pixelSize; + + public + Sdk7FontObject (Sdk7Typeface typeface, int pixelSize) + { + float totalWidth; + String testWidth, testString; + + this.typeface = typeface; + this.pixelSize = pixelSize; + + family = typeface.familyName; + adstyle = null; + weight = typeface.weight; + slant = typeface.slant; + spacing = typeface.spacing; + width = typeface.width; + + /* Compute the ascent and descent. */ + typeface.typefacePaint.setTextSize (pixelSize); + ascent + = Math.round (-typeface.typefacePaint.ascent ()); + descent + = Math.round (typeface.typefacePaint.descent ()); + + /* Compute the average width. */ + testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + totalWidth = typeface.typefacePaint.measureText (testString); + + if (totalWidth > 0) + avgwidth = Math.round (totalWidth + / testString.length ()); + + /* Android doesn't expose the font average width and height + information, so this will have to do. */ + minWidth = maxWidth = avgwidth; + + /* This is different from avgwidth in the font spec! */ + averageWidth = avgwidth; + + /* Set the space width. */ + totalWidth = typeface.typefacePaint.measureText (" "); + spaceWidth = Math.round (totalWidth); + + /* Set the height and default ascent. */ + height = ascent + descent; + defaultAscent = ascent; + } + }; + + private String[] fontFamilyList; + private Sdk7Typeface[] typefaceList; + private Sdk7Typeface fallbackTypeface; + + public + EmacsSdk7FontDriver () + { + int i; + File systemFontsDirectory, fontFile; + Typeface typeface; + + systemFontsDirectory = new File ("/system/fonts"); + + fontFamilyList = systemFontsDirectory.list (); + typefaceList = new Sdk7Typeface[fontFamilyList.length]; + + /* It would be nice to avoid opening each and every font upon + startup. But that doesn't seem to be possible on + Android. */ + + for (i = 0; i < fontFamilyList.length; ++i) + { + fontFile = new File (systemFontsDirectory, + fontFamilyList[i]); + typeface = Typeface.createFromFile (fontFile); + typefaceList[i] = new Sdk7Typeface (fontFile.getName (), + typeface); + } + + fallbackTypeface = new Sdk7Typeface ("monospace", + Typeface.MONOSPACE); + } + + private boolean + checkMatch (Sdk7Typeface typeface, FontSpec fontSpec) + { + if (fontSpec.family != null + && !fontSpec.family.equals (typeface.familyName)) + return false; + + + if (fontSpec.adstyle != null + && !fontSpec.adstyle.isEmpty ()) + /* return false; */; + + if (fontSpec.slant != null + && !fontSpec.weight.equals (typeface.weight)) + return false; + + if (fontSpec.spacing != null + && !fontSpec.spacing.equals (typeface.spacing)) + return false; + + if (fontSpec.weight != null + && !fontSpec.weight.equals (typeface.weight)) + return false; + + if (fontSpec.width != null + && !fontSpec.width.equals (typeface.width)) + return false; + + return true; + } + + @Override + public FontEntity[] + list (FontSpec fontSpec) + { + LinkedList list; + int i; + + list = new LinkedList (); + + Log.d (TAG, ("Looking for fonts matching font spec: " + + fontSpec.toString ())); + + for (i = 0; i < typefaceList.length; ++i) + { + if (checkMatch (typefaceList[i], fontSpec)) + list.add (new Sdk7FontEntity (typefaceList[i])); + } + + Log.d (TAG, "Found font entities: " + list.toString ()); + + return (FontEntity[]) list.toArray (new FontEntity[0]); + } + + @Override + public FontEntity + match (FontSpec fontSpec) + { + FontEntity[] entities; + int i; + + entities = this.list (fontSpec); + + if (entities.length == 0) + return new Sdk7FontEntity (fallbackTypeface); + + return entities[0]; + } + + @Override + public String[] + listFamilies () + { + return fontFamilyList; + } + + @Override + public FontObject + openFont (FontEntity fontEntity, int pixelSize) + { + return new Sdk7FontObject (((Sdk7FontEntity) fontEntity).typeface, + pixelSize); + } + + @Override + public int + hasChar (FontSpec font, char charCode) + { + float missingGlyphWidth, emGlyphWidth, width; + Rect rect1, rect2; + Paint paint; + Sdk7FontObject fontObject; + + if (font instanceof Sdk7FontObject) + { + fontObject = (Sdk7FontObject) font; + paint = fontObject.typeface.typefacePaint; + } + else + paint = ((Sdk7FontEntity) font).typeface.typefacePaint; + + paint.setTextSize (10); + + if (Character.isWhitespace (charCode)) + return 1; + + missingGlyphWidth = paint.measureText (TOFU_STRING); + emGlyphWidth = paint.measureText (EM_STRING); + width = paint.measureText ("" + charCode); + + if (width == 0f) + return 0; + + if (width != missingGlyphWidth) + return 1; + + rect1 = new Rect (); + rect2 = new Rect (); + + paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (), + rect1); + paint.getTextBounds ("" + charCode, 0, 1, rect2); + return rect1.equals (rect2) ? 1 : 0; + } + + private void + textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics, + Paint paint, Rect bounds) + { + char[] text; + float[] width; + + text = new char[1]; + text[0] = (char) code; + + paint.getTextBounds (text, 0, 1, bounds); + width = new float[1]; + paint.getTextWidths (text, 0, 1, width); + + /* bounds is the bounding box of the glyph corresponding to CODE. + Translate these into XCharStruct values. + + The origin is at 0, 0, and lbearing is the distance counting + rightwards from the origin to the left most pixel in the glyph + raster. rbearing is the distance between the origin and the + rightmost pixel in the glyph raster. ascent is the distance + counting upwards between the the topmost pixel in the glyph + raster. descent is the distance (once again counting + downwards) between the origin and the bottommost pixel in the + glyph raster. + + width is the distance between the origin and the origin of any + character to the right. */ + + metrics.lbearing = (short) bounds.left; + metrics.rbearing = (short) bounds.right; + metrics.ascent = (short) -bounds.top; + metrics.descent = (short) bounds.bottom; + metrics.width = (short) Math.round (width[0]); + } + + @Override + public void + textExtents (FontObject font, int code[], FontMetrics fontMetrics[]) + { + int i; + Paint paintCache; + Rect boundsCache; + Sdk7FontObject fontObject; + + fontObject = (Sdk7FontObject) font; + paintCache = fontObject.typeface.typefacePaint; + paintCache.setTextSize (fontObject.pixelSize); + boundsCache = new Rect (); + + for (i = 0; i < code.length; ++i) + textExtents1 ((Sdk7FontObject) font, code[i], fontMetrics[i], + paintCache, boundsCache); + } + + @Override + public int + encodeChar (FontObject fontObject, char charCode) + { + return charCode; + } +}; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java new file mode 100644 index 00000000000..311226e6f7e --- /dev/null +++ b/java/org/gnu/emacs/EmacsService.java @@ -0,0 +1,384 @@ +/* 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.Runnable; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +import android.graphics.Canvas; +import android.graphics.Bitmap; +import android.graphics.Point; + +import android.annotation.TargetApi; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.os.Build; +import android.os.Looper; +import android.os.IBinder; +import android.os.Handler; +import android.util.Log; + +class Holder +{ + T thing; +}; + +/* EmacsService is the service that starts the thread running Emacs + and handles requests by that Emacs instance. */ + +public class EmacsService extends Service +{ + public static final String TAG = "EmacsService"; + public static final int MAX_PENDING_REQUESTS = 256; + public static volatile EmacsService SERVICE; + + private EmacsThread thread; + private Handler handler; + private EmacsPaintQueue paintQueue; + + /* List of all EmacsWindows that are available to attach to an + activity. */ + public static List availableChildren; + + static + { + availableChildren = new ArrayList (); + }; + + @Override + public int + onStartCommand (Intent intent, int flags, int startId) + { + return START_NOT_STICKY; + } + + @Override + public IBinder + onBind (Intent intent) + { + return null; + } + + @TargetApi (Build.VERSION_CODES.GINGERBREAD) + String + getLibraryDirectory () + { + int apiLevel; + Context context; + + context = getApplicationContext (); + apiLevel = android.os.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"; + } + + @Override + public void + onCreate () + { + AssetManager manager; + Context app_context; + String filesDir, libDir; + + SERVICE = this; + handler = new Handler (Looper.getMainLooper ()); + manager = getAssets (); + app_context = getApplicationContext (); + + try + { + /* Configure Emacs with the asset manager and other necessary + parameters. */ + filesDir = app_context.getFilesDir ().getCanonicalPath (); + libDir = getLibraryDirectory (); + + Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir + + " and libDir = " + libDir); + + EmacsNative.setEmacsParams (manager, filesDir, libDir, + this); + + /* Start the thread that runs Emacs. */ + thread = new EmacsThread (this); + thread.start (); + } + catch (IOException exception) + { + EmacsNative.emacsAbort (); + return; + } + } + + + + /* Functions from here on must only be called from the Emacs + thread. */ + + void + runOnUiThread (Runnable runnable) + { + handler.post (runnable); + } + + EmacsView + getEmacsView (final EmacsWindow window) + { + Runnable runnable; + final Holder view; + + view = new Holder (); + + runnable = new Runnable () { + public void + run () + { + synchronized (this) + { + view.thing = new EmacsView (window); + notify (); + } + } + }; + + synchronized (runnable) + { + runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + + return view.thing; + } + + /* Notice that a child of the root window named WINDOW is now + available for attachment to a specific activity. */ + + public void + noticeAvailableChild (final EmacsWindow window) + { + Log.d (TAG, "A new child is available: " + window); + + handler.post (new Runnable () { + public void + run () + { + for (EmacsActivity activity + : EmacsActivity.availableActivities) + { + /* TODO: check if the activity matches. */ + activity.attachChild (window); + break; + } + + /* Nope, wait for an activity to become available. */ + availableChildren.add (window); + } + }); + } + + /* Notice that a child of the root window named WINDOW has been + destroyed. */ + + public void + noticeChildDestroyed (final EmacsWindow child) + { + handler.post (new Runnable () { + @Override + public void + run () + { + availableChildren.remove (child); + } + }); + } + + /* X drawing operations. These are quite primitive operations. The + drawing queue is kept on the Emacs thread, but is periodically + flushed to the application thread, upon buffers swaps and once it + gets too big. */ + + + + private void + ensurePaintQueue () + { + if (paintQueue == null) + paintQueue = new EmacsPaintQueue (); + } + + public void + flushPaintQueue () + { + final EmacsPaintQueue queue; + + if (paintQueue == null) + return; + + if (paintQueue.numRequests < 1) + /* No requests to flush. */ + return; + + queue = paintQueue; + + handler.post (new Runnable () { + @Override + public void + run () + { + queue.run (); + } + }); + + /* Clear the paint queue. */ + paintQueue = null; + } + + private void + checkFlush () + { + if (paintQueue != null + && paintQueue.numRequests > MAX_PENDING_REQUESTS) + flushPaintQueue (); + } + + public void + fillRectangle (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + req = new EmacsFillRectangle (drawable, x, y, + width, height, + gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + fillPolygon (EmacsDrawable drawable, EmacsGC gc, + Point points[]) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + req = new EmacsFillPolygon (drawable, points, + gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + drawRectangle (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + if (gc.clip_rects != null && gc.clip_rects.length >= 1) + android.util.Log.d ("drawRectangle", + gc.clip_rects[0].toString () + + " " + gc.toString ()); + + req = new EmacsDrawRectangle (drawable, x, y, + width, height, + gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + drawLine (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int x2, int y2) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + req = new EmacsDrawLine (drawable, x, y, + x2, y2, + gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + drawPoint (EmacsDrawable drawable, EmacsGC gc, + int x, int y) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + req = new EmacsDrawPoint (drawable, x, y, + gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + copyArea (EmacsDrawable srcDrawable, EmacsDrawable dstDrawable, + EmacsGC gc, + int srcX, int srcY, int width, int height, int destX, + int destY) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + req = new EmacsCopyArea (srcDrawable, dstDrawable, + srcX, srcY, width, height, destX, + destY, gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + clearWindow (EmacsWindow window) + { + window.clearWindow (); + } + + public void + clearArea (EmacsWindow window, int x, int y, int width, + int height) + { + window.clearArea (x, y, width, height); + } +}; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java new file mode 100644 index 00000000000..194f6ad37a3 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -0,0 +1,83 @@ +/* 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.view.SurfaceView; +import android.view.SurfaceHolder; + +import android.graphics.Canvas; +import android.graphics.Rect; + +public class EmacsSurfaceView extends SurfaceView +{ + private boolean created; + + public + EmacsSurfaceView (final EmacsView view) + { + super (view.getContext ()); + + getHolder ().addCallback (new SurfaceHolder.Callback () { + @Override + public void + surfaceChanged (SurfaceHolder holder, int format, + int width, int height) + { + + } + + @Override + public void + surfaceCreated (SurfaceHolder holder) + { + created = true; + + /* Force a buffer swap now to get the contents of the Emacs + view on screen. */ + view.swapBuffers (); + } + + @Override + public void + surfaceDestroyed (SurfaceHolder holder) + { + created = false; + } + }); + } + + public boolean + isCreated () + { + return created; + } + + public Canvas + lockCanvas (Rect damage) + { + return getHolder ().lockCanvas (damage); + } + + public void + unlockCanvasAndPost (Canvas canvas) + { + getHolder ().unlockCanvasAndPost (canvas); + } +}; 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); + } +}; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java new file mode 100644 index 00000000000..237946d6366 --- /dev/null +++ b/java/org/gnu/emacs/EmacsView.java @@ -0,0 +1,211 @@ +/* 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.view.View; +import android.view.KeyEvent; +import android.view.ViewGroup; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Paint; +import android.util.Log; + +import android.os.Build; + +/* This is an Android view which has a back and front buffer. When + swapBuffers is called, the back buffer is swapped to the front + buffer, and any damage is invalidated. frontBitmap and backBitmap + are modified and used both from the UI and the Emacs thread. As a + result, there is a lock held during all drawing operations. + + It is also a ViewGroup, as it also lays out children. */ + +public class EmacsView extends ViewGroup +{ + public static final String TAG = "EmacsView"; + + /* The associated EmacsWindow. */ + public EmacsWindow window; + + /* The buffer bitmap. */ + public Bitmap bitmap; + + /* The associated canvases. */ + public Canvas canvas; + + /* The damage region. */ + public Region damageRegion; + + /* The paint. */ + public Paint paint; + + /* The associated surface view. */ + private EmacsSurfaceView surfaceView; + + public + EmacsView (EmacsWindow window) + { + super (EmacsService.SERVICE); + + this.window = window; + this.damageRegion = new Region (); + this.paint = new Paint (); + + /* Create the surface view. */ + this.surfaceView = new EmacsSurfaceView (this); + + setFocusable (FOCUSABLE); + addView (this.surfaceView); + } + + @Override + protected void + onMeasure (int widthMeasureSpec, int heightMeasureSpec) + { + Rect measurements; + int width, height; + + /* Return the width and height of the window regardless of what + the parent says. */ + measurements = window.getGeometry (); + + width = measurements.width (); + height = measurements.height (); + + /* Now apply any extra requirements in widthMeasureSpec and + heightMeasureSpec. */ + + if (MeasureSpec.getMode (widthMeasureSpec) == MeasureSpec.EXACTLY) + width = MeasureSpec.getSize (widthMeasureSpec); + else if (MeasureSpec.getMode (widthMeasureSpec) == MeasureSpec.AT_MOST + && width > MeasureSpec.getSize (widthMeasureSpec)) + width = MeasureSpec.getSize (widthMeasureSpec); + + if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.EXACTLY) + height = MeasureSpec.getSize (heightMeasureSpec); + else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST + && height > MeasureSpec.getSize (heightMeasureSpec)) + height = MeasureSpec.getSize (heightMeasureSpec); + + super.setMeasuredDimension (width, height); + } + + @Override + protected void + onLayout (boolean changed, int left, int top, int right, + int bottom) + { + int count, i; + View child; + Rect windowRect; + + if (changed) + { + window.viewLayout (left, top, right, bottom); + + /* Recreate the front and back buffer bitmaps. */ + bitmap + = Bitmap.createBitmap (right - left, bottom - top, + Bitmap.Config.ARGB_8888); + + /* And canvases. */ + canvas = new Canvas (bitmap); + } + + count = getChildCount (); + + for (i = 0; i < count; ++i) + { + child = getChildAt (i); + + if (child == surfaceView) + /* The child is the surface view, so give it the entire + view. */ + child.layout (left, top, right, bottom); + else if (child.getVisibility () != GONE) + { + if (!(child instanceof EmacsView)) + continue; + + /* What to do: lay out the view precisely according to its + window rect. */ + windowRect = ((EmacsView) child).window.getGeometry (); + child.layout (windowRect.left, windowRect.top, + windowRect.right, windowRect.bottom); + } + } + } + + public void + damageRect (Rect damageRect) + { + damageRegion.union (damageRect); + } + + public void + swapBuffers () + { + Bitmap back; + Canvas canvas; + Rect damageRect; + + if (damageRegion.isEmpty ()) + return; + + if (!surfaceView.isCreated ()) + return; + + if (bitmap == null) + return; + + /* Lock the canvas with the specified damage. */ + damageRect = damageRegion.getBounds (); + canvas = surfaceView.lockCanvas (damageRect); + + /* Return if locking the canvas failed. */ + if (canvas == null) + return; + + /* Copy from the back buffer to the canvas. */ + canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + + /* Unlock the canvas and clear the damage. */ + surfaceView.unlockCanvasAndPost (canvas); + damageRegion.setEmpty (); + } + + @Override + public boolean + onKeyDown (int keyCode, KeyEvent event) + { + window.onKeyDown (keyCode, event); + return true; + } + + @Override + public boolean + onKeyUp (int keyCode, KeyEvent event) + { + window.onKeyUp (keyCode, event); + return true; + } +}; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java new file mode 100644 index 00000000000..28db04a261d --- /dev/null +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -0,0 +1,336 @@ +/* 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.IllegalStateException; +import java.util.ArrayList; +import java.util.List; + +import android.graphics.Rect; +import android.graphics.Canvas; +import android.graphics.Bitmap; +import android.graphics.Point; + +import android.view.View; +import android.view.ViewGroup; +import android.view.KeyEvent; + +/* This defines a window, which is a handle. Windows represent a + rectangular subset of the screen with their own contents. + + Windows either have a parent window, in which case their views are + attached to the parent's view, or are "floating", in which case + their views are attached to the parent activity (if any), else + nothing. + + Views are also drawables, meaning they can accept drawing + requests. */ + +public class EmacsWindow extends EmacsHandleObject + implements EmacsDrawable +{ + /* The view associated with the window. */ + public EmacsView view; + + /* The geometry of the window. */ + private Rect rect; + + /* The parent window, or null if it is the root window. */ + private EmacsWindow parent; + + /* List of all children in stacking order. This must be kept + consistent! */ + private ArrayList children; + + /* The EmacsActivity currently attached, if it exists. */ + private EmacsActivity attached; + + /* The window background scratch GC. foreground is always the + window background. */ + private EmacsGC scratchGC; + + public + EmacsWindow (short handle, final EmacsWindow parent, int x, int y, + int width, int height) + { + super (handle); + + rect = new Rect (x, y, x + width, y + height); + + /* Create the view from the context's UI thread. */ + view = EmacsService.SERVICE.getEmacsView (this); + this.parent = parent; + children = new ArrayList (); + + /* The window is unmapped by default. */ + view.setVisibility (View.GONE); + + /* If parent is the root window, notice that there are new + children available for interested activites to pick up. */ + if (parent == null) + EmacsService.SERVICE.noticeAvailableChild (this); + else + { + /* Otherwise, directly add this window as a child of that + window's view. */ + synchronized (parent) + { + parent.children.add (this); + parent.view.post (new Runnable () { + @Override + public void + run () + { + parent.view.addView (view); + } + }); + } + } + + scratchGC = new EmacsGC ((short) 0); + } + + public void + changeWindowBackground (int pixel) + { + /* scratchGC is used as the argument to a FillRectangles req. */ + scratchGC.foreground = pixel; + scratchGC.markDirty (); + } + + public Rect + getGeometry () + { + synchronized (this) + { + /* Huh, this is it. */ + return rect; + } + } + + @Override + public void + destroyHandle () throws IllegalStateException + { + synchronized (this) + { + if (!children.isEmpty ()) + throw new IllegalStateException ("Trying to destroy window with " + + "children!"); + } + + /* Notice that the child has been destroyed. */ + EmacsService.SERVICE.noticeChildDestroyed (this); + + /* Remove the view from its parent and make it invisible. */ + view.post (new Runnable () { + public void + run () + { + view.setVisibility (View.GONE); + + if (view.getParent () != null) + ((ViewGroup) view.getParent ()).removeView (view); + + if (attached != null) + attached.makeAvailable (); + } + }); + + super.destroyHandle (); + } + + public void + setActivity (EmacsActivity activity) + { + synchronized (this) + { + activity = activity; + } + } + + public void + viewLayout (int left, int top, int right, int bottom) + { + synchronized (this) + { + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; + + EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rect.width (), + rect.height ()); + } + } + + public void + requestViewLayout () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.requestLayout (); + } + }); + } + + public void + resizeWindow (int width, int height) + { + synchronized (this) + { + rect.right = rect.left + width; + rect.bottom = rect.top + height; + } + } + + public void + moveWindow (int x, int y) + { + int width, height; + + synchronized (this) + { + width = rect.width (); + height = rect.height (); + + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + + requestViewLayout (); + } + } + + public void + mapWindow () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.VISIBLE); + } + }); + } + + public void + unmapWindow () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.GONE); + } + }); + } + + @Override + public Canvas + lockCanvas () + { + if (view.canvas != null) + return view.canvas; + + return null; + } + + @Override + public void + unlockCanvas () + { + + } + + @Override + public void + damageRect (Rect damageRect) + { + view.damageRect (damageRect); + } + + public void + swapBuffers () + { + /* Before calling swapBuffers, make sure to flush the paint + queue. */ + EmacsService.SERVICE.flushPaintQueue (); + view.post (new Runnable () { + @Override + public void + run () + { + view.swapBuffers (); + } + }); + } + + public void + clearWindow () + { + synchronized (this) + { + EmacsService.SERVICE.fillRectangle (this, scratchGC, + 0, 0, rect.width (), + rect.height ()); + } + } + + public void + clearArea (int x, int y, int width, int height) + { + EmacsService.SERVICE.fillRectangle (this, scratchGC, + x, y, width, height); + } + + @Override + public Bitmap + getBitmap () + { + return view.bitmap; + } + + public void + onKeyDown (int keyCode, KeyEvent event) + { + EmacsNative.sendKeyPress (this.handle, + event.getEventTime (), + event.getModifiers (), + keyCode); + } + + public void + onKeyUp (int keyCode, KeyEvent event) + { + EmacsNative.sendKeyRelease (this.handle, + event.getEventTime (), + event.getModifiers (), + keyCode); + } +}; -- cgit v1.2.1 From a32963e11f9f8e5d22b0d754d34a867f3b178ed2 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 2 Jan 2023 21:38:19 +0800 Subject: Update Android port * Makefile.in (java): Depend on info. (MAKEFILE_NAME): (config.status): Remove unneeded changes. * configure.ac (BUILD_DETAILS, ANDROID_STUBIFY): Don't require a C++ compiler on Android. * java/AndroidManifest.xml: : Set launchMode appropriately. : New activity. * java/Makefile.in (CROSS_BINS): Add EmacsClient. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity) (onCreate): Use the window attachment manager. * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea) (paintTo): Implement clip masks correctly. * java/org/gnu/emacs/EmacsDrawRectangle.java (getRect, paintTo): Fix damage tracking rectangles. * java/org/gnu/emacs/EmacsFontDriver.java (FontSpec, toString): New function. (FontMetrics, EmacsFontDriver): Fix signature of textExtents. * java/org/gnu/emacs/EmacsMultitaskActivity.java (EmacsMultitaskActivity): New file. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New functions sendFocusIn, sendFocusOut, sendWindowAction. * java/org/gnu/emacs/EmacsPaintQueue.java (run): Fix clipping handling. * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): Add constructor for mutable pixmaps. * java/org/gnu/emacs/EmacsSdk23FontDriver.java (EmacsSdk23FontDriver): New file. * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver, Sdk7Typeface, Sdk7FontEntity, Sdk7FontObject) (checkMatch, hasChar, encodeChar): Implement text display and fix font metrics semantics. * java/org/gnu/emacs/EmacsService.java (EmacsService): Remove availableChildren. (getLibraryDirectory, onCreate): Pass pixel density to Emacs. (clearArea): Fix arguments. Switch to using the window attachment manager. * java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged) (surfaceCreated): Flip buffers on surface attachment. * java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers): New argument FORCE. Always swap if it is true. (onKeyMultiple, onFocusChanged): New functions. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, destroyHandle) (run): Switch to using the window attachment manager. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager): New file. * lisp/cus-edit.el (custom-button, custom-button-mouse) (custom-button-pressed): * lisp/faces.el (tool-bar): Define faces correctly on Android. * src/android.c (struct android_emacs_pixmap): Add mutable constructor. (struct android_emacs_drawable): New structure. (android_write_event): Check if event queue hasn't yet been initialized. (android_select): Set errno to EINTR if pselect fails. (android_close): Remove unused debugging code. (android_get_home_directory): New function. (Java_org_gnu_emacs_EmacsNative_setEmacsParams): Set pixel density and compute game path. (android_init_emacs_drawable): New function. (Java_org_gnu_emacs_EmacsNative_sendKeyPress): New argument `unicode_char'. Pass it in events. (Java_org_gnu_emacs_EmacsNative_sendKeyRelease): Likewise. (Java_org_gnu_emacs_EmacsNative_sendFocusIn) (Java_org_gnu_emacs_EmacsNative_sendFocusOut) (Java_org_gnu_emacs_EmacsNative_sendWindowAction): New functions. (android_resolve_handle): Export function. (android_change_gc): Clear clip rects under the right circumstances. Set right clip mask field. (android_create_pixmap_from_bitmap_data): Use correct alpha channels. (android_create_pixmap): Create mutable pixmap and avoid redundant color array allocation. (android_create_bitmap_from_data, android_create_image) (android_destroy_image, android_put_pixel, android_get_pixel) (android_get_image, android_put_image, faccessat): New functions. * src/android.h: Update prototypes. * src/androidfns.c (android_default_font_parameter): Prefer monospace to Droid Sans Mono. * src/androidfont.c (struct android_emacs_font_driver): New method `draw'. (struct android_emacs_font_spec): New field `dpi'. (struct androidfont_info): Add font metrics cache. (android_init_font_driver, android_init_font_spec): Adjust accordingly. (androidfont_from_lisp, androidfont_from_java): Handle new fields. (androidfont_draw): Implement function. (androidfont_open_font): Set pixel size correctly. (androidfont_close_font): Free metrics cache. (androidfont_cache_text_extents) (androidfont_check_cached_extents): New functions. (androidfont_text_extents): Cache glyph metrics somewhere for future use. (androidfont_list_family): Implement function. * src/androidgui.h (enum android_event_type): New focus and window action events. (enum android_modifier_mask): New masks. (struct android_key_event): New field `unicode_char'. (ANDROID_IS_MODIFIER_KEY): Newmacro. (struct android_focus_event, struct android_window_action_event): New structs. (union android_event): Add new fields. (enum android_image_format, struct android_image): New enums and structs. * src/androidterm.c (android_android_to_emacs_modifiers) (android_emacs_to_android_modifiers, android_lower_frame) (android_raise_frame, android_new_focus_frame) (android_focus_changed, android_detect_focus_change): New functions. (handle_one_android_event): Implement focus and key event handling. (android_frame_rehighlight): New function. (android_frame_raise_lower): Implement accordingly. (android_make_frame_invisible): Clear highlight_frame if required. (android_free_frame_resources): Clear x_focus_event_frame if required. (android_draw_fringe_bitmap, android_draw_image_foreground) (android_draw_image_foreground_1) (android_draw_image_glyph_string): Remove unnecessary code. (android_create_terminal, android_term_init): Set the baud rate to something sensible. * src/androidterm.h (struct android_bitmap_record): Make structure the same as on X. (struct android_display_info): New focus tracking fields. (struct android_output): Likewise. * src/dispextern.h (struct image): Add ximg and mask_img on Android. * src/emacs.c (android_emacs_init): Fix argc sorting iteration. * src/fileio.c (user_homedir): (get_homedir): Implement correctly on Android. * src/font.h (PT_PER_INCH): Define correctly on Android. * src/fringe.c (X, swap_nibble, init_fringe_bitmap): Swap fringe bitmaps correctly on Android. * src/image.c (GET_PIXEL, image_create_bitmap_from_data) (image_create_bitmap_from_file, free_bitmap_record) (image_unget_x_image_or_dc, struct image_type) (prepare_image_for_display, image_clear_image_1) (image_size_in_bytes, x_check_image_size) (x_create_x_image_and_pixmap, x_destroy_x_image) (image_check_image_size, image_create_x_image_and_pixmap_1) (image_destroy_x_image, gui_put_x_image, image_put_x_image) (image_get_x_image, image_unget_x_image) (Create_Pixmap_From_Bitmap_Data, image_pixmap_draw_cross) (MaskForeground, image_types, syms_of_image): Implement all of the above on Android in terms of an API very similar to X. * src/keyboard.c (FUNCTION_KEY_OFFSET, lispy_function_keys): Define on Android to something sensible. * src/lread.c (build_load_history): Fix problem. --- java/AndroidManifest.xml | 9 +- java/Makefile.in | 3 +- java/org/gnu/emacs/EmacsActivity.java | 153 +++++++++++------ java/org/gnu/emacs/EmacsCopyArea.java | 135 ++++++++++++--- java/org/gnu/emacs/EmacsDrawRectangle.java | 8 +- java/org/gnu/emacs/EmacsFontDriver.java | 26 ++- java/org/gnu/emacs/EmacsMultitaskActivity.java | 25 +++ java/org/gnu/emacs/EmacsNative.java | 18 +- java/org/gnu/emacs/EmacsPaintQueue.java | 24 +-- java/org/gnu/emacs/EmacsPixmap.java | 29 ++++ java/org/gnu/emacs/EmacsSdk23FontDriver.java | 43 +++++ java/org/gnu/emacs/EmacsSdk7FontDriver.java | 188 +++++++++++++++++++-- java/org/gnu/emacs/EmacsService.java | 83 +++------ java/org/gnu/emacs/EmacsSurfaceView.java | 37 +++- java/org/gnu/emacs/EmacsView.java | 45 ++++- java/org/gnu/emacs/EmacsWindow.java | 143 +++++++++++----- .../gnu/emacs/EmacsWindowAttachmentManager.java | 166 ++++++++++++++++++ 17 files changed, 904 insertions(+), 231 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsMultitaskActivity.java create mode 100644 java/org/gnu/emacs/EmacsSdk23FontDriver.java create mode 100644 java/org/gnu/emacs/EmacsWindowAttachmentManager.java (limited to 'java') diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index 75aa5bdf409..06417017ae3 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -31,13 +31,15 @@ android:targetSdkVersion="28"/> - + @@ -45,6 +47,9 @@ + + availableActivities; - /* The currently attached EmacsWindow, or null if none. */ private EmacsWindow window; /* The frame layout associated with the activity. */ private FrameLayout layout; + /* List of activities with focus. */ + private static List focusedActivities; + + /* The currently focused window. */ + public static EmacsWindow focusedWindow; + static { - /* Set up the list of available activities. */ - availableActivities = new ArrayList (); + focusedActivities = new ArrayList (); }; + public static void + invalidateFocus1 (EmacsWindow window) + { + if (window.view.isFocused ()) + focusedWindow = window; + + for (EmacsWindow child : window.children) + invalidateFocus1 (window); + } + + public static void + invalidateFocus () + { + EmacsWindow oldFocus; + + /* Walk through each focused activity and assign the window focus + to the bottom-most focused window within. Record the old focus + as well. */ + oldFocus = focusedWindow; + focusedWindow = null; + + for (EmacsActivity activity : focusedActivities) + { + if (activity.window != null) + invalidateFocus1 (activity.window); + } + + /* Send focus in- and out- events to the previous and current + focus. */ + + if (oldFocus != null) + EmacsNative.sendFocusOut (oldFocus.handle, + System.currentTimeMillis ()); + + if (focusedWindow != null) + EmacsNative.sendFocusIn (focusedWindow.handle, + System.currentTimeMillis ()); + } + + @Override public void - attachChild (EmacsWindow child) + detachWindow () + { + if (window == null) + Log.w (TAG, "detachWindow called, but there is no window"); + else + { + /* Clear the window's pointer to this activity and remove the + window's view. */ + window.setConsumer (null); + layout.removeView (window.view); + window = null; + + invalidateFocus (); + } + } + + @Override + public void + attachWindow (EmacsWindow child) { if (window != null) throw new IllegalStateException ("trying to attach window when one" + " already exists"); /* Record and attach the view. */ + window = child; layout.addView (window.view); + child.setConsumer (this); - /* Remove the objects from the lists of what is available. */ - EmacsService.availableChildren.remove (child); - availableActivities.remove (this); - - /* Now set child->activity. */ - child.setActivity (this); + /* Invalidate the focus. */ + invalidateFocus (); } - /* Make this activity available for future windows to attach - again. */ - + @Override public void - makeAvailable () + destroy () { - window = null; - - for (EmacsWindow iterWindow - : EmacsService.availableChildren) - { - synchronized (iterWindow) - { - if (!iterWindow.isDestroyed ()) - attachChild (iterWindow); - - return; - } - } + finish (); + } - availableActivities.add (this); + @Override + public EmacsWindow + getAttachedWindow () + { + return window; } @Override @@ -109,38 +158,38 @@ public class EmacsActivity extends Activity /* Set it as the content view. */ setContentView (layout); - /* Make the activity available before starting the - service. */ - makeAvailable (); - if (EmacsService.SERVICE == null) /* Start the Emacs service now. */ startService (new Intent (this, EmacsService.class)); + /* Add this activity to the list of available activities. */ + EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this); + super.onCreate (savedInstanceState); } @Override public void - onStop () + onDestroy () { - /* The activity is no longer visible. If there is a window - attached, detach it. */ - - if (window != null) - { - layout.removeView (window.view); + /* The activity will die shortly hereafter. If there is a window + attached, close it now. */ + Log.d (TAG, "onDestroy " + this); + EmacsWindowAttachmentManager.MANAGER.removeWindowConsumer (this); + focusedActivities.remove (this); + invalidateFocus (); + super.onDestroy (); + } - /* Notice that the window is already available too. But do - not call noticeAvailableChild; that might assign it to some - other activity, which behaves badly. */ - EmacsService.availableChildren.add (window); - window = null; - } + @Override + public void + onWindowFocusChanged (boolean isFocused) + { + if (isFocused && !focusedActivities.contains (this)) + focusedActivities.add (this); + else + focusedActivities.remove (this); - /* Finally, remove this activity from the list of available - activities. */ - availableActivities.remove (this); - super.onStop (); + invalidateFocus (); } }; diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index f34d1ecde01..0dd5b2c1fb6 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -32,19 +32,22 @@ public class EmacsCopyArea implements EmacsPaintReq private int src_x, src_y, dest_x, dest_y, width, height; private EmacsDrawable destination, source; private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; + private static Xfermode xorAlu, srcInAlu, overAlu; static { + overAlu = new PorterDuffXfermode (Mode.SRC_OVER); xorAlu = new PorterDuffXfermode (Mode.XOR); srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); }; public - EmacsCopyArea (EmacsDrawable destination, EmacsDrawable source, + EmacsCopyArea (EmacsDrawable source, EmacsDrawable destination, int src_x, int src_y, int width, int height, int dest_x, int dest_y, EmacsGC immutableGC) { + Bitmap bitmap; + this.destination = destination; this.source = source; this.src_x = src_x; @@ -71,6 +74,16 @@ public class EmacsCopyArea implements EmacsPaintReq return destination; } + private void + insetRectBy (Rect rect, int left, int top, int right, + int bottom) + { + rect.left += left; + rect.top += top; + rect.right -= right; + rect.bottom -= bottom; + } + @Override public EmacsGC getGC () @@ -86,16 +99,45 @@ public class EmacsCopyArea implements EmacsPaintReq Bitmap bitmap; Paint maskPaint; Canvas maskCanvas; - Bitmap maskBitmap; - Rect rect, srcRect; + Bitmap srcBitmap, maskBitmap, clipBitmap; + Rect rect, maskRect, srcRect, dstRect, maskDestRect; + boolean needFill; /* TODO implement stippling. */ if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; alu = immutableGC.function; + + /* A copy must be created or drawBitmap could end up overwriting + itself. */ + srcBitmap = source.getBitmap (); + + /* If srcBitmap is out of bounds, then adjust the source rectangle + to be within bounds. Note that tiling on windows with + backgrounds is unimplemented. */ + + if (src_x < 0) + { + width += src_x; + dest_x -= src_x; + src_x = 0; + } + + if (src_y < 0) + { + height += src_y; + dest_y -= src_y; + src_y = 0; + } + + if (src_x + width > srcBitmap.getWidth ()) + width = srcBitmap.getWidth () - src_x; + + if (src_y + height > srcBitmap.getHeight ()) + height = srcBitmap.getHeight () - src_y; + rect = getRect (); - bitmap = source.getBitmap (); if (alu == EmacsGC.GC_COPY) paint.setXfermode (null); @@ -103,29 +145,76 @@ public class EmacsCopyArea implements EmacsPaintReq paint.setXfermode (xorAlu); if (immutableGC.clip_mask == null) - canvas.drawBitmap (bitmap, new Rect (src_x, src_y, - src_x + width, - src_y + height), - rect, paint); + { + bitmap = Bitmap.createBitmap (srcBitmap, + src_x, src_y, width, + height); + canvas.drawBitmap (bitmap, null, rect, paint); + } else { - maskPaint = new Paint (); - srcRect = new Rect (0, 0, rect.width (), - rect.height ()); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) + /* Drawing with a clip mask involves calculating the + intersection of the clip mask with the dst rect, and + extrapolating the corresponding part of the src rect. */ + clipBitmap = immutableGC.clip_mask.bitmap; + dstRect = new Rect (dest_x, dest_y, + dest_x + width, + dest_y + height); + maskRect = new Rect (immutableGC.clip_x_origin, + immutableGC.clip_y_origin, + (immutableGC.clip_x_origin + + clipBitmap.getWidth ()), + (immutableGC.clip_y_origin + + clipBitmap.getHeight ())); + clipBitmap = immutableGC.clip_mask.bitmap; + + if (!maskRect.setIntersect (dstRect, maskRect)) + /* There is no intersection between the clip mask and the + dest rect. */ return; - maskPaint.setXfermode (srcInAlu); + /* Now figure out which part of the source corresponds to + maskRect and return it relative to srcBitmap. */ + srcRect = new Rect (src_x, src_y, src_x + width, + src_y + height); + insetRectBy (srcRect, maskRect.left - dstRect.left, + maskRect.top - dstRect.top, + maskRect.right - dstRect.right, + maskRect.bottom - dstRect.bottom); + + /* Finally, create a temporary bitmap that is the size of + maskRect. */ + + maskBitmap + = Bitmap.createBitmap (maskRect.width (), maskRect.height (), + Bitmap.Config.ARGB_8888); + + /* Draw the mask onto the maskBitmap. */ maskCanvas = new Canvas (maskBitmap); - maskCanvas.drawBitmap (bitmap, new Rect (src_x, src_y, - src_x + width, - src_y + height), - srcRect, maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + maskRect.offset (-immutableGC.clip_x_origin, + -immutableGC.clip_y_origin); + maskCanvas.drawBitmap (immutableGC.clip_mask.bitmap, + maskRect, new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + paint); + maskRect.offset (immutableGC.clip_x_origin, + immutableGC.clip_y_origin); + + /* Set the transfer mode to SRC_IN to preserve only the parts + of the source that overlap with the mask. */ + maskPaint = new Paint (); + maskPaint.setXfermode (srcInAlu); + + /* Draw the source. */ + maskDestRect = new Rect (0, 0, srcRect.width (), + srcRect.height ()); + maskCanvas.drawBitmap (srcBitmap, srcRect, maskDestRect, + maskPaint); + + /* Finally, draw the mask bitmap to the destination. */ + paint.setXfermode (overAlu); + canvas.drawBitmap (maskBitmap, null, maskRect, paint); } } } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 462bf7c85b5..e3f28227146 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -57,7 +57,10 @@ public class EmacsDrawRectangle implements EmacsPaintReq public Rect getRect () { - return new Rect (x, y, x + width, y + height); + /* Canvas.drawRect actually behaves exactly like PolyRectangle wrt + to where the lines are placed, so extend the width and height + by 1 in the damage rectangle. */ + return new Rect (x, y, x + width + 1, y + height + 1); } @Override @@ -89,9 +92,10 @@ public class EmacsDrawRectangle implements EmacsPaintReq return; alu = immutableGC.function; - rect = getRect (); + rect = new Rect (x, y, x + width, y + height); paint.setStyle (Paint.Style.STROKE); + paint.setStrokeWidth (1); if (alu == EmacsGC.GC_COPY) paint.setXfermode (null); diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index f419e71059d..9f40aa04c44 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -21,6 +21,8 @@ package org.gnu.emacs; import java.util.List; +import android.os.Build; + public abstract class EmacsFontDriver { /* Font weights. */ @@ -75,6 +77,7 @@ public abstract class EmacsFontDriver public Integer size; public Integer spacing; public Integer avgwidth; + public Integer dpi; @Override public String @@ -88,7 +91,8 @@ public abstract class EmacsFontDriver + " weight: " + weight + " slant: " + slant + " spacing: " + spacing - + " avgwidth: " + avgwidth); + + " avgwidth: " + avgwidth + + " dpi: " + dpi); } }; @@ -99,6 +103,17 @@ public abstract class EmacsFontDriver public short width; public short ascent; public short descent; + + @Override + public String + toString () + { + return ("lbearing " + lbearing + + " rbearing " + rbearing + + " width " + width + + " ascent " + ascent + + " descent " + descent); + } } public class FontEntity extends FontSpec @@ -139,12 +154,19 @@ public abstract class EmacsFontDriver public abstract FontObject openFont (FontEntity fontEntity, int pixelSize); public abstract int hasChar (FontSpec font, char charCode); public abstract void textExtents (FontObject font, int code[], - FontMetrics fontMetrics[]); + FontMetrics fontMetrics); public abstract int encodeChar (FontObject fontObject, char charCode); + public abstract int draw (FontObject fontObject, EmacsGC gc, + EmacsDrawable drawable, int[] chars, + int x, int y, int backgroundWidth, + boolean withBackground); public static EmacsFontDriver createFontDriver () { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) + return new EmacsSdk23FontDriver (); + return new EmacsSdk7FontDriver (); } }; diff --git a/java/org/gnu/emacs/EmacsMultitaskActivity.java b/java/org/gnu/emacs/EmacsMultitaskActivity.java new file mode 100644 index 00000000000..dbdc99a3559 --- /dev/null +++ b/java/org/gnu/emacs/EmacsMultitaskActivity.java @@ -0,0 +1,25 @@ +/* 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; + +public class EmacsMultitaskActivity extends EmacsActivity +{ + +} diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 6550e6fa2a1..c80339031a8 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -38,10 +38,15 @@ public class EmacsNative libDir must be the package's data storage location for native libraries. It is used as PATH. + pixelDensityX and pixelDensityY are the DPI values that will be + used by Emacs. + emacsService must be the emacsService singleton. */ public static native void setEmacsParams (AssetManager assetManager, String filesDir, String libDir, + float pixelDensityX, + float pixelDensityY, EmacsService emacsService); /* Initialize Emacs with the argument array ARGV. Each argument @@ -59,11 +64,20 @@ public class EmacsNative /* Send an ANDROID_KEY_PRESS event. */ public static native void sendKeyPress (short window, long time, int state, - int keyCode); + int keyCode, int unicodeChar); /* Send an ANDROID_KEY_RELEASE event. */ public static native void sendKeyRelease (short window, long time, int state, - int keyRelease); + int keyCode, int unicodeChar); + + /* Send an ANDROID_FOCUS_IN event. */ + public static native void sendFocusIn (short window, long time); + + /* Send an ANDROID_FOCUS_OUT event. */ + public static native void sendFocusOut (short window, long time); + + /* Send an ANDROID_WINDOW_ACTION event. */ + public static native void sendWindowAction (short window, int action); static { diff --git a/java/org/gnu/emacs/EmacsPaintQueue.java b/java/org/gnu/emacs/EmacsPaintQueue.java index 5af5868d3b9..f4840dbf5ae 100644 --- a/java/org/gnu/emacs/EmacsPaintQueue.java +++ b/java/org/gnu/emacs/EmacsPaintQueue.java @@ -47,7 +47,7 @@ public class EmacsPaintQueue { EmacsDrawable drawable, last; Canvas canvas; - EmacsGC gc, lastGC; + EmacsGC gc; int i; Paint paint; Rect rect, offsetRect, copyRect; @@ -60,45 +60,34 @@ public class EmacsPaintQueue for (EmacsPaintReq req : paintOperations) { drawable = req.getDrawable (); - - synchronized (req) - { - /* Ignore graphics requests for drawables that have been - destroyed. */ - if (drawable.isDestroyed ()) - continue; - } - canvas = drawable.lockCanvas (); if (canvas == null) /* No canvas is currently available. */ continue; - lastGC = gc; gc = req.getGC (); rect = req.getRect (); + drawable.damageRect (rect); + if (gc.clip_rects == null) { /* No clipping is applied. Just draw and continue. */ - canvas.save (); req.paintTo (canvas, paint, gc); - canvas.restore (); - drawable.damageRect (rect); continue; } if (gc.clip_rects != null && gc.clip_rects.length > 0) { - canvas.save (); - if (gc.clip_rects.length == 1) { /* There is only a single clip rect, which is simple enough. */ + canvas.save (); canvas.clipRect (gc.clip_rects[0]); req.paintTo (canvas, paint, gc); + canvas.restore (); } else { @@ -122,9 +111,6 @@ public class EmacsPaintQueue } } } - - drawable.damageRect (rect); - canvas.restore (); } } } diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index ccd5f1e0043..897902c5b57 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -69,6 +69,35 @@ public class EmacsPixmap extends EmacsHandleObject this.depth = depth; } + public + EmacsPixmap (short handle, int width, int height, int depth) + { + super (handle); + + if (depth != 1 && depth != 24) + throw new IllegalArgumentException ("Invalid depth specified" + + " for pixmap: " + depth); + + switch (depth) + { + case 1: + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ALPHA_8, + false); + break; + + case 24: + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ARGB_8888, + false); + break; + } + + this.width = width; + this.height = height; + this.depth = depth; + } + @Override public Canvas lockCanvas () diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java b/java/org/gnu/emacs/EmacsSdk23FontDriver.java new file mode 100644 index 00000000000..34e2b1803a2 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java @@ -0,0 +1,43 @@ +/* Font backend 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.graphics.Paint; + +public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver +{ + @Override + public int + hasChar (FontSpec font, char charCode) + { + Sdk7FontObject fontObject; + Paint paint; + + if (font instanceof Sdk7FontObject) + { + fontObject = (Sdk7FontObject) font; + paint = fontObject.typeface.typefacePaint; + } + else + paint = ((Sdk7FontEntity) font).typeface.typefacePaint; + + return paint.hasGlyph (String.valueOf (charCode)) ? 1 : 0; + } +}; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 5a8cdbfc75b..437f38e62db 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -28,16 +28,19 @@ import java.util.List; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.Canvas; import android.util.Log; +import android.os.Build; + public class EmacsSdk7FontDriver extends EmacsFontDriver { private static final String TOFU_STRING = "\uDB3F\uDFFD"; private static final String EM_STRING = "m"; private static final String TAG = "EmacsSdk7FontDriver"; - private class Sdk7Typeface + protected class Sdk7Typeface { /* The typeface and paint. */ public Typeface typeface; @@ -57,7 +60,10 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver width = UNSPECIFIED; spacing = PROPORTIONAL; + this.typeface = typeface; + typefacePaint = new Paint (); + typefacePaint.setAntiAlias (true); typefacePaint.setTypeface (typeface); /* For the calls to measureText below. */ @@ -160,7 +166,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; - private class Sdk7FontEntity extends FontEntity + protected class Sdk7FontEntity extends FontEntity { /* The typeface. */ public Sdk7Typeface typeface; @@ -177,19 +183,17 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver slant = typeface.slant; spacing = typeface.spacing; width = typeface.width; + dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f); this.typeface = typeface; } }; - private class Sdk7FontObject extends FontObject + protected class Sdk7FontObject extends FontObject { /* The typeface. */ public Sdk7Typeface typeface; - /* The text size. */ - public int pixelSize; - public Sdk7FontObject (Sdk7Typeface typeface, int pixelSize) { @@ -205,6 +209,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver slant = typeface.slant; spacing = typeface.spacing; width = typeface.width; + dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f); /* Compute the ascent and descent. */ typeface.typefacePaint.setTextSize (pixelSize); @@ -238,6 +243,93 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; + private class Sdk7DrawString implements EmacsPaintReq + { + private boolean drawBackground; + private Sdk7FontObject fontObject; + private char[] chars; + private EmacsGC immutableGC; + private EmacsDrawable drawable; + private Rect rect; + private int originX, originY; + + public + Sdk7DrawString (Sdk7FontObject fontObject, char[] chars, + EmacsGC immutableGC, EmacsDrawable drawable, + boolean drawBackground, Rect rect, + int originX, int originY) + { + this.fontObject = fontObject; + this.chars = chars; + this.immutableGC = immutableGC; + this.drawable = drawable; + this.drawBackground = drawBackground; + this.rect = rect; + this.originX = originX; + this.originY = originY; + } + + @Override + public EmacsDrawable + getDrawable () + { + return drawable; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int scratch; + + paint.setStyle (Paint.Style.FILL); + + if (drawBackground) + { + paint.setColor (immutableGC.background | 0xff000000); + canvas.drawRect (rect, paint); + } + + paint.setTextSize (fontObject.pixelSize); + paint.setColor (immutableGC.foreground | 0xff000000); + paint.setTypeface (fontObject.typeface.typeface); + paint.setAntiAlias (true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + /* Disable hinting as that leads to displayed text not + matching the computed metrics. */ + paint.setHinting (Paint.HINTING_OFF); + + canvas.drawText (chars, 0, chars.length, originX, originY, paint); + paint.setAntiAlias (false); + } + + @Override + public Rect + getRect () + { + Rect rect; + + rect = new Rect (); + + fontObject.typeface.typefacePaint.setTextSize (fontObject.pixelSize); + fontObject.typeface.typefacePaint.getTextBounds (chars, 0, chars.length, + rect); + + /* Add the background rect to the damage as well. */ + rect.union (this.rect); + + return rect; + } + }; + private String[] fontFamilyList; private Sdk7Typeface[] typefaceList; private Sdk7Typeface fallbackTypeface; @@ -252,7 +344,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver systemFontsDirectory = new File ("/system/fonts"); fontFamilyList = systemFontsDirectory.list (); - typefaceList = new Sdk7Typeface[fontFamilyList.length]; + typefaceList = new Sdk7Typeface[fontFamilyList.length + 3]; /* It would be nice to avoid opening each and every font upon startup. But that doesn't seem to be possible on @@ -267,8 +359,18 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver typeface); } + /* Initialize the default monospace and serif typefaces. */ fallbackTypeface = new Sdk7Typeface ("monospace", Typeface.MONOSPACE); + typefaceList[fontFamilyList.length] = fallbackTypeface; + + fallbackTypeface = new Sdk7Typeface ("Monospace", + Typeface.MONOSPACE); + typefaceList[fontFamilyList.length + 1] = fallbackTypeface; + + fallbackTypeface = new Sdk7Typeface ("Sans Serif", + Typeface.DEFAULT); + typefaceList[fontFamilyList.length + 2] = fallbackTypeface; } private boolean @@ -278,11 +380,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver && !fontSpec.family.equals (typeface.familyName)) return false; - - if (fontSpec.adstyle != null - && !fontSpec.adstyle.isEmpty ()) - /* return false; */; - if (fontSpec.slant != null && !fontSpec.weight.equals (typeface.weight)) return false; @@ -393,7 +490,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (), rect1); paint.getTextBounds ("" + charCode, 0, 1, rect2); - return rect1.equals (rect2) ? 1 : 0; + return rect1.equals (rect2) ? 0 : 1; } private void @@ -434,21 +531,47 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver @Override public void - textExtents (FontObject font, int code[], FontMetrics fontMetrics[]) + textExtents (FontObject font, int code[], FontMetrics fontMetrics) { int i; Paint paintCache; Rect boundsCache; Sdk7FontObject fontObject; + char[] text; + float width; fontObject = (Sdk7FontObject) font; paintCache = fontObject.typeface.typefacePaint; paintCache.setTextSize (fontObject.pixelSize); boundsCache = new Rect (); - for (i = 0; i < code.length; ++i) - textExtents1 ((Sdk7FontObject) font, code[i], fontMetrics[i], + if (code.length == 0) + { + fontMetrics.lbearing = 0; + fontMetrics.rbearing = 0; + fontMetrics.ascent = 0; + fontMetrics.descent = 0; + fontMetrics.width = 0; + } + else if (code.length == 1) + textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics, paintCache, boundsCache); + else + { + text = new char[code.length]; + + for (i = 0; i < code.length; ++i) + text[i] = (char) code[i]; + + paintCache.getTextBounds (text, 0, 1, boundsCache); + width = paintCache.measureText (text, 0, code.length); + + fontMetrics.lbearing = (short) boundsCache.left; + fontMetrics.rbearing = (short) boundsCache.right; + fontMetrics.ascent = (short) -boundsCache.top; + fontMetrics.descent = (short) boundsCache.bottom; + fontMetrics.width = (short) Math.round (width); + } } @Override @@ -457,4 +580,37 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver { return charCode; } + + @Override + public int + draw (FontObject fontObject, EmacsGC gc, EmacsDrawable drawable, + int[] chars, int x, int y, int backgroundWidth, + boolean withBackground) + { + Rect backgroundRect; + Sdk7FontObject sdk7FontObject; + Sdk7DrawString op; + char[] charsArray; + int i; + + sdk7FontObject = (Sdk7FontObject) fontObject; + charsArray = new char[chars.length]; + + for (i = 0; i < chars.length; ++i) + charsArray[i] = (char) chars[i]; + + backgroundRect = new Rect (); + backgroundRect.top = y - sdk7FontObject.ascent; + backgroundRect.left = x; + backgroundRect.right = x + backgroundWidth; + backgroundRect.bottom = y + sdk7FontObject.descent; + + op = new Sdk7DrawString (sdk7FontObject, charsArray, + gc.immutableGC (), drawable, + withBackground, + backgroundRect, x, y); + + EmacsService.SERVICE.appendPaintOperation (op); + return 1; + } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 311226e6f7e..41a45b0bd85 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -28,6 +28,8 @@ import android.graphics.Canvas; import android.graphics.Bitmap; import android.graphics.Point; +import android.view.View; + import android.annotation.TargetApi; import android.app.Service; import android.content.Context; @@ -37,7 +39,9 @@ import android.os.Build; import android.os.Looper; import android.os.IBinder; import android.os.Handler; + import android.util.Log; +import android.util.DisplayMetrics; class Holder { @@ -57,14 +61,8 @@ public class EmacsService extends Service private Handler handler; private EmacsPaintQueue paintQueue; - /* List of all EmacsWindows that are available to attach to an - activity. */ - public static List availableChildren; - - static - { - availableChildren = new ArrayList (); - }; + /* Display metrics used by font backends. */ + public DisplayMetrics metrics; @Override public int @@ -88,7 +86,7 @@ public class EmacsService extends Service Context context; context = getApplicationContext (); - apiLevel = android.os.Build.VERSION.SDK_INT; + apiLevel = Build.VERSION.SDK_INT; if (apiLevel >= Build.VERSION_CODES.GINGERBREAD) return context.getApplicationInfo().nativeLibraryDir; @@ -105,11 +103,16 @@ public class EmacsService extends Service AssetManager manager; Context app_context; String filesDir, libDir; + double pixelDensityX; + double pixelDensityY; SERVICE = this; handler = new Handler (Looper.getMainLooper ()); manager = getAssets (); app_context = getApplicationContext (); + metrics = getResources ().getDisplayMetrics (); + pixelDensityX = metrics.xdpi; + pixelDensityY = metrics.ydpi; try { @@ -122,6 +125,8 @@ public class EmacsService extends Service + " and libDir = " + libDir); EmacsNative.setEmacsParams (manager, filesDir, libDir, + (float) pixelDensityX, + (float) pixelDensityY, this); /* Start the thread that runs Emacs. */ @@ -147,7 +152,8 @@ public class EmacsService extends Service } EmacsView - getEmacsView (final EmacsWindow window) + getEmacsView (final EmacsWindow window, final int visibility, + final boolean isFocusedByDefault) { Runnable runnable; final Holder view; @@ -161,6 +167,8 @@ public class EmacsService extends Service synchronized (this) { view.thing = new EmacsView (window); + view.thing.setVisibility (visibility); + view.thing.setFocusedByDefault (isFocusedByDefault); notify (); } } @@ -183,48 +191,6 @@ public class EmacsService extends Service return view.thing; } - /* Notice that a child of the root window named WINDOW is now - available for attachment to a specific activity. */ - - public void - noticeAvailableChild (final EmacsWindow window) - { - Log.d (TAG, "A new child is available: " + window); - - handler.post (new Runnable () { - public void - run () - { - for (EmacsActivity activity - : EmacsActivity.availableActivities) - { - /* TODO: check if the activity matches. */ - activity.attachChild (window); - break; - } - - /* Nope, wait for an activity to become available. */ - availableChildren.add (window); - } - }); - } - - /* Notice that a child of the root window named WINDOW has been - destroyed. */ - - public void - noticeChildDestroyed (final EmacsWindow child) - { - handler.post (new Runnable () { - @Override - public void - run () - { - availableChildren.remove (child); - } - }); - } - /* X drawing operations. These are quite primitive operations. The drawing queue is kept on the Emacs thread, but is periodically flushed to the application thread, upon buffers swaps and once it @@ -311,11 +277,6 @@ public class EmacsService extends Service ensurePaintQueue (); - if (gc.clip_rects != null && gc.clip_rects.length >= 1) - android.util.Log.d ("drawRectangle", - gc.clip_rects[0].toString () - + " " + gc.toString ()); - req = new EmacsDrawRectangle (drawable, x, y, width, height, gc.immutableGC ()); @@ -381,4 +342,12 @@ public class EmacsService extends Service { window.clearArea (x, y, width, height); } + + public void + appendPaintOperation (EmacsPaintReq op) + { + ensurePaintQueue (); + paintQueue.appendPaintOperation (op); + checkFlush (); + } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 194f6ad37a3..b8b828e4820 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -22,6 +22,8 @@ package org.gnu.emacs; import android.view.SurfaceView; import android.view.SurfaceHolder; +import android.os.Build; + import android.graphics.Canvas; import android.graphics.Rect; @@ -40,7 +42,9 @@ public class EmacsSurfaceView extends SurfaceView surfaceChanged (SurfaceHolder holder, int format, int width, int height) { - + /* Force a buffer swap now to get the contents of the Emacs + view on screen. */ + view.swapBuffers (true); } @Override @@ -51,7 +55,7 @@ public class EmacsSurfaceView extends SurfaceView /* Force a buffer swap now to get the contents of the Emacs view on screen. */ - view.swapBuffers (); + view.swapBuffers (true); } @Override @@ -72,12 +76,37 @@ public class EmacsSurfaceView extends SurfaceView public Canvas lockCanvas (Rect damage) { - return getHolder ().lockCanvas (damage); + SurfaceHolder holder; + + holder = getHolder (); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + damage.setEmpty (); + return holder.lockHardwareCanvas (); + } + + return holder.lockCanvas (damage); + } + + /* This method is only used during debugging when it seems damage + isn't working correctly. */ + + public Canvas + lockCanvas () + { + SurfaceHolder holder; + + holder = getHolder (); + return holder.lockCanvas (); } public void unlockCanvasAndPost (Canvas canvas) { - getHolder ().unlockCanvasAndPost (canvas); + SurfaceHolder holder; + + holder = getHolder (); + holder.unlockCanvasAndPost (canvas); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 237946d6366..7b48eaf0aa6 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -19,17 +19,20 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import android.content.res.ColorStateList; + import android.view.View; import android.view.KeyEvent; import android.view.ViewGroup; + import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Region; import android.graphics.Paint; -import android.util.Log; import android.os.Build; +import android.util.Log; /* This is an Android view which has a back and front buffer. When swapBuffers is called, the back buffer is swapped to the front @@ -70,10 +73,11 @@ public class EmacsView extends ViewGroup this.damageRegion = new Region (); this.paint = new Paint (); + setFocusable (true); + setFocusableInTouchMode (true); + /* Create the surface view. */ this.surfaceView = new EmacsSurfaceView (this); - - setFocusable (FOCUSABLE); addView (this.surfaceView); } @@ -162,7 +166,7 @@ public class EmacsView extends ViewGroup } public void - swapBuffers () + swapBuffers (boolean force) { Bitmap back; Canvas canvas; @@ -185,14 +189,25 @@ public class EmacsView extends ViewGroup if (canvas == null) return; - /* Copy from the back buffer to the canvas. */ - canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + /* Copy from the back buffer to the canvas. If damageRect was + made empty, then draw the entire back buffer. */ + + if (damageRect.isEmpty ()) + canvas.drawBitmap (bitmap, 0f, 0f, paint); + else + canvas.drawBitmap (bitmap, damageRect, damageRect, paint); /* Unlock the canvas and clear the damage. */ surfaceView.unlockCanvasAndPost (canvas); damageRegion.setEmpty (); } + public void + swapBuffers () + { + swapBuffers (false); + } + @Override public boolean onKeyDown (int keyCode, KeyEvent event) @@ -201,6 +216,14 @@ public class EmacsView extends ViewGroup return true; } + @Override + public boolean + onKeyMultiple (int keyCode, int repeatCount, KeyEvent event) + { + window.onKeyDown (keyCode, event); + return true; + } + @Override public boolean onKeyUp (int keyCode, KeyEvent event) @@ -208,4 +231,14 @@ public class EmacsView extends ViewGroup window.onKeyUp (keyCode, event); return true; } + + @Override + public void + onFocusChanged (boolean gainFocus, int direction, + Rect previouslyFocusedRect) + { + window.onFocusChanged (gainFocus); + super.onFocusChanged (gainFocus, direction, + previouslyFocusedRect); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 28db04a261d..26e788a20a8 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -32,6 +32,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.KeyEvent; +import android.content.Intent; + /* This defines a window, which is a handle. Windows represent a rectangular subset of the screen with their own contents. @@ -57,10 +59,10 @@ public class EmacsWindow extends EmacsHandleObject /* List of all children in stacking order. This must be kept consistent! */ - private ArrayList children; + public ArrayList children; - /* The EmacsActivity currently attached, if it exists. */ - private EmacsActivity attached; + /* The window consumer currently attached, if it exists. */ + private EmacsWindowAttachmentManager.WindowConsumer attached; /* The window background scratch GC. foreground is always the window background. */ @@ -74,35 +76,44 @@ public class EmacsWindow extends EmacsHandleObject rect = new Rect (x, y, x + width, y + height); - /* Create the view from the context's UI thread. */ - view = EmacsService.SERVICE.getEmacsView (this); + /* Create the view from the context's UI thread. The window is + unmapped, so the view is GONE. */ + view = EmacsService.SERVICE.getEmacsView (this, View.GONE, + parent == null); this.parent = parent; - children = new ArrayList (); - /* The window is unmapped by default. */ - view.setVisibility (View.GONE); + /* Create the list of children. */ + children = new ArrayList (); - /* If parent is the root window, notice that there are new - children available for interested activites to pick up. */ - if (parent == null) - EmacsService.SERVICE.noticeAvailableChild (this); - else + if (parent != null) { - /* Otherwise, directly add this window as a child of that - window's view. */ - synchronized (parent) + parent.children.add (this); + parent.view.post (new Runnable () { + @Override + public void + run () + { + parent.view.addView (view); + } + }); + } + else + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () { - parent.children.add (this); - parent.view.post (new Runnable () { - @Override - public void - run () - { - parent.view.addView (view); - } - }); + EmacsWindowAttachmentManager manager; + + manager = EmacsWindowAttachmentManager.MANAGER; + + /* If parent is the root window, notice that there are new + children available for interested activites to pick + up. */ + + manager.registerWindow (EmacsWindow.this); } - } + }); scratchGC = new EmacsGC ((short) 0); } @@ -129,28 +140,35 @@ public class EmacsWindow extends EmacsHandleObject public void destroyHandle () throws IllegalStateException { - synchronized (this) - { - if (!children.isEmpty ()) - throw new IllegalStateException ("Trying to destroy window with " - + "children!"); - } + if (parent != null) + parent.children.remove (this); + + EmacsActivity.invalidateFocus (); - /* Notice that the child has been destroyed. */ - EmacsService.SERVICE.noticeChildDestroyed (this); + if (!children.isEmpty ()) + throw new IllegalStateException ("Trying to destroy window with " + + "children!"); /* Remove the view from its parent and make it invisible. */ view.post (new Runnable () { public void run () { + View parent; + EmacsWindowAttachmentManager manager; + + if (EmacsActivity.focusedWindow == EmacsWindow.this) + EmacsActivity.focusedWindow = null; + + manager = EmacsWindowAttachmentManager.MANAGER; view.setVisibility (View.GONE); - if (view.getParent () != null) - ((ViewGroup) view.getParent ()).removeView (view); + parent = (View) view.getParent (); - if (attached != null) - attached.makeAvailable (); + if (parent != null && attached == null) + ((ViewGroup) parent).removeView (view); + + manager.detachWindow (EmacsWindow.this); } }); @@ -158,12 +176,15 @@ public class EmacsWindow extends EmacsHandleObject } public void - setActivity (EmacsActivity activity) + setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer) { - synchronized (this) - { - activity = activity; - } + attached = consumer; + } + + public EmacsWindowAttachmentManager.WindowConsumer + getAttachedConsumer () + { + return attached; } public void @@ -233,7 +254,10 @@ public class EmacsWindow extends EmacsHandleObject public void run () { + view.setVisibility (View.VISIBLE); + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); } }); } @@ -319,18 +343,47 @@ public class EmacsWindow extends EmacsHandleObject public void onKeyDown (int keyCode, KeyEvent event) { + int state; + + state = event.getModifiers (); + state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + EmacsNative.sendKeyPress (this.handle, event.getEventTime (), event.getModifiers (), - keyCode); + keyCode, + /* Ignore meta-state understood by Emacs + for now, or Ctrl+C will not be + recognized as an ASCII key press + event. */ + event.getUnicodeChar (state)); } public void onKeyUp (int keyCode, KeyEvent event) { + int state; + + state = event.getModifiers (); + state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + EmacsNative.sendKeyRelease (this.handle, event.getEventTime (), event.getModifiers (), - keyCode); + keyCode, + event.getUnicodeChar (state)); + } + + public void + onFocusChanged (boolean gainFocus) + { + EmacsActivity.invalidateFocus (); + } + + public void + onActivityDetached () + { + /* Destroy the associated frame when the activity is detached. */ + EmacsNative.sendWindowAction (this.handle, 0); } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java new file mode 100644 index 00000000000..34be2ab8789 --- /dev/null +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -0,0 +1,166 @@ +/* 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.util.LinkedList; +import java.util.List; + +import android.content.Intent; +import android.util.Log; + +/* Code to paper over the differences in lifecycles between + "activities" and windows. There are four interfaces to an instance + of this class: + + registerWindowConsumer (WindowConsumer) + registerWindow (EmacsWindow) + removeWindowConsumer (WindowConsumer) + removeWindow (EmacsWindow) + + A WindowConsumer is expected to allow an EmacsWindow to be attached + to it, and be created or destroyed. + + Every time a window is created, registerWindow checks the list of + window consumers. If a consumer exists and does not currently have + a window of its own attached, it gets the new window. Otherwise, + the window attachment manager starts a new consumer. + + Every time a consumer is registered, registerWindowConsumer checks + the list of available windows. If a window exists and is not + currently attached to a consumer, then the consumer gets it. + + Finally, every time a window is removed, the consumer is + destroyed. */ + +public class EmacsWindowAttachmentManager +{ + public static EmacsWindowAttachmentManager MANAGER; + private final static String TAG = "EmacsWindowAttachmentManager"; + + static + { + MANAGER = new EmacsWindowAttachmentManager (); + }; + + public interface WindowConsumer + { + public void attachWindow (EmacsWindow window); + public EmacsWindow getAttachedWindow (); + public void detachWindow (); + public void destroy (); + }; + + private List consumers; + private List windows; + + public + EmacsWindowAttachmentManager () + { + consumers = new LinkedList (); + windows = new LinkedList (); + } + + public void + registerWindowConsumer (WindowConsumer consumer) + { + Log.d (TAG, "registerWindowConsumer " + consumer); + + consumers.add (consumer); + + for (EmacsWindow window : windows) + { + if (window.getAttachedConsumer () == null) + { + Log.d (TAG, "registerWindowConsumer: attaching " + window); + consumer.attachWindow (window); + return; + } + } + + Log.d (TAG, "registerWindowConsumer: sendWindowAction 0, 0"); + EmacsNative.sendWindowAction ((short) 0, 0); + } + + public void + registerWindow (EmacsWindow window) + { + Intent intent; + + Log.d (TAG, "registerWindow " + window); + windows.add (window); + + for (WindowConsumer consumer : consumers) + { + if (consumer.getAttachedWindow () == null) + { + Log.d (TAG, "registerWindow: attaching " + consumer); + consumer.attachWindow (window); + return; + } + } + + intent = new Intent (EmacsService.SERVICE, + EmacsMultitaskActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + EmacsService.SERVICE.startActivity (intent); + Log.d (TAG, "registerWindow: startActivity"); + } + + public void + removeWindowConsumer (WindowConsumer consumer) + { + EmacsWindow window; + + Log.d (TAG, "removeWindowConsumer " + consumer); + + window = consumer.getAttachedWindow (); + + if (window != null) + { + Log.d (TAG, "removeWindowConsumer: detaching " + window); + + consumer.detachWindow (); + window.onActivityDetached (); + } + + Log.d (TAG, "removeWindowConsumer: removing " + consumer); + consumers.remove (consumer); + } + + public void + detachWindow (EmacsWindow window) + { + WindowConsumer consumer; + + Log.d (TAG, "detachWindow " + window); + + if (window.getAttachedConsumer () != null) + { + consumer = window.getAttachedConsumer (); + + Log.d (TAG, "detachWindow: removing" + consumer); + + consumers.remove (consumer); + consumer.destroy (); + } + } +}; -- cgit v1.2.1 From 695e26079eb60d10ffe25bb8ae91ebc6131fb27d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 8 Jan 2023 15:39:02 +0800 Subject: Update Java part of Android port * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea, perform) (paintTo): * java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine): * java/org/gnu/emacs/EmacsDrawPoint.java (EmacsDrawPoint): * java/org/gnu/emacs/EmacsDrawRectangle.java (EmacsDrawRectangle) (paintTo): * 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/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): * java/org/gnu/emacs/EmacsSdk23FontDriver.java (EmacsSdk23FontDriver): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver, textExtents1, textExtents, draw): * java/org/gnu/emacs/EmacsService.java (EmacsService, copyArea): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout) (onFocusChanged): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, run) (resizeWindow, lockCanvas, getBitmap, onKeyDown, onKeyUp) (onActivityDetached): Move rendering to main thread. Make drawing operations completely static. --- java/org/gnu/emacs/EmacsCopyArea.java | 131 +++++++++------------- java/org/gnu/emacs/EmacsDrawLine.java | 120 +++++--------------- java/org/gnu/emacs/EmacsDrawPoint.java | 11 +- java/org/gnu/emacs/EmacsDrawRectangle.java | 160 +++++++++++++-------------- java/org/gnu/emacs/EmacsDrawable.java | 1 - java/org/gnu/emacs/EmacsFillPolygon.java | 128 ++++++--------------- java/org/gnu/emacs/EmacsFillRectangle.java | 152 ++++++++++++------------- java/org/gnu/emacs/EmacsFontDriver.java | 2 +- java/org/gnu/emacs/EmacsGC.java | 77 ++++++------- java/org/gnu/emacs/EmacsNative.java | 22 ++++ java/org/gnu/emacs/EmacsPixmap.java | 9 +- java/org/gnu/emacs/EmacsSdk23FontDriver.java | 71 ++++++++++++ java/org/gnu/emacs/EmacsSdk7FontDriver.java | 139 +++++++---------------- java/org/gnu/emacs/EmacsService.java | 122 ++------------------ java/org/gnu/emacs/EmacsSurfaceView.java | 22 ++-- java/org/gnu/emacs/EmacsView.java | 133 ++++++++++++++++------ java/org/gnu/emacs/EmacsWindow.java | 141 +++++++++++++++++++---- 17 files changed, 685 insertions(+), 756 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 0dd5b2c1fb6..00e817bb97d 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -27,54 +27,16 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsCopyArea implements EmacsPaintReq +public class EmacsCopyArea { - private int src_x, src_y, dest_x, dest_y, width, height; - private EmacsDrawable destination, source; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu, overAlu; + private static Xfermode overAlu; static { overAlu = new PorterDuffXfermode (Mode.SRC_OVER); - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); }; - public - EmacsCopyArea (EmacsDrawable source, EmacsDrawable destination, - int src_x, int src_y, int width, int height, - int dest_x, int dest_y, EmacsGC immutableGC) - { - Bitmap bitmap; - - this.destination = destination; - this.source = source; - this.src_x = src_x; - this.src_y = src_y; - this.width = width; - this.height = height; - this.dest_x = dest_x; - this.dest_y = dest_y; - this.immutableGC = immutableGC; - } - - @Override - public Rect - getRect () - { - return new Rect (dest_x, dest_y, dest_x + width, - dest_y + height); - } - - @Override - public EmacsDrawable - getDrawable () - { - return destination; - } - - private void + private static void insetRectBy (Rect rect, int left, int top, int right, int bottom) { @@ -84,30 +46,38 @@ public class EmacsCopyArea implements EmacsPaintReq rect.bottom -= bottom; } - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + public static void + perform (EmacsDrawable source, EmacsGC gc, + EmacsDrawable destination, + int src_x, int src_y, int width, int height, + int dest_x, int dest_y) { - int alu; + int i; Bitmap bitmap; - Paint maskPaint; - Canvas maskCanvas; + Paint maskPaint, paint; + Canvas maskCanvas, canvas; Bitmap srcBitmap, maskBitmap, clipBitmap; Rect rect, maskRect, srcRect, dstRect, maskDestRect; boolean needFill; /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - alu = immutableGC.function; + paint = gc.gcPaint; + + canvas = destination.lockCanvas (); + + if (canvas == null) + return; + + canvas.save (); + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } /* A copy must be created or drawBitmap could end up overwriting itself. */ @@ -137,14 +107,10 @@ public class EmacsCopyArea implements EmacsPaintReq if (src_y + height > srcBitmap.getHeight ()) height = srcBitmap.getHeight () - src_y; - rect = getRect (); - - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + rect = new Rect (dest_x, dest_y, dest_x + width, + dest_y + height); - if (immutableGC.clip_mask == null) + if (gc.clip_mask == null) { bitmap = Bitmap.createBitmap (srcBitmap, src_x, src_y, width, @@ -156,17 +122,17 @@ public class EmacsCopyArea implements EmacsPaintReq /* Drawing with a clip mask involves calculating the intersection of the clip mask with the dst rect, and extrapolating the corresponding part of the src rect. */ - clipBitmap = immutableGC.clip_mask.bitmap; + clipBitmap = gc.clip_mask.bitmap; dstRect = new Rect (dest_x, dest_y, dest_x + width, dest_y + height); - maskRect = new Rect (immutableGC.clip_x_origin, - immutableGC.clip_y_origin, - (immutableGC.clip_x_origin + maskRect = new Rect (gc.clip_x_origin, + gc.clip_y_origin, + (gc.clip_x_origin + clipBitmap.getWidth ()), - (immutableGC.clip_y_origin + (gc.clip_y_origin + clipBitmap.getHeight ())); - clipBitmap = immutableGC.clip_mask.bitmap; + clipBitmap = gc.clip_mask.bitmap; if (!maskRect.setIntersect (dstRect, maskRect)) /* There is no intersection between the clip mask and the @@ -191,20 +157,21 @@ public class EmacsCopyArea implements EmacsPaintReq /* Draw the mask onto the maskBitmap. */ maskCanvas = new Canvas (maskBitmap); - maskRect.offset (-immutableGC.clip_x_origin, - -immutableGC.clip_y_origin); - maskCanvas.drawBitmap (immutableGC.clip_mask.bitmap, - maskRect, new Rect (0, 0, - maskRect.width (), - maskRect.height ()), - paint); - maskRect.offset (immutableGC.clip_x_origin, - immutableGC.clip_y_origin); + maskPaint = new Paint (); + maskRect.offset (-gc.clip_x_origin, + -gc.clip_y_origin); + maskCanvas.drawBitmap (gc.clip_mask.bitmap, + maskRect, + new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + maskPaint); + maskRect.offset (gc.clip_x_origin, + gc.clip_y_origin); /* Set the transfer mode to SRC_IN to preserve only the parts of the source that overlap with the mask. */ - maskPaint = new Paint (); - maskPaint.setXfermode (srcInAlu); + maskPaint.setXfermode (EmacsGC.srcInAlu); /* Draw the source. */ maskDestRect = new Rect (0, 0, srcRect.width (), @@ -215,6 +182,10 @@ public class EmacsCopyArea implements EmacsPaintReq /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (overAlu); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + gc.resetXfermode (); } + + canvas.restore (); + destination.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 6389031bbfa..8941d4c217f 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -29,109 +29,49 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsDrawLine implements EmacsPaintReq +public class EmacsDrawLine { - private int x, y, x2, y2; - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; - - static + public static void + perform (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int x2, int y2) { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; + Rect rect; + Canvas canvas; + Paint paint; + int i; - public - EmacsDrawLine (EmacsDrawable drawable, int x, int y, - int x2, int y2, EmacsGC immutableGC) - { - this.drawable = drawable; - this.x = x; - this.y = y; - this.x2 = x2; - this.y2 = y2; - this.immutableGC = immutableGC; - } + /* TODO implement stippling. */ + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; - @Override - public Rect - getRect () - { - return new Rect (Math.min (x, x2 + 1), + paint = gc.gcPaint; + rect = new Rect (Math.min (x, x2 + 1), Math.min (y, y2 + 1), Math.max (x2 + 1, x), Math.max (y2 + 1, y)); - } - - @Override - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int alu; - Paint maskPaint; - Canvas maskCanvas; - Bitmap maskBitmap; - Rect rect, srcRect; - int width, height; + canvas = drawable.lockCanvas (); - /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (canvas == null) return; - alu = immutableGC.function; - rect = getRect (); - width = rect.width (); - height = rect.height (); - - paint.setStyle (Paint.Style.STROKE); - - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + canvas.save (); - if (immutableGC.clip_mask == null) - { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawLine ((float) x, (float) y, - (float) x2, (float) y2, - paint); - } - else + if (gc.real_clip_rects != null) { - maskPaint = new Paint (); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) - return; - - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); - maskCanvas = new Canvas (maskBitmap); - srcRect = new Rect (0, 0, maskBitmap.getWidth (), - maskBitmap.getHeight ()); - maskCanvas.drawLine (0.0f, 0.0f, (float) Math.abs (x - x2), - (float) Math.abs (y - y2), maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); } - paint.setXfermode (null); + paint.setStyle (Paint.Style.STROKE); + + if (gc.clip_mask == null) + canvas.drawLine ((float) x, (float) y, + (float) x2, (float) y2, + paint); + + /* DrawLine with clip mask not implemented; it is not used by + Emacs. */ + canvas.restore (); + drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java b/java/org/gnu/emacs/EmacsDrawPoint.java index 772757ff424..3bc7be17961 100644 --- a/java/org/gnu/emacs/EmacsDrawPoint.java +++ b/java/org/gnu/emacs/EmacsDrawPoint.java @@ -19,12 +19,13 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -public class EmacsDrawPoint extends EmacsDrawRectangle +public class EmacsDrawPoint { - public - EmacsDrawPoint (EmacsDrawable drawable, int x, int y, - EmacsGC immutableGC) + public static void + perform (EmacsDrawable drawable, + EmacsGC immutableGC, int x, int y) { - super (drawable, x, y, 1, 1, immutableGC); + EmacsDrawRectangle.perform (drawable, immutableGC, + x, y, 1, 1); } } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index e3f28227146..b42e9556e8c 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -22,110 +22,104 @@ package org.gnu.emacs; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.Xfermode; -public class EmacsDrawRectangle implements EmacsPaintReq -{ - private int x, y, width, height; - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; - - static - { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; - - public - EmacsDrawRectangle (EmacsDrawable drawable, int x, int y, - int width, int height, - EmacsGC immutableGC) - { - this.drawable = drawable; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.immutableGC = immutableGC; - } +import android.util.Log; - @Override - public Rect - getRect () - { - /* Canvas.drawRect actually behaves exactly like PolyRectangle wrt - to where the lines are placed, so extend the width and height - by 1 in the damage rectangle. */ - return new Rect (x, y, x + width + 1, y + height + 1); - } - - @Override - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) +public class EmacsDrawRectangle +{ + public static void + perform (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) { - int alu; - Paint maskPaint; + int i; + Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect, srcRect; + Rect rect; + Rect maskRect, dstRect; + Canvas canvas; + Bitmap clipBitmap; /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - alu = immutableGC.function; - rect = new Rect (x, y, x + width, y + height); + canvas = drawable.lockCanvas (); - paint.setStyle (Paint.Style.STROKE); - paint.setStrokeWidth (1); + if (canvas == null) + return; - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + canvas.save (); - if (immutableGC.clip_mask == null) + if (gc.real_clip_rects != null) { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawRect (rect, paint); + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); } + + paint = gc.gcPaint; + rect = new Rect (x, y, x + width, y + height); + + paint.setStyle (Paint.Style.STROKE); + + if (gc.clip_mask == null) + canvas.drawRect (rect, paint); else { - maskPaint = new Paint (); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) + /* Drawing with a clip mask involves calculating the + intersection of the clip mask with the dst rect, and + extrapolating the corresponding part of the src rect. */ + clipBitmap = gc.clip_mask.bitmap; + dstRect = new Rect (x, y, x + width, y + height); + maskRect = new Rect (gc.clip_x_origin, + gc.clip_y_origin, + (gc.clip_x_origin + + clipBitmap.getWidth ()), + (gc.clip_y_origin + + clipBitmap.getHeight ())); + clipBitmap = gc.clip_mask.bitmap; + + if (!maskRect.setIntersect (dstRect, maskRect)) + /* There is no intersection between the clip mask and the + dest rect. */ return; - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); + /* Finally, create a temporary bitmap that is the size of + maskRect. */ + + maskBitmap + = Bitmap.createBitmap (maskRect.width (), maskRect.height (), + Bitmap.Config.ARGB_8888); + + /* Draw the mask onto the maskBitmap. */ maskCanvas = new Canvas (maskBitmap); - srcRect = new Rect (0, 0, maskBitmap.getWidth (), - maskBitmap.getHeight ()); - maskCanvas.drawRect (srcRect, maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + maskRect.offset (-gc.clip_x_origin, + -gc.clip_y_origin); + maskCanvas.drawBitmap (gc.clip_mask.bitmap, + maskRect, new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + paint); + maskRect.offset (gc.clip_x_origin, + gc.clip_y_origin); + + /* Set the transfer mode to SRC_IN to preserve only the parts + of the source that overlap with the mask. */ + maskPaint = new Paint (); + maskPaint.setXfermode (EmacsGC.srcInAlu); + maskPaint.setStyle (Paint.Style.STROKE); + + /* Draw the source. */ + maskCanvas.drawRect (maskRect, maskPaint); + + /* Finally, draw the mask bitmap to the destination. */ + paint.setXfermode (null); + canvas.drawBitmap (maskBitmap, null, maskRect, paint); } - paint.setXfermode (null); + canvas.restore (); + drawable.damageRect (new Rect (x, y, x + width + 1, + y + height + 1)); } } diff --git a/java/org/gnu/emacs/EmacsDrawable.java b/java/org/gnu/emacs/EmacsDrawable.java index 19062137213..6a6199ff214 100644 --- a/java/org/gnu/emacs/EmacsDrawable.java +++ b/java/org/gnu/emacs/EmacsDrawable.java @@ -26,7 +26,6 @@ import android.graphics.Canvas; public interface EmacsDrawable { public Canvas lockCanvas (); - public void unlockCanvas (); public void damageRect (Rect damageRect); public Bitmap getBitmap (); public boolean isDestroyed (); diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index 3198c7f07c4..42b73886dff 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -26,34 +26,35 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Xfermode; -public class EmacsFillPolygon implements EmacsPaintReq +public class EmacsFillPolygon { - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private Path path; + public static void + perform (EmacsDrawable drawable, EmacsGC gc, Point points[]) + { + Canvas canvas; + Path path; + Paint paint; + Rect rect; + RectF rectF; + int i; - private static Xfermode xorAlu, srcInAlu; + canvas = drawable.lockCanvas (); - static - { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; + if (canvas == null) + return; - public - EmacsFillPolygon (EmacsDrawable drawable, Point points[], - EmacsGC immutableGC) - { - int i; + paint = gc.gcPaint; + + canvas.save (); - this.drawable = drawable; - this.immutableGC = immutableGC; + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } /* Build the path from the given array of points. */ path = new Path (); @@ -67,84 +68,25 @@ public class EmacsFillPolygon implements EmacsPaintReq path.close (); } - } - - @Override - public Rect - getRect () - { - RectF rect; - rect = new RectF (0, 0, 0, 0); - path.computeBounds (rect, true); + /* Compute the damage rectangle. */ + rectF = new RectF (0, 0, 0, 0); + path.computeBounds (rectF, true); - return new Rect ((int) Math.floor (rect.left), - (int) Math.floor (rect.top), - (int) Math.ceil (rect.right), - (int) Math.ceil (rect.bottom)); - } + rect = new Rect ((int) Math.floor (rectF.left), + (int) Math.floor (rectF.top), + (int) Math.ceil (rectF.right), + (int) Math.ceil (rectF.bottom)); - @Override - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int alu; - Paint maskPaint; - Canvas maskCanvas; - Bitmap maskBitmap; - Rect rect; - - /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) - return; - - alu = immutableGC.function; - rect = getRect (); + paint.setStyle (Paint.Style.FILL); - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + if (gc.clip_mask == null) + canvas.drawPath (path, paint); - paint.setStyle (Paint.Style.FILL); + canvas.restore (); + drawable.damageRect (rect); - if (immutableGC.clip_mask == null) - { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawPath (path, paint); - } - else - { - maskPaint = new Paint (); - maskBitmap = immutableGC.clip_mask.bitmap; - maskBitmap = maskBitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) - return; - - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); - maskCanvas = new Canvas (maskBitmap); - path.offset (-rect.left, -rect.top, null); - maskCanvas.drawPath (path, maskPaint); - canvas.drawBitmap (maskBitmap, new Rect (0, 0, rect.width (), - rect.height ()), - rect, paint); - } + /* FillPolygon with clip mask not implemented; it is not used by + Emacs. */ } } diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index 7246c13de7f..b733b417d6b 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -22,108 +22,102 @@ package org.gnu.emacs; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.Xfermode; import android.util.Log; -public class EmacsFillRectangle implements EmacsPaintReq +public class EmacsFillRectangle { - private int x, y, width, height; - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; - - static - { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; - - public - EmacsFillRectangle (EmacsDrawable drawable, int x, int y, - int width, int height, - EmacsGC immutableGC) - { - this.drawable = drawable; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.immutableGC = immutableGC; - } - - @Override - public Rect - getRect () - { - return new Rect (x, y, x + width, y + height); - } - - @Override - public EmacsDrawable - getDrawable () + public static void + perform (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int alu; - Paint maskPaint; + int i; + Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect, srcRect; + Rect rect; + Rect maskRect, dstRect; + Canvas canvas; + Bitmap clipBitmap; /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - alu = immutableGC.function; - rect = getRect (); + canvas = drawable.lockCanvas (); - paint.setStyle (Paint.Style.FILL); + if (canvas == null) + return; - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + canvas.save (); - if (immutableGC.clip_mask == null) + if (gc.real_clip_rects != null) { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawRect (rect, paint); + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); } + + paint = gc.gcPaint; + rect = new Rect (x, y, x + width, y + height); + + paint.setStyle (Paint.Style.FILL); + + if (gc.clip_mask == null) + canvas.drawRect (rect, paint); else { - maskPaint = new Paint (); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) + /* Drawing with a clip mask involves calculating the + intersection of the clip mask with the dst rect, and + extrapolating the corresponding part of the src rect. */ + clipBitmap = gc.clip_mask.bitmap; + dstRect = new Rect (x, y, x + width, y + height); + maskRect = new Rect (gc.clip_x_origin, + gc.clip_y_origin, + (gc.clip_x_origin + + clipBitmap.getWidth ()), + (gc.clip_y_origin + + clipBitmap.getHeight ())); + clipBitmap = gc.clip_mask.bitmap; + + if (!maskRect.setIntersect (dstRect, maskRect)) + /* There is no intersection between the clip mask and the + dest rect. */ return; - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); + /* Finally, create a temporary bitmap that is the size of + maskRect. */ + + maskBitmap + = Bitmap.createBitmap (maskRect.width (), maskRect.height (), + Bitmap.Config.ARGB_8888); + + /* Draw the mask onto the maskBitmap. */ maskCanvas = new Canvas (maskBitmap); - srcRect = new Rect (0, 0, maskBitmap.getWidth (), - maskBitmap.getHeight ()); - maskCanvas.drawRect (srcRect, maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + maskRect.offset (-gc.clip_x_origin, + -gc.clip_y_origin); + maskCanvas.drawBitmap (gc.clip_mask.bitmap, + maskRect, new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + paint); + maskRect.offset (gc.clip_x_origin, + gc.clip_y_origin); + + /* Set the transfer mode to SRC_IN to preserve only the parts + of the source that overlap with the mask. */ + maskPaint = new Paint (); + maskPaint.setXfermode (EmacsGC.srcInAlu); + + /* Draw the source. */ + maskCanvas.drawRect (maskRect, maskPaint); + + /* Finally, draw the mask bitmap to the destination. */ + paint.setXfermode (null); + canvas.drawBitmap (maskBitmap, null, maskRect, paint); } - paint.setXfermode (null); + canvas.restore (); + drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index 9f40aa04c44..1d1e6f7b33f 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -164,7 +164,7 @@ public abstract class EmacsFontDriver public static EmacsFontDriver createFontDriver () { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) return new EmacsSdk23FontDriver (); return new EmacsSdk7FontDriver (); diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index 0becb04519d..caa5c91edd4 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -20,6 +20,11 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import android.graphics.Rect; +import android.graphics.Paint; + +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Xfermode; /* X like graphics context structures. Keep the enums in synch with androidgui.h! */ @@ -32,14 +37,21 @@ public class EmacsGC extends EmacsHandleObject public static final int GC_FILL_SOLID = 0; public static final int GC_FILL_OPAQUE_STIPPLED = 1; + public static final Xfermode xorAlu, srcInAlu; + public int function, fill_style; public int foreground, background; public int clip_x_origin, clip_y_origin; public int ts_origin_x, ts_origin_y; - public Rect clip_rects[]; + public Rect clip_rects[], real_clip_rects[]; public EmacsPixmap clip_mask, stipple; - private boolean dirty; - private EmacsGC immutableGC; + public Paint gcPaint; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + } /* The following fields are only set on immutable GCs. */ @@ -55,62 +67,41 @@ public class EmacsGC extends EmacsHandleObject fill_style = GC_FILL_SOLID; function = GC_COPY; foreground = 0; - background = 0xffffffff; + background = 0xffffff; + gcPaint = new Paint (); } - public - EmacsGC (EmacsGC source) - { - super ((short) 0); + /* Mark this GC as dirty. Apply parameters to the paint and + recompute real_clip_rects. */ + public void + markDirty () + { int i; - function = source.function; - fill_style = source.fill_style; - foreground = source.foreground; - background = source.background; - clip_x_origin = source.clip_x_origin; - clip_y_origin = source.clip_y_origin; - clip_rects = source.clip_rects; - clip_mask = source.clip_mask; - stipple = source.stipple; - ts_origin_x = source.ts_origin_x; - ts_origin_y = source.ts_origin_y; - - /* Offset all the clip rects by ts_origin_x and ts_origin_y. */ - if ((ts_origin_x != 0 || ts_origin_y != 0) && clip_rects != null) { - clip_rects = new Rect[clip_rects.length]; + real_clip_rects = new Rect[clip_rects.length]; for (i = 0; i < clip_rects.length; ++i) { - clip_rects[i] = new Rect (source.clip_rects[i]); - clip_rects[i].offset (ts_origin_x, - ts_origin_y); + real_clip_rects[i] = new Rect (clip_rects[i]); + real_clip_rects[i].offset (ts_origin_x, ts_origin_y); } } - } + else + real_clip_rects = clip_rects; - /* Mark this GC as dirty. This means immutableGC will return a new - copy of this GC the next time it is called. */ + gcPaint.setColor (foreground | 0xff000000); + gcPaint.setXfermode (function == GC_XOR + ? xorAlu : srcInAlu); + } public void - markDirty () + resetXfermode () { - dirty = true; + gcPaint.setXfermode (function == GC_XOR + ? xorAlu : srcInAlu); } - - public EmacsGC - immutableGC () - { - if (immutableGC == null || dirty) - { - immutableGC = new EmacsGC (this); - dirty = false; - } - - return immutableGC; - }; }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index c80339031a8..ae48ce30408 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -79,6 +79,28 @@ public class EmacsNative /* Send an ANDROID_WINDOW_ACTION event. */ public static native void sendWindowAction (short window, int action); + /* Send an ANDROID_ENTER_NOTIFY event. */ + public static native void sendEnterNotify (short window, int x, int y, + long time); + + /* Send an ANDROID_LEAVE_NOTIFY event. */ + public static native void sendLeaveNotify (short window, int x, int y, + long time); + + /* Send an ANDROID_MOTION_NOTIFY event. */ + public static native void sendMotionNotify (short window, int x, int y, + long time); + + /* Send an ANDROID_BUTTON_PRESS event. */ + public static native void sendButtonPress (short window, int x, int y, + long time, int state, + int button); + + /* Send an ANDROID_BUTTON_RELEASE event. */ + public static native void sendButtonRelease (short window, int x, int y, + long time, int state, + int button); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 897902c5b57..15452f007c4 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -93,6 +93,8 @@ public class EmacsPixmap extends EmacsHandleObject break; } + bitmap.eraseColor (0xff000000); + this.width = width; this.height = height; this.depth = depth; @@ -108,13 +110,6 @@ public class EmacsPixmap extends EmacsHandleObject return canvas; } - @Override - public void - unlockCanvas () - { - - } - @Override public void damageRect (Rect damageRect) diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java b/java/org/gnu/emacs/EmacsSdk23FontDriver.java index 34e2b1803a2..11e128d5769 100644 --- a/java/org/gnu/emacs/EmacsSdk23FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java @@ -20,9 +20,80 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import android.graphics.Paint; +import android.graphics.Rect; public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver { + private void + textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics, + Paint paint, Rect bounds) + { + char[] text; + + text = new char[2]; + text[0] = (char) code; + text[1] = 'c'; + + paint.getTextBounds (text, 0, 1, bounds); + + metrics.lbearing = (short) bounds.left; + metrics.rbearing = (short) bounds.right; + metrics.ascent = (short) -bounds.top; + metrics.descent = (short) bounds.bottom; + metrics.width + = (short) paint.getRunAdvance (text, 0, 1, 0, 1, false, 1); + } + + @Override + public void + textExtents (FontObject font, int code[], FontMetrics fontMetrics) + { + int i; + Paint paintCache; + Rect boundsCache; + Sdk7FontObject fontObject; + char[] text; + float width; + + fontObject = (Sdk7FontObject) font; + paintCache = fontObject.typeface.typefacePaint; + paintCache.setTextSize (fontObject.pixelSize); + boundsCache = new Rect (); + + if (code.length == 0) + { + fontMetrics.lbearing = 0; + fontMetrics.rbearing = 0; + fontMetrics.ascent = 0; + fontMetrics.descent = 0; + fontMetrics.width = 0; + } + else if (code.length == 1) + textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics, + paintCache, boundsCache); + else + { + text = new char[code.length + 1]; + + for (i = 0; i < code.length; ++i) + text[i] = (char) code[i]; + + text[code.length] = 'c'; + + paintCache.getTextBounds (text, 0, code.length, + boundsCache); + width = paintCache.getRunAdvance (text, 0, code.length, 0, + code.length, + false, code.length); + + fontMetrics.lbearing = (short) boundsCache.left; + fontMetrics.rbearing = (short) boundsCache.right; + fontMetrics.ascent = (short) -boundsCache.top; + fontMetrics.descent = (short) boundsCache.bottom; + fontMetrics.width = (short) width; + } + } + @Override public int hasChar (FontSpec font, char charCode) diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 437f38e62db..8a9426050ae 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -243,93 +243,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; - private class Sdk7DrawString implements EmacsPaintReq - { - private boolean drawBackground; - private Sdk7FontObject fontObject; - private char[] chars; - private EmacsGC immutableGC; - private EmacsDrawable drawable; - private Rect rect; - private int originX, originY; - - public - Sdk7DrawString (Sdk7FontObject fontObject, char[] chars, - EmacsGC immutableGC, EmacsDrawable drawable, - boolean drawBackground, Rect rect, - int originX, int originY) - { - this.fontObject = fontObject; - this.chars = chars; - this.immutableGC = immutableGC; - this.drawable = drawable; - this.drawBackground = drawBackground; - this.rect = rect; - this.originX = originX; - this.originY = originY; - } - - @Override - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int scratch; - - paint.setStyle (Paint.Style.FILL); - - if (drawBackground) - { - paint.setColor (immutableGC.background | 0xff000000); - canvas.drawRect (rect, paint); - } - - paint.setTextSize (fontObject.pixelSize); - paint.setColor (immutableGC.foreground | 0xff000000); - paint.setTypeface (fontObject.typeface.typeface); - paint.setAntiAlias (true); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - /* Disable hinting as that leads to displayed text not - matching the computed metrics. */ - paint.setHinting (Paint.HINTING_OFF); - - canvas.drawText (chars, 0, chars.length, originX, originY, paint); - paint.setAntiAlias (false); - } - - @Override - public Rect - getRect () - { - Rect rect; - - rect = new Rect (); - - fontObject.typeface.typefacePaint.setTextSize (fontObject.pixelSize); - fontObject.typeface.typefacePaint.getTextBounds (chars, 0, chars.length, - rect); - - /* Add the background rect to the damage as well. */ - rect.union (this.rect); - - return rect; - } - }; - private String[] fontFamilyList; private Sdk7Typeface[] typefaceList; private Sdk7Typeface fallbackTypeface; @@ -498,14 +411,11 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver Paint paint, Rect bounds) { char[] text; - float[] width; text = new char[1]; text[0] = (char) code; paint.getTextBounds (text, 0, 1, bounds); - width = new float[1]; - paint.getTextWidths (text, 0, 1, width); /* bounds is the bounding box of the glyph corresponding to CODE. Translate these into XCharStruct values. @@ -526,7 +436,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver metrics.rbearing = (short) bounds.right; metrics.ascent = (short) -bounds.top; metrics.descent = (short) bounds.bottom; - metrics.width = (short) Math.round (width[0]); + metrics.width = (short) paint.measureText ("" + text[0]); } @Override @@ -563,7 +473,8 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver for (i = 0; i < code.length; ++i) text[i] = (char) code[i]; - paintCache.getTextBounds (text, 0, 1, boundsCache); + paintCache.getTextBounds (text, 0, code.length, + boundsCache); width = paintCache.measureText (text, 0, code.length); fontMetrics.lbearing = (short) boundsCache.left; @@ -587,11 +498,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver int[] chars, int x, int y, int backgroundWidth, boolean withBackground) { - Rect backgroundRect; + Rect backgroundRect, bounds; Sdk7FontObject sdk7FontObject; - Sdk7DrawString op; char[] charsArray; int i; + Canvas canvas; + Paint paint; sdk7FontObject = (Sdk7FontObject) fontObject; charsArray = new char[chars.length]; @@ -605,12 +517,41 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver backgroundRect.right = x + backgroundWidth; backgroundRect.bottom = y + sdk7FontObject.descent; - op = new Sdk7DrawString (sdk7FontObject, charsArray, - gc.immutableGC (), drawable, - withBackground, - backgroundRect, x, y); + canvas = drawable.lockCanvas (); + + if (canvas == null) + return 0; + + canvas.save (); + paint = gc.gcPaint; + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } + + paint.setStyle (Paint.Style.FILL); + + if (withBackground) + { + paint.setColor (gc.background | 0xff000000); + canvas.drawRect (backgroundRect, paint); + paint.setColor (gc.foreground | 0xff000000); + } - EmacsService.SERVICE.appendPaintOperation (op); + paint.setTextSize (sdk7FontObject.pixelSize); + paint.setTypeface (sdk7FontObject.typeface.typeface); + paint.setAntiAlias (true); + canvas.drawText (charsArray, 0, chars.length, x, y, paint); + + canvas.restore (); + bounds = new Rect (); + paint.getTextBounds (charsArray, 0, chars.length, bounds); + bounds.offset (x, y); + bounds.union (backgroundRect); + drawable.damageRect (bounds); + paint.setAntiAlias (false); return 1; } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 41a45b0bd85..4444b7f1c56 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -19,7 +19,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import java.lang.Runnable; import java.io.IOException; import java.util.List; import java.util.ArrayList; @@ -59,7 +58,6 @@ public class EmacsService extends Service private EmacsThread thread; private Handler handler; - private EmacsPaintQueue paintQueue; /* Display metrics used by font backends. */ public DisplayMetrics metrics; @@ -191,126 +189,42 @@ public class EmacsService extends Service return view.thing; } - /* X drawing operations. These are quite primitive operations. The - drawing queue is kept on the Emacs thread, but is periodically - flushed to the application thread, upon buffers swaps and once it - gets too big. */ - - - - private void - ensurePaintQueue () - { - if (paintQueue == null) - paintQueue = new EmacsPaintQueue (); - } - - public void - flushPaintQueue () - { - final EmacsPaintQueue queue; - - if (paintQueue == null) - return; - - if (paintQueue.numRequests < 1) - /* No requests to flush. */ - return; - - queue = paintQueue; - - handler.post (new Runnable () { - @Override - public void - run () - { - queue.run (); - } - }); - - /* Clear the paint queue. */ - paintQueue = null; - } - - private void - checkFlush () - { - if (paintQueue != null - && paintQueue.numRequests > MAX_PENDING_REQUESTS) - flushPaintQueue (); - } - public void fillRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsFillRectangle (drawable, x, y, - width, height, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsFillRectangle.perform (drawable, gc, x, y, + width, height); } public void fillPolygon (EmacsDrawable drawable, EmacsGC gc, Point points[]) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsFillPolygon (drawable, points, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsFillPolygon.perform (drawable, gc, points); } public void drawRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsDrawRectangle (drawable, x, y, - width, height, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsDrawRectangle.perform (drawable, gc, x, y, + width, height); } public void drawLine (EmacsDrawable drawable, EmacsGC gc, int x, int y, int x2, int y2) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsDrawLine (drawable, x, y, - x2, y2, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsDrawLine.perform (drawable, gc, x, y, + x2, y2); } public void drawPoint (EmacsDrawable drawable, EmacsGC gc, int x, int y) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsDrawPoint (drawable, x, y, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsDrawPoint.perform (drawable, gc, x, y); } public void @@ -319,15 +233,9 @@ public class EmacsService extends Service int srcX, int srcY, int width, int height, int destX, int destY) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsCopyArea (srcDrawable, dstDrawable, - srcX, srcY, width, height, destX, - destY, gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsCopyArea.perform (srcDrawable, gc, dstDrawable, + srcX, srcY, width, height, destX, + destY); } public void @@ -342,12 +250,4 @@ public class EmacsService extends Service { window.clearArea (x, y, width, height); } - - public void - appendPaintOperation (EmacsPaintReq op) - { - ensurePaintQueue (); - paintQueue.appendPaintOperation (op); - checkFlush (); - } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index b8b828e4820..5efb8882263 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -29,6 +29,7 @@ import android.graphics.Rect; public class EmacsSurfaceView extends SurfaceView { + public Object surfaceChangeLock; private boolean created; public @@ -36,33 +37,36 @@ public class EmacsSurfaceView extends SurfaceView { super (view.getContext ()); + surfaceChangeLock = new Object (); + getHolder ().addCallback (new SurfaceHolder.Callback () { @Override public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) { - /* Force a buffer swap now to get the contents of the Emacs - view on screen. */ - view.swapBuffers (true); + view.swapBuffers (); } @Override public void surfaceCreated (SurfaceHolder holder) { - created = true; - - /* Force a buffer swap now to get the contents of the Emacs - view on screen. */ - view.swapBuffers (true); + synchronized (surfaceChangeLock) + { + created = true; + view.swapBuffers (); + } } @Override public void surfaceDestroyed (SurfaceHolder holder) { - created = false; + synchronized (surfaceChangeLock) + { + created = false; + } } }); } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 7b48eaf0aa6..169a1e42ee3 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -23,6 +23,7 @@ import android.content.res.ColorStateList; import android.view.View; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.ViewGroup; import android.graphics.Bitmap; @@ -64,6 +65,14 @@ public class EmacsView extends ViewGroup /* The associated surface view. */ private EmacsSurfaceView surfaceView; + /* Whether or not a configure event must be sent for the next layout + event regardless of what changed. */ + public boolean mustReportLayout; + + /* If non-null, whether or not bitmaps must be recreated upon the + next call to getBitmap. */ + private Rect bitmapDirty; + public EmacsView (EmacsWindow window) { @@ -81,6 +90,43 @@ public class EmacsView extends ViewGroup addView (this.surfaceView); } + private void + handleDirtyBitmap () + { + /* Recreate the front and back buffer bitmaps. */ + bitmap + = Bitmap.createBitmap (bitmapDirty.width (), + bitmapDirty.height (), + Bitmap.Config.ARGB_8888); + bitmap.eraseColor (0xffffffff); + + /* And canvases. */ + canvas = new Canvas (bitmap); + + /* If Emacs is drawing to the bitmap right now from the + main thread, the image contents are lost until the next + ConfigureNotify and complete garbage. Sorry! */ + bitmapDirty = null; + } + + public synchronized Bitmap + getBitmap () + { + if (bitmapDirty != null) + handleDirtyBitmap (); + + return bitmap; + } + + public synchronized Canvas + getCanvas () + { + if (bitmapDirty != null) + handleDirtyBitmap (); + + return canvas; + } + @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) @@ -114,7 +160,7 @@ public class EmacsView extends ViewGroup } @Override - protected void + protected synchronized void onLayout (boolean changed, int left, int top, int right, int bottom) { @@ -122,19 +168,15 @@ public class EmacsView extends ViewGroup View child; Rect windowRect; - if (changed) + if (changed || mustReportLayout) { + mustReportLayout = false; window.viewLayout (left, top, right, bottom); - - /* Recreate the front and back buffer bitmaps. */ - bitmap - = Bitmap.createBitmap (right - left, bottom - top, - Bitmap.Config.ARGB_8888); - - /* And canvases. */ - canvas = new Canvas (bitmap); } + if (changed) + bitmapDirty = new Rect (left, top, right, bottom); + count = getChildCount (); for (i = 0; i < count; ++i) @@ -159,47 +201,60 @@ public class EmacsView extends ViewGroup } } - public void + public synchronized void damageRect (Rect damageRect) { damageRegion.union (damageRect); } - public void + /* This method is called from both the UI thread and the Emacs + thread. */ + + public synchronized void swapBuffers (boolean force) { - Bitmap back; Canvas canvas; Rect damageRect; + Bitmap bitmap; if (damageRegion.isEmpty ()) return; - if (!surfaceView.isCreated ()) - return; + bitmap = getBitmap (); - if (bitmap == null) - return; + /* Emacs must take the following lock to ensure the access to the + canvas occurs with the surface created. Otherwise, Android + will throttle calls to lockCanvas. */ + + synchronized (surfaceView.surfaceChangeLock) + { + damageRect = damageRegion.getBounds (); - /* Lock the canvas with the specified damage. */ - damageRect = damageRegion.getBounds (); - canvas = surfaceView.lockCanvas (damageRect); + if (!surfaceView.isCreated ()) + return; - /* Return if locking the canvas failed. */ - if (canvas == null) - return; + if (bitmap == null) + return; + + /* Lock the canvas with the specified damage. */ + canvas = surfaceView.lockCanvas (damageRect); - /* Copy from the back buffer to the canvas. If damageRect was - made empty, then draw the entire back buffer. */ + /* Return if locking the canvas failed. */ + if (canvas == null) + return; - if (damageRect.isEmpty ()) - canvas.drawBitmap (bitmap, 0f, 0f, paint); - else - canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + /* Copy from the back buffer to the canvas. If damageRect was + made empty, then draw the entire back buffer. */ - /* Unlock the canvas and clear the damage. */ - surfaceView.unlockCanvasAndPost (canvas); - damageRegion.setEmpty (); + if (damageRect.isEmpty ()) + canvas.drawBitmap (bitmap, 0f, 0f, paint); + else + canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + + /* Unlock the canvas and clear the damage. */ + surfaceView.unlockCanvasAndPost (canvas); + damageRegion.setEmpty (); + } } public void @@ -241,4 +296,18 @@ public class EmacsView extends ViewGroup super.onFocusChanged (gainFocus, direction, previouslyFocusedRect); } + + @Override + public boolean + onGenericMotionEvent (MotionEvent motion) + { + return window.onSomeKindOfMotionEvent (motion); + } + + @Override + public boolean + onTouchEvent (MotionEvent motion) + { + return window.onSomeKindOfMotionEvent (motion); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 26e788a20a8..a9a24b61869 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -31,8 +31,13 @@ import android.graphics.Point; import android.view.View; import android.view.ViewGroup; import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.InputDevice; import android.content.Intent; +import android.util.Log; + +import android.os.Build; /* This defines a window, which is a handle. Windows represent a rectangular subset of the screen with their own contents. @@ -68,6 +73,10 @@ public class EmacsWindow extends EmacsHandleObject window background. */ private EmacsGC scratchGC; + /* The button state and keyboard modifier mask at the time of the + last button press or release event. */ + private int lastButtonState, lastModifiers; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height) @@ -212,6 +221,7 @@ public class EmacsWindow extends EmacsHandleObject public void run () { + view.mustReportLayout = true; view.requestLayout (); } }); @@ -224,6 +234,8 @@ public class EmacsWindow extends EmacsHandleObject { rect.right = rect.left + width; rect.bottom = rect.top + height; + + requestViewLayout (); } } @@ -279,17 +291,7 @@ public class EmacsWindow extends EmacsHandleObject public Canvas lockCanvas () { - if (view.canvas != null) - return view.canvas; - - return null; - } - - @Override - public void - unlockCanvas () - { - + return view.getCanvas (); } @Override @@ -302,17 +304,7 @@ public class EmacsWindow extends EmacsHandleObject public void swapBuffers () { - /* Before calling swapBuffers, make sure to flush the paint - queue. */ - EmacsService.SERVICE.flushPaintQueue (); - view.post (new Runnable () { - @Override - public void - run () - { - view.swapBuffers (); - } - }); + view.swapBuffers (); } public void @@ -337,7 +329,7 @@ public class EmacsWindow extends EmacsHandleObject public Bitmap getBitmap () { - return view.bitmap; + return view.getBitmap (); } public void @@ -357,6 +349,7 @@ public class EmacsWindow extends EmacsHandleObject recognized as an ASCII key press event. */ event.getUnicodeChar (state)); + lastModifiers = event.getModifiers (); } public void @@ -372,6 +365,7 @@ public class EmacsWindow extends EmacsHandleObject event.getModifiers (), keyCode, event.getUnicodeChar (state)); + lastModifiers = event.getModifiers (); } public void @@ -386,4 +380,105 @@ public class EmacsWindow extends EmacsHandleObject /* Destroy the associated frame when the activity is detached. */ EmacsNative.sendWindowAction (this.handle, 0); } + + /* Look through the button state to determine what button EVENT was + generated from. DOWN is true if EVENT is a button press event, + false otherwise. Value is the X number of the button. */ + + private int + whatButtonWasIt (MotionEvent event, boolean down) + { + int eventState, notIn; + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + /* Earlier versions of Android only support one mouse + button. */ + return 1; + + eventState = event.getButtonState (); + notIn = (down ? eventState & ~lastButtonState + : lastButtonState & ~eventState); + + if ((notIn & MotionEvent.BUTTON_PRIMARY) != 0) + return 1; + + if ((notIn & MotionEvent.BUTTON_SECONDARY) != 0) + return 3; + + if ((notIn & MotionEvent.BUTTON_TERTIARY) != 0) + return 2; + + /* Not a real value. */ + return 4; + } + + public boolean + onSomeKindOfMotionEvent (MotionEvent event) + { + if (!event.isFromSource (InputDevice.SOURCE_CLASS_POINTER)) + return false; + + switch (event.getAction ()) + { + case MotionEvent.ACTION_HOVER_ENTER: + EmacsNative.sendEnterNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); + return true; + + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: + EmacsNative.sendMotionNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); + return true; + + case MotionEvent.ACTION_HOVER_EXIT: + EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); + return true; + + case MotionEvent.ACTION_BUTTON_PRESS: + /* Find the button which was pressed. */ + EmacsNative.sendButtonPress (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + whatButtonWasIt (event, true)); + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return true; + + lastButtonState = event.getButtonState (); + return true; + + case MotionEvent.ACTION_BUTTON_RELEASE: + /* Find the button which was released. */ + EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + whatButtonWasIt (event, false)); + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return true; + + lastButtonState = event.getButtonState (); + return true; + + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_UP: + /* Emacs must return true even though touch events are not yet + handled, because the value of this function is used by the + system to decide whether or not Emacs gets ACTION_MOVE + events. */ + return true; + } + + return false; + } }; -- cgit v1.2.1 From 86fe89312893bbc8aa47605afbf8da8cd5a12faf Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 8 Jan 2023 15:39:28 +0800 Subject: Delete unused files * java/org/gnu/emacs/EmacsPaintQueue.java * java/org/gnu/emacs/EmacsPaintReq.java: Remove files. --- java/org/gnu/emacs/EmacsPaintQueue.java | 124 -------------------------------- java/org/gnu/emacs/EmacsPaintReq.java | 33 --------- 2 files changed, 157 deletions(-) delete mode 100644 java/org/gnu/emacs/EmacsPaintQueue.java delete mode 100644 java/org/gnu/emacs/EmacsPaintReq.java (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsPaintQueue.java b/java/org/gnu/emacs/EmacsPaintQueue.java deleted file mode 100644 index f4840dbf5ae..00000000000 --- a/java/org/gnu/emacs/EmacsPaintQueue.java +++ /dev/null @@ -1,124 +0,0 @@ -/* 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.util.LinkedList; -import java.util.List; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; - -public class EmacsPaintQueue -{ - /* Queue of paint operations. This is modified from the Emacs - thread, and entire paint queues are periodically flushed to the - application thread where it is executed. */ - private List paintOperations; - - /* Number of operations in this queue. */ - public int numRequests; - - public - EmacsPaintQueue () - { - paintOperations = new LinkedList (); - } - - public void - run () - { - EmacsDrawable drawable, last; - Canvas canvas; - EmacsGC gc; - int i; - Paint paint; - Rect rect, offsetRect, copyRect; - - canvas = null; - last = null; - gc = null; - paint = new Paint (); - - for (EmacsPaintReq req : paintOperations) - { - drawable = req.getDrawable (); - canvas = drawable.lockCanvas (); - - if (canvas == null) - /* No canvas is currently available. */ - continue; - - gc = req.getGC (); - rect = req.getRect (); - - drawable.damageRect (rect); - - if (gc.clip_rects == null) - { - /* No clipping is applied. Just draw and continue. */ - req.paintTo (canvas, paint, gc); - continue; - } - - if (gc.clip_rects != null && gc.clip_rects.length > 0) - { - if (gc.clip_rects.length == 1) - { - /* There is only a single clip rect, which is simple - enough. */ - canvas.save (); - canvas.clipRect (gc.clip_rects[0]); - req.paintTo (canvas, paint, gc); - canvas.restore (); - } - else - { - /* There are multiple clip rects. Android doesn't let - programs use RegionOp.UNION on the clip rectangle, - so Emacs must iterate over each intersection and - paint it manually. This seems inefficient but - thankfully Emacs never seems to use more than one - clip rect. */ - - for (i = 0; i < gc.clip_rects.length; ++i) - { - copyRect = new Rect (gc.clip_rects[i]); - - if (copyRect.intersect (rect)) - { - canvas.save (); - canvas.clipRect (copyRect); - req.paintTo (canvas, paint, gc); - canvas.restore (); - } - } - } - } - } - } - - public void - appendPaintOperation (EmacsPaintReq req) - { - paintOperations.add (req); - numRequests++; - } -}; diff --git a/java/org/gnu/emacs/EmacsPaintReq.java b/java/org/gnu/emacs/EmacsPaintReq.java deleted file mode 100644 index 5b14b005093..00000000000 --- a/java/org/gnu/emacs/EmacsPaintReq.java +++ /dev/null @@ -1,33 +0,0 @@ -/* 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.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; - -public interface EmacsPaintReq -{ - public EmacsDrawable getDrawable (); - public EmacsGC getGC (); - public void paintTo (Canvas canvas, Paint paint, - EmacsGC immutableGC); - public Rect getRect (); -}; -- cgit v1.2.1 From f9732131cf3c67e24db74a3d49f256d3c189a7e3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 13 Jan 2023 15:53:08 +0800 Subject: Update Android port * configure.ac (ANDROID_MIN_SDK): New variable. (DX): Remove and replace with D8. (XCONFIGURE): Check for the minimum version of Android the cross compiler compiles for. Generate java/AndroidManifest.xml from java/AndroidManifest.xml.in. Allow using Zlib on Android. * java/AndroidManifest.xml.in: New file. Use the minimum SDK detected by configure. * java/Makefile.in (top_srcdir, version): New variables. (DX, D8): Replace with D8. (ANDROID_MIN_SDK, APK_NAME): New variables. (.PHONY): (.PRECIOUS): (classes.dex): (emacs.apk): Generate $(APK_NAME) instead of `emacs.apk'. * java/debug.sh: New option --attach-existing. Attach to an existing Emacs instance when specified. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `isPaused'. (invalidateFocus1): Fix infinite recursion. (detachWindow): Deiconify window. (attachWindow): Iconify the window if the activity is paused. (onCreate): Use the ``no title bar'' theme. (onPause, onResume): New functions. * java/org/gnu/emacs/EmacsNative.java (sendTouchUp, sendTouchDown) (sendTouchMove, sendWheel, sendIconified, sendDeiconified): New functions. * java/org/gnu/emacs/EmacsSdk7FontDriver.java (Sdk7Typeface): (list): Remove logging for code that is mostly going to be unused. * java/org/gnu/emacs/EmacsService.java (ringBell, queryTree) (getScreenWidth, getScreenHeight, detectMouse): New functions. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView) (surfaceChanged, surfaceCreated, surfaceDestroyed): Add extra debug logging. Avoid deadlock in surfaceCreated. * java/org/gnu/emacs/EmacsView.java (EmacsView): Try very hard to make the SurfaceView respect Z order. It didn't work. (handleDirtyBitmap): Copy over the contents from the old bitmap. (explicitlyDirtyBitmap): New function. (onLayout): Don't dirty bitmap if unnecessary. (damageRect, swapBuffers): Don't synchronize so hard. (onTouchEvent): Call window.onTouchEvent instead. (moveChildToBack, raise, lower): New functions. * java/org/gnu/emacs/EmacsWindow.java (Coordinate): New subclass. (pointerMap, isMapped, isIconified, dontFocusOnMap) (dontAcceptFocus): New fields. (EmacsWindow): Don't immediately register unmapped window. (viewLayout): Send configure event outside the lock. (requestViewLayout): Explicitly dirty the bitmap. (mapWindow): Register the window now. Respect dontFocusOnMap. (unmapWindow): Unregister the window now. (figureChange, onTouchEvent): New functions. (onSomeKindOfMotionEvent): Handle scroll wheel events. (reparentTo, makeInputFocus, raise, lower, getWindowGeometry) (noticeIconified, noticeDeiconified, setDontAcceptFocus) (setDontFocusOnMap, getDontFocusOnMap): New functions. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (registerWindow, detachWindow): Synchronize. (noticeIconified, noticeDeiconified): New functions. (copyWindows): New function. * lisp/frame.el (frame-geometry, frame-edges) (mouse-absolute-pixel-position, set-mouse-absolute-pixel-position) (frame-list-z-order, frame-restack, display-mouse-p) (display-monitor-attributes-list): Implement on Android. * lisp/mwheel.el (mouse-wheel-down-event): (mouse-wheel-up-event): (mouse-wheel-left-event): (mouse-wheel-right-event): Define on Android. * src/android.c (struct android_emacs_service): New methods `ringBell', `queryTree', `getScreenWidth', `getScreenHeight', and `detectMouse'. (struct android_event_queue, android_init_events) (android_next_event, android_write_event): Remove write limit. (android_file_access_p): Handle directories correcty. (android_close): Fix coding style. (android_fclose): New function. (android_init_emacs_service): Initialize new methods. (android_reparent_window): Implement function. (android_bell, android_set_input_focus, android_raise_window) (android_lower_window, android_query_tree, android_get_geometry) (android_get_screen_width, android_get_screen_height) (android_get_mm_width, android_get_mm_height, android_detect_mouse) (android_set_dont_focus_on_map, android_set_dont_accept_focus): New functions. (struct android_dir): New structure. (android_opendir, android_readdir, android_closedir): New functions. (emacs_abort): Implement here on Android and poke debuggerd into generating a tombstone. * src/android.h: Update prototypes. * src/androidfns.c (android_set_parent_frame): New function. (android_default_font_parameter): Use sane font size by default. (Fx_display_pixel_width, Fx_display_pixel_height) (Fx_display_mm_width, Fx_display_mm_height) (Fx_display_monitor_attributes_list): Rename to start with `android-'. Implement. Fiddle with documentation to introduce Android specific nuances. (Fandroid_display_monitor_attributes_list): New function. (Fx_frame_geometry, frame_geometry): New function. (Fandroid_frame_geometry): Implement correctly. (Fx_frame_list_z_order): Rename to start with `android-'. (android_frame_list_z_order, Fandroid_frame_list_z_order): Implement. (Fx_frame_restack): Rename to start with `android-'. (Fandroid_frame_restack): ``Implement''. (Fx_mouse_absolute_pixel_position): Rename to start with `android-'. (Fandroid_mouse_absolute_pixel_position): ``Implement''. (Fx_set_mouse_absolute_pixel_position): Rename to start with `android-'. (Fandroid_set_mouse_absolute_pixel_position): ``Implement''. (Fandroid_detect_mouse): New function. (android_set_menu_bar_lines): Use FRAME_ANDROID_DRAWABLE when clearing area. (android_set_no_focus_on_map, android_set_no_accept_focus): New functions. (android_frame_parm_handlers): Register new frame parameter handlers. (syms_of_androidfns): Update appropriately. * src/androidfont.c (androidfont_draw): Use FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW. * src/androidgui.h (enum android_event_type): New events. (struct android_touch_event, struct android_wheel_event) (struct android_iconify_event): New structures. (union android_event): Add new events. * src/androidterm.c (android_clear_frame): Use FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW. (android_flash, android_ring_bell): Implement bell ringing. (android_toggle_invisible_pointer): Don't TODO function that can't be implemented. (show_back_buffer, android_flush_dirty_back_buffer_on): Check if a buffer flip is required before doing the flip. (android_lower_frame, android_raise_frame): Implement functions. (android_update_tools, android_find_tool): New functions. (handle_one_android_event): Handle new iconification, wheel and touch events. (android_read_socket): Implement pending-autoraise-frames. (android_frame_up_to_date): Implement bell ringing. (android_buffer_flipping_unblocked_hook): Check if a buffer flip is required before doing the flip. (android_focus_frame, android_frame_highlight) (android_frame_unhighlight): New function. (android_frame_rehighlight): Implement functions. (android_iconify_frame): Always display error. (android_set_alpha): Update commentary. (android_free_frame_resources): Free frame touch points. (android_scroll_run, android_flip_and_flush) (android_clear_rectangle, android_draw_fringe_bitmap) (android_draw_glyph_string_background, android_fill_triangle) (android_clear_point, android_draw_relief_rect) (android_draw_box_rect, android_draw_glyph_string_bg_rect) (android_draw_image_foreground, 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_clear_frame_area) (android_clear_under_internal_border, android_draw_hollow_cursor) (android_draw_bar_cursor, android_draw_vertical_window_border) (android_draw_window_divider): Use FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW for drawing operations. * src/androidterm.h (struct android_touch_point): New structure. (struct android_output): New fields. (FRAME_ANDROID_NEED_BUFFER_FLIP): New macro. * src/dired.c (emacs_readdir, open_directory) (directory_files_internal_unwind, read_dirent) (directory_files_internal, file_name_completion): Add indirection over readdir and opendir. Use android variants on Android. * src/dispnew.c (Fopen_termscript): * src/fileio.c (fclose_unwind): Use emacs_fclose. (Faccess_file): Call android_file_access_p. (file_accessible_directory_p): Append right suffix to Android assets directory. (do_auto_save_unwind): Use emacs_fclose. * src/keyboard.c (lispy_function_keys): Use right function key for page up and page down. (Fopen_dribble_file): Use emacs_fclose. * src/lisp.h: New prototype emacs_fclose. * src/lread.c (close_infile_unwind): Use emacs_fclose. * src/sfnt.c (sfnt_curve_is_flat): Fix area-squared computation. (sfnt_prepare_raster): Compute raster width and height consistently with outline building. (sfnt_build_outline_edges): Use the same offsets used to set offy and offx. (main): Adjust debug code. * src/sfntfont-android.c (sfntfont_android_saturate32): Delete function. (sfntfont_android_blend, sfntfont_android_blendrgb): Remove unnecessary debug code. (sfntfont_android_composite_bitmap): Prevent out of bounds write. (sfntfont_android_put_glyphs): Use FRAME_ANDROID_DRAWABLE. (init_sfntfont_android): Initialize Monospace Serif font to something sensible. * src/sfntfont.c (sfntfont_text_extents): Clear glyph metrics before summing up pcm. (sfntfont_draw): Use s->font instead of s->face->font. * src/sysdep.c (emacs_fclose): Wrap around android_fclose on android. * src/term.c (Fsuspend_tty): (delete_tty): Use emacs_fclose. * src/verbose.mk.in (AM_V_DX): Replace with D8 version. --- java/AndroidManifest.xml.in | 84 ++++ java/Makefile.in | 30 +- java/debug.sh | 45 +- java/org/gnu/emacs/EmacsActivity.java | 37 +- java/org/gnu/emacs/EmacsNative.java | 25 +- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 7 - java/org/gnu/emacs/EmacsService.java | 116 +++++ java/org/gnu/emacs/EmacsSurfaceView.java | 22 +- java/org/gnu/emacs/EmacsView.java | 189 ++++++-- java/org/gnu/emacs/EmacsWindow.java | 498 +++++++++++++++++++-- .../gnu/emacs/EmacsWindowAttachmentManager.java | 54 ++- 11 files changed, 991 insertions(+), 116 deletions(-) create mode 100644 java/AndroidManifest.xml.in (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in new file mode 100644 index 00000000000..b680137a9d0 --- /dev/null +++ b/java/AndroidManifest.xml.in @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/Makefile.in b/java/Makefile.in index 31bd22b5a2e..05e61dede89 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -18,13 +18,15 @@ # along with GNU Emacs. If not, see . top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +version = @version@ -include ${top_builddir}/src/verbose.mk SHELL = @SHELL@ JAVAC = @JAVAC@ AAPT = @AAPT@ -DX = @DX@ +D8 = @D8@ ZIPALIGN = @ZIPALIGN@ JARSIGNER = @JARSIGNER@ ANDROID_JAR = @ANDROID_JAR@ @@ -39,6 +41,12 @@ SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 JAVA_FILES = $(shell find . -type f -name *.java) CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) +# Compute the name for the Emacs application package. This should be: +# emacs---.apk + +ANDROID_MIN_SDK = @ANDROID_MIN_SDK@ +APK_NAME = emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk + # How this stuff works. # emacs.apk depends on emacs.apk-in, which is simply a ZIP archive @@ -55,7 +63,7 @@ CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) # assets/lisp/ .PHONY: emacs.apk-in all -all: emacs.apk +all: $(APK_NAME) # Binaries to cross-compile. CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \ @@ -118,6 +126,18 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml pushd install_temp; $(AAPT) add ../$@ `find assets -type f`; popd rm -rf install_temp +# Makefile itself. +.PRECIOUS: ../config.status Makefile +../config.status: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 + $(MAKE) -C $(dir $@) $(notdir $@) +Makefile: ../config.status $(top_builddir)/java/Makefile.in + $(MAKE) -C .. java/$@ + +# AndroidManifest.xml: +AndroidManifest.xml: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 \ + AndroidManifest.xml.in + pushd ..; ./config.status java/AndroidManifest.xml; popd + .SUFFIXES: .java .class .java.class &: $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< @@ -126,7 +146,7 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml # nested classes. classes.dex: $(CLASS_FILES) - $(AM_V_DX) $(DX) --classpath $(ANDROID_JAR) \ + $(AM_V_D8) $(D8) --classpath $(ANDROID_JAR) \ $(subst $$,\$$,$(shell find . -type f -name *.class)) # When emacs.keystore expires, regenerate it with: @@ -136,7 +156,7 @@ classes.dex: $(CLASS_FILES) .PHONY: clean maintainer-clean -emacs.apk: classes.dex emacs.apk-in emacs.keystore +$(APK_NAME): classes.dex emacs.apk-in emacs.keystore cp -f emacs.apk-in $@.unaligned $(AAPT) add $@.unaligned classes.dex $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore" @@ -144,7 +164,7 @@ emacs.apk: classes.dex emacs.apk-in emacs.keystore rm -f $@.unaligned clean: - rm -f emacs.apk emacs.apk-in *.dex *.unaligned *.class + rm -f *.apk emacs.apk-in *.dex *.unaligned *.class rm -rf install-temp find . -name '*.class' -delete diff --git a/java/debug.sh b/java/debug.sh index dd710dc31af..3e3e3d9c281 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -30,6 +30,7 @@ activity=org.gnu.emacs.EmacsActivity gdb_port=5039 jdb_port=64013 jdb=no +attach_existing=no while [ $# -gt 0 ]; do case "$1" in @@ -48,6 +49,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 " --attach-existing attach to an existing process" echo " --help print this message" echo "" echo "Available devices:" @@ -63,6 +65,9 @@ while [ $# -gt 0 ]; do "--port" ) gdb_port=$1 ;; + "--attach-existing" ) + attach_existing=yes + ;; "--" ) shift gdbargs=$@ @@ -120,30 +125,32 @@ package_pids=`awk -- '{ print $1 }' <<< $package_pids` -# Finally, kill each existing process. -for pid in $package_pids; do - echo "Killing existing process $pid..." - adb -s $device shell run-as $package kill -9 $pid &> /dev/null -done - -# Now run the main activity. This must be done as the adb user and -# not as the package user. -echo "Starting activity $activity and attaching debugger" - -# Exit if the activity could not be started. -adb -s $device shell am start -D "$package/$activity" -if [ ! $? ]; then - exit 1; -fi +if [ "$attach_existing" != "yes" ]; then + # Finally, kill each existing process. + for pid in $package_pids; do + echo "Killing existing process $pid..." + adb -s $device shell run-as $package kill -9 $pid &> /dev/null + done + + # Now run the main activity. This must be done as the adb user and + # not as the package user. + echo "Starting activity $activity and attaching debugger" + + # Exit if the activity could not be started. + adb -s $device shell am start -D "$package/$activity" + if [ ! $? ]; then + exit 1; + fi -# Now look for processes matching the package again. -package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` + # Now look for processes matching the package again. + package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` -# Next, remove lines matching "ps" itself. -package_pids=`awk -- '{ + # Next, remove lines matching "ps" itself. + package_pids=`awk -- '{ if (!match ($0, /(PID|ps)/)) print $1 }' <<< $package_pids` +fi pid=$package_pids num_pids=`wc -w <<< "$package_pids"` diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 67f411a38c3..2b661024842 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -48,6 +48,9 @@ public class EmacsActivity extends Activity /* The currently focused window. */ public static EmacsWindow focusedWindow; + /* Whether or not this activity is paused. */ + private boolean isPaused; + static { focusedActivities = new ArrayList (); @@ -60,7 +63,7 @@ public class EmacsActivity extends Activity focusedWindow = window; for (EmacsWindow child : window.children) - invalidateFocus1 (window); + invalidateFocus1 (child); } public static void @@ -103,6 +106,9 @@ public class EmacsActivity extends Activity /* Clear the window's pointer to this activity and remove the window's view. */ window.setConsumer (null); + + /* The window can't be iconified any longer. */ + window.noticeDeiconified (); layout.removeView (window.view); window = null; @@ -114,6 +120,8 @@ public class EmacsActivity extends Activity public void attachWindow (EmacsWindow child) { + Log.d (TAG, "attachWindow: " + child); + if (window != null) throw new IllegalStateException ("trying to attach window when one" + " already exists"); @@ -124,6 +132,10 @@ public class EmacsActivity extends Activity layout.addView (window.view); child.setConsumer (this); + /* If the activity is iconified, send that to the window. */ + if (isPaused) + window.noticeIconified (); + /* Invalidate the focus. */ invalidateFocus (); } @@ -148,6 +160,9 @@ public class EmacsActivity extends Activity { FrameLayout.LayoutParams params; + /* Set the theme to one without a title bar. */ + setTheme (android.R.style.Theme_NoTitleBar); + params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -192,4 +207,24 @@ public class EmacsActivity extends Activity invalidateFocus (); } + + @Override + public void + onPause () + { + isPaused = true; + + EmacsWindowAttachmentManager.MANAGER.noticeIconified (this); + super.onResume (); + } + + @Override + public void + onResume () + { + isPaused = false; + + EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); + super.onResume (); + } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index ae48ce30408..a11e509cd7f 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -96,11 +96,34 @@ public class EmacsNative long time, int state, int button); - /* Send an ANDROID_BUTTON_RELEASE event. */ + /* Send an ANDROID_BUTTON_RELEASE event. */ public static native void sendButtonRelease (short window, int x, int y, long time, int state, int button); + /* Send an ANDROID_TOUCH_DOWN event. */ + public static native void sendTouchDown (short window, int x, int y, + long time, int pointerID); + + /* Send an ANDROID_TOUCH_UP event. */ + public static native void sendTouchUp (short window, int x, int y, + long time, int pointerID); + + /* Send an ANDROID_TOUCH_MOVE event. */ + public static native void sendTouchMove (short window, int x, int y, + long time, int pointerID); + + /* Send an ANDROID_WHEEL event. */ + public static native void sendWheel (short window, int x, int y, + long time, int state, + float xDelta, float yDelta); + + /* Send an ANDROID_ICONIFIED event. */ + public static native void sendIconified (short window); + + /* Send an ANDROID_DEICONIFIED event. */ + public static native void sendDeiconified (short window); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 8a9426050ae..c0f24c7433a 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -149,8 +149,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } else familyName = fileName; - - Log.d (TAG, "Initialized new typeface " + familyName); } @Override @@ -321,17 +319,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver list = new LinkedList (); - Log.d (TAG, ("Looking for fonts matching font spec: " - + fontSpec.toString ())); - for (i = 0; i < typefaceList.length; ++i) { if (checkMatch (typefaceList[i], fontSpec)) list.add (new Sdk7FontEntity (typefaceList[i])); } - Log.d (TAG, "Found font entities: " + list.toString ()); - return (FontEntity[]) list.toArray (new FontEntity[0]); } diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 4444b7f1c56..01a1695f385 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -28,20 +28,27 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.view.View; +import android.view.InputDevice; import android.annotation.TargetApi; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; + import android.os.Build; import android.os.Looper; import android.os.IBinder; import android.os.Handler; +import android.os.Vibrator; +import android.os.VibratorManager; +import android.os.VibrationEffect; import android.util.Log; import android.util.DisplayMetrics; +import android.hardware.input.InputManager; + class Holder { T thing; @@ -250,4 +257,113 @@ public class EmacsService extends Service { window.clearArea (x, y, width, height); } + + @SuppressWarnings ("deprecation") + public void + ringBell () + { + Vibrator vibrator; + VibrationEffect effect; + VibratorManager vibratorManager; + Object tem; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + tem = getSystemService (Context.VIBRATOR_MANAGER_SERVICE); + vibratorManager = (VibratorManager) tem; + vibrator = vibratorManager.getDefaultVibrator (); + } + else + vibrator + = (Vibrator) getSystemService (Context.VIBRATOR_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + effect + = VibrationEffect.createOneShot (50, + VibrationEffect.DEFAULT_AMPLITUDE); + vibrator.vibrate (effect); + } + else + vibrator.vibrate (50); + } + + public short[] + queryTree (EmacsWindow window) + { + short[] array; + List windowList; + int i; + + if (window == null) + /* Just return all the windows without a parent. */ + windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows (); + else + windowList = window.children; + + array = new short[windowList.size () + 1]; + i = 1; + + array[0] = window.parent != null ? 0 : window.parent.handle; + + for (EmacsWindow treeWindow : windowList) + array[i++] = treeWindow.handle; + + return array; + } + + public int + getScreenWidth (boolean mmWise) + { + DisplayMetrics metrics; + + metrics = getResources ().getDisplayMetrics (); + + if (!mmWise) + return metrics.widthPixels; + else + return (int) ((metrics.widthPixels / metrics.xdpi) * 2540.0); + } + + public int + getScreenHeight (boolean mmWise) + { + DisplayMetrics metrics; + + metrics = getResources ().getDisplayMetrics (); + + if (!mmWise) + return metrics.heightPixels; + else + return (int) ((metrics.heightPixels / metrics.ydpi) * 2540.0); + } + + public boolean + detectMouse () + { + InputManager manager; + InputDevice device; + int[] ids; + int i; + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.JELLY_BEAN) + return false; + + manager = (InputManager) getSystemService (Context.INPUT_SERVICE); + ids = manager.getInputDeviceIds (); + + for (i = 0; i < ids.length; ++i) + { + device = manager.getInputDevice (ids[i]); + + if (device == null) + continue; + + if (device.supportsSource (InputDevice.SOURCE_MOUSE)) + return true; + } + + return false; + } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 5efb8882263..f713818d4bc 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -27,8 +27,12 @@ import android.os.Build; import android.graphics.Canvas; import android.graphics.Rect; +import android.util.Log; + public class EmacsSurfaceView extends SurfaceView { + private static final String TAG = "EmacsSurfaceView"; + public Object surfaceChangeLock; private boolean created; @@ -45,6 +49,7 @@ public class EmacsSurfaceView extends SurfaceView surfaceChanged (SurfaceHolder holder, int format, int width, int height) { + Log.d (TAG, "surfaceChanged: " + view); view.swapBuffers (); } @@ -54,9 +59,13 @@ public class EmacsSurfaceView extends SurfaceView { synchronized (surfaceChangeLock) { + Log.d (TAG, "surfaceCreated: " + view); created = true; - view.swapBuffers (); } + + /* Drop the lock when doing this, or a deadlock can + result. */ + view.swapBuffers (); } @Override @@ -65,6 +74,7 @@ public class EmacsSurfaceView extends SurfaceView { synchronized (surfaceChangeLock) { + Log.d (TAG, "surfaceDestroyed: " + view); created = false; } } @@ -93,6 +103,16 @@ public class EmacsSurfaceView extends SurfaceView return holder.lockCanvas (damage); } + @Override + protected void + onLayout (boolean changed, int left, int top, int right, + int bottom) + { + Log.d (TAG, ("onLayout: " + left + " " + top + " " + right + + " " + bottom + " -- " + changed + " visibility " + + getVisibility ())); + } + /* This method is only used during debugging when it seems damage isn't working correctly. */ diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 169a1e42ee3..41acabab97b 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -87,12 +87,27 @@ public class EmacsView extends ViewGroup /* Create the surface view. */ this.surfaceView = new EmacsSurfaceView (this); + this.surfaceView.setZOrderMediaOverlay (true); addView (this.surfaceView); + + /* Not sure exactly what this does but it makes things magically + work. Why is something as simple as XRaiseWindow so involved + on Android? */ + setChildrenDrawingOrderEnabled (true); + + /* Get rid of the foreground and background tint. */ + setBackgroundTintList (null); + setForegroundTintList (null); } private void handleDirtyBitmap () { + Bitmap oldBitmap; + + /* Save the old bitmap. */ + oldBitmap = bitmap; + /* Recreate the front and back buffer bitmaps. */ bitmap = Bitmap.createBitmap (bitmapDirty.width (), @@ -103,12 +118,23 @@ public class EmacsView extends ViewGroup /* And canvases. */ canvas = new Canvas (bitmap); - /* If Emacs is drawing to the bitmap right now from the - main thread, the image contents are lost until the next - ConfigureNotify and complete garbage. Sorry! */ + /* Copy over the contents of the old bitmap. */ + if (oldBitmap != null) + canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ()); + bitmapDirty = null; } + public synchronized void + explicitlyDirtyBitmap (Rect rect) + { + if (bitmapDirty == null + && (bitmap == null + || rect.width () != bitmap.getWidth () + || rect.height () != bitmap.getHeight ())) + bitmapDirty = rect; + } + public synchronized Bitmap getBitmap () { @@ -168,25 +194,31 @@ public class EmacsView extends ViewGroup View child; Rect windowRect; + count = getChildCount (); + if (changed || mustReportLayout) { mustReportLayout = false; window.viewLayout (left, top, right, bottom); } - if (changed) + if (changed + /* Check that a change has really happened. */ + && (bitmapDirty == null + || bitmapDirty.width () != right - left + || bitmapDirty.height () != bottom - top)) bitmapDirty = new Rect (left, top, right, bottom); - count = getChildCount (); - for (i = 0; i < count; ++i) { child = getChildAt (i); + Log.d (TAG, "onLayout: " + child); + if (child == surfaceView) /* The child is the surface view, so give it the entire view. */ - child.layout (left, top, right, bottom); + child.layout (0, 0, right - left, bottom - top); else if (child.getVisibility () != GONE) { if (!(child instanceof EmacsView)) @@ -201,59 +233,68 @@ public class EmacsView extends ViewGroup } } - public synchronized void + public void damageRect (Rect damageRect) { - damageRegion.union (damageRect); + synchronized (damageRegion) + { + damageRegion.union (damageRect); + } } /* This method is called from both the UI thread and the Emacs thread. */ - public synchronized void + public void swapBuffers (boolean force) { Canvas canvas; Rect damageRect; Bitmap bitmap; - if (damageRegion.isEmpty ()) - return; + /* Code must always take damageRegion, and then surfaceChangeLock, + never the other way around! */ - bitmap = getBitmap (); + synchronized (damageRegion) + { + if (damageRegion.isEmpty ()) + return; - /* Emacs must take the following lock to ensure the access to the - canvas occurs with the surface created. Otherwise, Android - will throttle calls to lockCanvas. */ + bitmap = getBitmap (); - synchronized (surfaceView.surfaceChangeLock) - { - damageRect = damageRegion.getBounds (); + /* Emacs must take the following lock to ensure the access to the + canvas occurs with the surface created. Otherwise, Android + will throttle calls to lockCanvas. */ - if (!surfaceView.isCreated ()) - return; + synchronized (surfaceView.surfaceChangeLock) + { + damageRect = damageRegion.getBounds (); - if (bitmap == null) - return; + if (!surfaceView.isCreated ()) + return; - /* Lock the canvas with the specified damage. */ - canvas = surfaceView.lockCanvas (damageRect); + if (bitmap == null) + return; - /* Return if locking the canvas failed. */ - if (canvas == null) - return; + /* Lock the canvas with the specified damage. */ + canvas = surfaceView.lockCanvas (damageRect); - /* Copy from the back buffer to the canvas. If damageRect was - made empty, then draw the entire back buffer. */ + /* Return if locking the canvas failed. */ + if (canvas == null) + return; - if (damageRect.isEmpty ()) - canvas.drawBitmap (bitmap, 0f, 0f, paint); - else - canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + /* Copy from the back buffer to the canvas. If damageRect was + made empty, then draw the entire back buffer. */ - /* Unlock the canvas and clear the damage. */ - surfaceView.unlockCanvasAndPost (canvas); - damageRegion.setEmpty (); + if (damageRect.isEmpty ()) + canvas.drawBitmap (bitmap, 0f, 0f, paint); + else + canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + + /* Unlock the canvas and clear the damage. */ + surfaceView.unlockCanvasAndPost (canvas); + damageRegion.setEmpty (); + } } } @@ -308,6 +349,78 @@ public class EmacsView extends ViewGroup public boolean onTouchEvent (MotionEvent motion) { - return window.onSomeKindOfMotionEvent (motion); + return window.onTouchEvent (motion); + } + + private void + moveChildToBack (View child) + { + int index; + + index = indexOfChild (child); + + if (index > 0) + { + detachViewFromParent (index); + + /* The view at 0 is the surface view. */ + attachViewToParent (child, 1, + child.getLayoutParams()); + } + } + + + /* The following two functions must not be called if the view has no + parent, or is parented to an activity. */ + + public void + raise () + { + EmacsView parent; + + parent = (EmacsView) getParent (); + + Log.d (TAG, "raise: parent " + parent); + + if (parent.indexOfChild (this) + == parent.getChildCount () - 1) + return; + + parent.bringChildToFront (this); + + /* Yes, all of this is really necessary! */ + parent.requestLayout (); + parent.invalidate (); + requestLayout (); + invalidate (); + + /* The surface view must be destroyed and recreated. */ + removeView (surfaceView); + addView (surfaceView, 0); + } + + public void + lower () + { + EmacsView parent; + + parent = (EmacsView) getParent (); + + Log.d (TAG, "lower: parent " + parent); + + if (parent.indexOfChild (this) == 1) + return; + + parent.moveChildToBack (this); + + /* Yes, all of this is really necessary! */ + parent.requestLayout (); + parent.invalidate (); + requestLayout (); + invalidate (); + + /* The surface view must be removed and attached again. */ + removeView (surfaceView); + addView (surfaceView, 0); } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index a9a24b61869..1f8596dba50 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -22,6 +22,7 @@ package org.gnu.emacs; import java.lang.IllegalStateException; import java.util.ArrayList; import java.util.List; +import java.util.HashMap; import android.graphics.Rect; import android.graphics.Canvas; @@ -50,9 +51,29 @@ import android.os.Build; Views are also drawables, meaning they can accept drawing requests. */ +/* Help wanted. What does not work includes `EmacsView.raise', + `EmacsView.lower', reparenting a window onto another window. + + All three are likely undocumented restrictions within + EmacsSurface. */ + public class EmacsWindow extends EmacsHandleObject implements EmacsDrawable { + private static final String TAG = "EmacsWindow"; + + private class Coordinate + { + /* Integral coordinate. */ + int x, y; + + Coordinate (int x, int y) + { + this.x = x; + this.y = y; + } + }; + /* The view associated with the window. */ public EmacsView view; @@ -60,12 +81,16 @@ public class EmacsWindow extends EmacsHandleObject private Rect rect; /* The parent window, or null if it is the root window. */ - private EmacsWindow parent; + public EmacsWindow parent; /* List of all children in stacking order. This must be kept consistent! */ public ArrayList children; + /* Map between pointer identifiers and last known position. Used to + compute which pointer changed upon a touch event. */ + private HashMap pointerMap; + /* The window consumer currently attached, if it exists. */ private EmacsWindowAttachmentManager.WindowConsumer attached; @@ -77,6 +102,14 @@ public class EmacsWindow extends EmacsHandleObject last button press or release event. */ private int lastButtonState, lastModifiers; + /* Whether or not the window is mapped, and whether or not it is + deiconified. */ + private boolean isMapped, isIconified; + + /* Whether or not to ask for focus upon being mapped, and whether or + not the window should be focusable. */ + private boolean dontFocusOnMap, dontAcceptFocus; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height) @@ -84,6 +117,7 @@ public class EmacsWindow extends EmacsHandleObject super (handle); rect = new Rect (x, y, x + width, y + height); + pointerMap = new HashMap (); /* Create the view from the context's UI thread. The window is unmapped, so the view is GONE. */ @@ -97,7 +131,7 @@ public class EmacsWindow extends EmacsHandleObject if (parent != null) { parent.children.add (this); - parent.view.post (new Runnable () { + EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override public void run () @@ -106,23 +140,6 @@ public class EmacsWindow extends EmacsHandleObject } }); } - else - EmacsService.SERVICE.runOnUiThread (new Runnable () { - @Override - public void - run () - { - EmacsWindowAttachmentManager manager; - - manager = EmacsWindowAttachmentManager.MANAGER; - - /* If parent is the root window, notice that there are new - children available for interested activites to pick - up. */ - - manager.registerWindow (EmacsWindow.this); - } - }); scratchGC = new EmacsGC ((short) 0); } @@ -159,7 +176,7 @@ public class EmacsWindow extends EmacsHandleObject + "children!"); /* Remove the view from its parent and make it invisible. */ - view.post (new Runnable () { + EmacsService.SERVICE.runOnUiThread (new Runnable () { public void run () { @@ -174,7 +191,7 @@ public class EmacsWindow extends EmacsHandleObject parent = (View) view.getParent (); - if (parent != null && attached == null) + if (parent != null) ((ViewGroup) parent).removeView (view); manager.detachWindow (EmacsWindow.this); @@ -199,24 +216,33 @@ public class EmacsWindow extends EmacsHandleObject public void viewLayout (int left, int top, int right, int bottom) { + int rectWidth, rectHeight; + synchronized (this) { rect.left = left; rect.top = top; rect.right = right; rect.bottom = bottom; - - EmacsNative.sendConfigureNotify (this.handle, - System.currentTimeMillis (), - left, top, rect.width (), - rect.height ()); } + + rectWidth = right - left; + rectHeight = bottom - top; + + EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rectWidth, + rectHeight); } public void requestViewLayout () { - view.post (new Runnable () { + /* This is necessary because otherwise subsequent drawing on the + Emacs thread may be lost. */ + view.explicitlyDirtyBitmap (rect); + + EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override public void run () @@ -261,28 +287,77 @@ public class EmacsWindow extends EmacsHandleObject public void mapWindow () { - view.post (new Runnable () { - @Override - public void - run () - { + if (isMapped) + return; - view.setVisibility (View.VISIBLE); - /* Eventually this should check no-focus-on-map. */ - view.requestFocus (); - } - }); + isMapped = true; + + if (parent == null) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsWindowAttachmentManager manager; + + /* Make the view visible, first of all. */ + view.setVisibility (View.VISIBLE); + + manager = EmacsWindowAttachmentManager.MANAGER; + + /* If parent is the root window, notice that there are new + children available for interested activites to pick + up. */ + manager.registerWindow (EmacsWindow.this); + + if (!getDontFocusOnMap ()) + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); + } + }); + } + else + { + /* Do the same thing as above, but don't register this + window. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.VISIBLE); + + if (!getDontFocusOnMap ()) + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); + } + }); + } } public void unmapWindow () { + if (!isMapped) + return; + + isMapped = false; + view.post (new Runnable () { @Override public void run () { + EmacsWindowAttachmentManager manager; + + manager = EmacsWindowAttachmentManager.MANAGER; + view.setVisibility (View.GONE); + + /* Now that the window is unmapped, unregister it as + well. */ + manager.detachWindow (EmacsWindow.this); } }); } @@ -413,6 +488,161 @@ public class EmacsWindow extends EmacsHandleObject return 4; } + /* Return the ID of the pointer which changed in EVENT. Value is -1 + if it could not be determined, else the pointer that changed, or + -2 if -1 would have been returned, but there is also a pointer + that is a mouse. */ + + private int + figureChange (MotionEvent event) + { + int pointerID, i, truncatedX, truncatedY, pointerIndex; + Coordinate coordinate; + boolean mouseFlag; + + /* pointerID is always initialized but the Java compiler is too + dumb to know that. */ + pointerID = -1; + mouseFlag = false; + + switch (event.getActionMasked ()) + { + case MotionEvent.ACTION_DOWN: + /* Primary pointer pressed with index 0. */ + + /* Detect mice. If this is a mouse event, give it to + onSomeKindOfMotionEvent. */ + if ((Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + && event.getToolType (0) == MotionEvent.TOOL_TYPE_MOUSE) + return -2; + + pointerID = event.getPointerId (0); + pointerMap.put (pointerID, + new Coordinate ((int) event.getX (0), + (int) event.getY (0))); + break; + + case MotionEvent.ACTION_UP: + /* Primary pointer released with index 0. */ + pointerID = event.getPointerId (0); + pointerMap.remove (pointerID); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + /* New pointer. Find the pointer ID from the index and place + it in the map. */ + pointerIndex = event.getActionIndex (); + pointerID = event.getPointerId (pointerIndex); + pointerMap.put (pointerID, + new Coordinate ((int) event.getX (pointerID), + (int) event.getY (pointerID))); + break; + + case MotionEvent.ACTION_POINTER_UP: + /* Pointer removed. Remove it from the map. */ + pointerIndex = event.getActionIndex (); + pointerID = event.getPointerId (pointerIndex); + pointerMap.remove (pointerID); + break; + + default: + + /* Loop through each pointer in the event. */ + for (i = 0; i < event.getPointerCount (); ++i) + { + pointerID = event.getPointerId (i); + + /* Look up that pointer in the map. */ + coordinate = pointerMap.get (pointerID); + + if (coordinate != null) + { + /* See if coordinates have changed. */ + truncatedX = (int) event.getX (i); + truncatedY = (int) event.getY (i); + + if (truncatedX != coordinate.x + || truncatedY != coordinate.y) + { + /* The pointer changed. Update the coordinate and + break out of the loop. */ + coordinate.x = truncatedX; + coordinate.y = truncatedY; + + break; + } + } + + /* See if this is a mouse. If so, set the mouseFlag. */ + if ((Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + && event.getToolType (i) == MotionEvent.TOOL_TYPE_MOUSE) + mouseFlag = true; + } + + /* Set the pointer ID to -1 if the loop failed to find any + changed pointer. If a mouse pointer was found, set it to + -2. */ + if (i == event.getPointerCount ()) + pointerID = (mouseFlag ? -2 : -1); + } + + /* Return the pointer ID. */ + return pointerID; + } + + public boolean + onTouchEvent (MotionEvent event) + { + int pointerID, index; + + /* Extract the ``touch ID'' (or in Android, the ``pointer + ID''.) */ + pointerID = figureChange (event); + + if (pointerID < 0) + { + /* If this is a mouse event, give it to + onSomeKindOfMotionEvent. */ + if (pointerID == -2) + return onSomeKindOfMotionEvent (event); + + return false; + } + + /* Find the pointer index corresponding to the event. */ + index = event.findPointerIndex (pointerID); + + switch (event.getActionMasked ()) + { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + /* Touch down event. */ + EmacsNative.sendTouchDown (this.handle, (int) event.getX (index), + (int) event.getY (index), + event.getEventTime (), pointerID); + return true; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + /* Touch up event. */ + EmacsNative.sendTouchUp (this.handle, (int) event.getX (index), + (int) event.getY (index), + event.getEventTime (), pointerID); + return true; + + case MotionEvent.ACTION_MOVE: + /* Pointer motion event. */ + EmacsNative.sendTouchMove (this.handle, (int) event.getX (index), + (int) event.getY (index), + event.getEventTime (), pointerID); + return true; + } + + return false; + } + public boolean onSomeKindOfMotionEvent (MotionEvent event) { @@ -472,13 +702,201 @@ public class EmacsWindow extends EmacsHandleObject case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: - /* Emacs must return true even though touch events are not yet - handled, because the value of this function is used by the - system to decide whether or not Emacs gets ACTION_MOVE + /* Emacs must return true even though touch events are not + handled here, because the value of this function is used by + the system to decide whether or not Emacs gets ACTION_MOVE events. */ return true; + + case MotionEvent.ACTION_SCROLL: + /* Send a scroll event with the specified deltas. */ + EmacsNative.sendWheel (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + event.getAxisValue (MotionEvent.AXIS_HSCROLL), + event.getAxisValue (MotionEvent.AXIS_VSCROLL)); + return true; } return false; } + + public void + reparentTo (final EmacsWindow otherWindow, int x, int y) + { + int width, height; + + /* Reparent this window to the other window. */ + + if (parent != null) + parent.children.remove (this); + + if (otherWindow != null) + otherWindow.children.add (this); + + parent = otherWindow; + + /* Move this window to the new location. */ + synchronized (this) + { + width = rect.width (); + height = rect.height (); + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + } + + /* Now do the work necessary on the UI thread to reparent the + window. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsWindowAttachmentManager manager; + View parent; + + /* First, detach this window if necessary. */ + manager = EmacsWindowAttachmentManager.MANAGER; + manager.detachWindow (EmacsWindow.this); + + /* Also unparent this view. */ + parent = (View) view.getParent (); + + if (parent != null) + ((ViewGroup) parent).removeView (view); + + /* Next, either add this window as a child of the new + parent's view, or make it available again. */ + if (otherWindow != null) + otherWindow.view.addView (view); + else if (EmacsWindow.this.isMapped) + manager.registerWindow (EmacsWindow.this); + + /* Request relayout. */ + view.requestLayout (); + } + }); + } + + public void + makeInputFocus (long time) + { + /* TIME is currently ignored. Request the input focus now. */ + + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.requestFocus (); + } + }); + } + + public void + raise () + { + /* This does nothing here. */ + if (parent == null) + return; + + /* Remove and add this view again. */ + parent.children.remove (this); + parent.children.add (this); + + /* Request a relayout. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.raise (); + } + }); + } + + public void + lower () + { + /* This does nothing here. */ + if (parent == null) + return; + + /* Remove and add this view again. */ + parent.children.remove (this); + parent.children.add (this); + + /* Request a relayout. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.lower (); + } + }); + } + + public int[] + getWindowGeometry () + { + int[] array; + Rect rect; + + array = new int[4]; + rect = getGeometry (); + + array[0] = rect.left; + array[1] = rect.top; + array[2] = rect.width (); + array[3] = rect.height (); + + return array; + } + + public void + noticeIconified () + { + isIconified = true; + EmacsNative.sendIconified (this.handle); + } + + public void + noticeDeiconified () + { + isIconified = false; + EmacsNative.sendDeiconified (this.handle); + } + + public synchronized void + setDontAcceptFocus (final boolean dontAcceptFocus) + { + this.dontAcceptFocus = dontAcceptFocus; + + /* Update the view's focus state. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.setFocusable (!dontAcceptFocus); + view.setFocusableInTouchMode (!dontAcceptFocus); + } + }); + } + + public synchronized void + setDontFocusOnMap (final boolean dontFocusOnMap) + { + this.dontFocusOnMap = dontFocusOnMap; + } + + public synchronized boolean + getDontFocusOnMap () + { + return dontFocusOnMap; + } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 34be2ab8789..15eb3bb65c2 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -68,7 +69,7 @@ public class EmacsWindowAttachmentManager }; private List consumers; - private List windows; + public List windows; public EmacsWindowAttachmentManager () @@ -98,12 +99,19 @@ public class EmacsWindowAttachmentManager EmacsNative.sendWindowAction ((short) 0, 0); } - public void + public synchronized void registerWindow (EmacsWindow window) { Intent intent; - Log.d (TAG, "registerWindow " + window); + Log.d (TAG, "registerWindow (maybe): " + window); + + if (windows.contains (window)) + /* The window is already registered. */ + return; + + Log.d (TAG, "registerWindow: " + window); + windows.add (window); for (WindowConsumer consumer : consumers) @@ -146,7 +154,7 @@ public class EmacsWindowAttachmentManager consumers.remove (consumer); } - public void + public synchronized void detachWindow (EmacsWindow window) { WindowConsumer consumer; @@ -162,5 +170,43 @@ public class EmacsWindowAttachmentManager consumers.remove (consumer); consumer.destroy (); } + + windows.remove (window); + } + + public void + noticeIconified (WindowConsumer consumer) + { + EmacsWindow window; + + Log.d (TAG, "noticeIconified " + consumer); + + /* If a window is attached, send the appropriate iconification + events. */ + window = consumer.getAttachedWindow (); + + if (window != null) + window.noticeIconified (); + } + + public void + noticeDeiconified (WindowConsumer consumer) + { + EmacsWindow window; + + Log.d (TAG, "noticeDeiconified " + consumer); + + /* If a window is attached, send the appropriate iconification + events. */ + window = consumer.getAttachedWindow (); + + if (window != null) + window.noticeDeiconified (); + } + + public synchronized List + copyWindows () + { + return new ArrayList (windows); } }; -- cgit v1.2.1 From 0b61fd7700711fa83bfbe3f6ad84e268f1492bdd Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 13 Jan 2023 16:05:08 +0800 Subject: * java/AndroidManifest.xml: Remove file that is now generated. --- java/AndroidManifest.xml | 59 ------------------------------------------------ 1 file changed, 59 deletions(-) delete mode 100644 java/AndroidManifest.xml (limited to 'java') diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml deleted file mode 100644 index 06417017ae3..00000000000 --- a/java/AndroidManifest.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- cgit v1.2.1 From 24910d3f375a11360c66b742e1054b55e9e25ccc Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 13 Jan 2023 16:08:11 +0800 Subject: Fix crashes in Android port * java/org/gnu/emacs/EmacsService.java (queryTree): Fix NULL pointer dereference. * src/android.c (android_query_tree): Set *nchildren_return. --- java/org/gnu/emacs/EmacsService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 01a1695f385..c008300dd3a 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -304,7 +304,9 @@ public class EmacsService extends Service array = new short[windowList.size () + 1]; i = 1; - array[0] = window.parent != null ? 0 : window.parent.handle; + array[0] = (window == null + ? 0 : (window.parent != null + ? window.parent.handle : 0)); for (EmacsWindow treeWindow : windowList) array[i++] = treeWindow.handle; -- cgit v1.2.1 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. --- java/org/gnu/emacs/EmacsThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java') 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); -- cgit v1.2.1 From 2b87ab7b27163fbd7b6b64c5a44e26b0e692c00a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 14 Jan 2023 22:12:16 +0800 Subject: Update Android port * java/Makefile.in (clean): Fix distclean and bootstrap-clean rules. * java/debug.sh (jdb_port): (attach_existing): (num_pids): (line): Add new options to upload a gdbserver binary to the device. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): Make focusedActivities public. * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New class. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix bounds computation. * java/org/gnu/emacs/EmacsGC.java (markDirty): Set stroke width explicitly. * java/org/gnu/emacs/EmacsService.java (EmacsService) (getLocationOnScreen, nameKeysym): New functions. * java/org/gnu/emacs/EmacsView.java (EmacsView): Disable focus highlight. (onCreateContextMenu, popupMenu, cancelPopupMenu): New functions. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): Implement a kind of ``override redirect'' window for tooltips. * src/android.c (struct android_emacs_service): New method `name_keysym'. (android_run_select_thread, android_init_events): (android_select): Release select thread on semaphores instead of signals to avoid one nasty race on SIGUSR2 delivery. (android_init_emacs_service): Initialize new method. (android_create_window): Handle CW_OVERRIDE_REDIRECT. (android_move_resize_window, android_map_raised) (android_translate_coordinates, android_get_keysym_name) (android_build_string, android_exception_check): New functions. * src/android.h: Update prototypes. * src/androidfns.c (android_set_parent_frame, Fx_create_frame) (unwind_create_tip_frame, android_create_tip_frame) (android_hide_tip, compute_tip_xy, Fx_show_tip, Fx_hide_tip) (syms_of_androidfns): Implement tooltips and iconification reporting. * src/androidgui.h (enum android_window_value_mask): Add CWOverrideRedirect. (struct android_set_window_attributes): Add `override_redirect'. (ANDROID_IS_MODIFIER_KEY): Recognize Caps Lock. * src/androidmenu.c (struct android_emacs_context_menu): New struct. (android_init_emacs_context_menu, android_unwind_local_frame) (android_push_local_frame, android_menu_show, init_androidmenu): New functions. * src/androidterm.c (handle_one_android_event): Fix NULL pointer dereference. (android_fullscreen_hook): Handle fullscreen correctly. (android_draw_box_rect): Fix top line. (get_keysym_name): Implement function. (android_create_terminal): Remove scroll bar stubs and add menu hook. * src/androidterm.h: Update prototypes. * src/emacs.c (android_emacs_init): Initialize androidmenu.c. * xcompile/Makefile.in: Fix clean rules. --- java/Makefile.in | 2 +- java/debug.sh | 110 ++++++++++----- java/org/gnu/emacs/EmacsActivity.java | 2 +- java/org/gnu/emacs/EmacsContextMenu.java | 213 +++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsDrawRectangle.java | 2 +- java/org/gnu/emacs/EmacsGC.java | 1 + java/org/gnu/emacs/EmacsService.java | 43 +++++- java/org/gnu/emacs/EmacsView.java | 47 +++++++ java/org/gnu/emacs/EmacsWindow.java | 161 +++++++++++++++++++--- 9 files changed, 523 insertions(+), 58 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsContextMenu.java (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 05e61dede89..c539fb0f1fb 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -168,4 +168,4 @@ clean: rm -rf install-temp find . -name '*.class' -delete -maintainer-clean: clean +maintainer-clean distclean bootstrap-clean: clean diff --git a/java/debug.sh b/java/debug.sh index 3e3e3d9c281..aa80aeeebcd 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -31,6 +31,7 @@ gdb_port=5039 jdb_port=64013 jdb=no attach_existing=no +gdbserver= while [ $# -gt 0 ]; do case "$1" in @@ -41,6 +42,7 @@ while [ $# -gt 0 ]; do echo "You must specify an argument to --device" exit 1 fi + shift ;; "--help" ) echo "Usage: $progname [options] -- [gdb options]" @@ -50,6 +52,7 @@ while [ $# -gt 0 ]; do echo " --jdb-port PORT run the JDB server on a specific port" echo " --jdb run JDB instead of GDB" echo " --attach-existing attach to an existing process" + echo " --gdbserver BINARY upload and use the specified gdbserver binary" echo " --help print this message" echo "" echo "Available devices:" @@ -62,9 +65,18 @@ while [ $# -gt 0 ]; do "--jdb" ) jdb=yes ;; + "--gdbserver" ) + shift + gdbserver=$1 + ;; "--port" ) + shift gdb_port=$1 ;; + "--jdb-port" ) + shift + jdb_port=$1 + ;; "--attach-existing" ) attach_existing=yes ;; @@ -170,46 +182,71 @@ elif [ -z $package_pids ]; then exit 1 fi -# Start JDB to make the wait dialog disappear. -echo "Attaching JDB to unblock the application." -adb -s $device forward --remove-all -adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" +# This isn't necessary when attaching gdb to an existing process. +if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then + # Start JDB to make the wait dialog disappear. + echo "Attaching JDB to unblock the application." + adb -s $device forward --remove-all + adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" -if [ ! $? ]; then - echo "Failed to forward jdwp:$pid to $jdb_port!" - echo "Perhaps you need to specify a different port with --port?" - exit 1; -fi + if [ ! $? ]; then + echo "Failed to forward jdwp:$pid to $jdb_port!" + echo "Perhaps you need to specify a different port with --port?" + exit 1; + fi -jdb_command="jdb -connect \ + jdb_command="jdb -connect \ com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port" -if [ $jdb = "yes" ]; then - # Just start JDB and then exit - $jdb_command - exit 1 -fi + if [ $jdb = "yes" ]; then + # Just start JDB and then exit + $jdb_command + exit 1 + fi -exec 4<> /tmp/file-descriptor-stamp + exec 4<> /tmp/file-descriptor-stamp -# Now run JDB with IO redirected to file descriptor 4 in a subprocess. -$jdb_command <&4 >&4 & + # Now run JDB with IO redirected to file descriptor 4 in a subprocess. + $jdb_command <&4 >&4 & -character= -# Next, wait until the prompt is found. -while read -n1 -u 4 character; do - if [ "$character" = ">" ]; then - echo "JDB attached successfully" - break; - fi -done + character= + # Next, wait until the prompt is found. + while read -n1 -u 4 character; do + if [ "$character" = ">" ]; then + echo "JDB attached successfully" + break; + fi + done +fi + +# See if gdbserver has to be uploaded +if [ -z "$gdbserver" ]; then + gdbserver_bin=/system/bin/gdbserver +else + gdbserver_bin=/data/local/tmp/gdbserver + + # Upload the specified gdbserver binary to the device. + adb -s $device push "$gdbserver" "$gdbserver_bin" + adb -s $device shell chmod +x "$gdbserver_bin" +fi # Now start gdbserver on the device asynchronously. echo "Attaching gdbserver to $pid on $device..." exec 5<> /tmp/file-descriptor-stamp -adb -s $device shell run-as $package /system/bin/gdbserver --once \ - "+debug.$package_uid.socket" --attach $pid >&5 & + +if [ -z "$gdbserver" ]; then + adb -s $device shell run-as $package $gdbserver_bin --once \ + "+debug.$package_uid.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:$app_data_dir/debug.$package_uid.socket" +else + # Normally the program cannot access $gdbserver_bin when it is + # placed in /data/local/tmp. + adb -s $device shell $gdbserver_bin --once \ + "+/data/local/tmp/debug.$package_uid.socket" \ + --attach $pid >&5 & + gdb_socket="localfilesystem:/data/local/tmp/debug.$package_uid.socket" +fi # Wait until gdbserver successfully runs. line= @@ -227,16 +264,17 @@ while read -u 5 line; do esac done -# Send EOF to JDB to make it go away. This will also cause Android to -# allow Emacs to continue executing. -echo "Making JDB go away..." -echo "exit" >&4 -read -u 4 line -echo "JDB has gone away with $line" +if [ "$attach_existing" != "yes" ]; then + # Send EOF to JDB to make it go away. This will also cause + # Android to allow Emacs to continue executing. + echo "Making JDB go away..." + echo "exit" >&4 + read -u 4 line + echo "JDB has gone away with $line" +fi # Forward the gdb server port here. -adb -s $device forward "tcp:$gdb_port" \ - "localfilesystem:$app_data_dir/debug.$package_uid.socket" +adb -s $device forward "tcp:$gdb_port" $gdb_socket if [ ! $? ]; then echo "Failed to forward $app_data_dir/debug.$package_uid.socket" echo "to $gdb_port! Perhaps you need to specify a different port" diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 2b661024842..4cd286d1e89 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -43,7 +43,7 @@ public class EmacsActivity extends Activity private FrameLayout layout; /* List of activities with focus. */ - private static List focusedActivities; + public static List focusedActivities; /* The currently focused window. */ public static EmacsWindow focusedWindow; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java new file mode 100644 index 00000000000..8d7ae08b257 --- /dev/null +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -0,0 +1,213 @@ +/* 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.util.List; +import java.util.ArrayList; + +import android.content.Context; +import android.content.Intent; + +import android.os.Bundle; + +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import android.widget.PopupMenu; + +/* Context menu implementation. This object is built from JNI and + describes a menu hiearchy. Then, `inflate' can turn it into an + Android menu, which can be turned into a popup (or other kind of) + menu. */ + +public class EmacsContextMenu +{ + private class Item + { + public int itemID; + public String itemName; + public EmacsContextMenu subMenu; + public boolean isEnabled; + }; + + public List menuItems; + public String title; + private EmacsContextMenu parent; + + /* Create a context menu with no items inside and the title TITLE, + which may be NULL. */ + + public static EmacsContextMenu + createContextMenu (String title) + { + EmacsContextMenu menu; + + menu = new EmacsContextMenu (); + menu.menuItems = new ArrayList (); + menu.title = title; + + return menu; + } + + /* Add a normal menu item to the context menu with the id ITEMID and + the name ITEMNAME. Enable it if ISENABLED, else keep it + disabled. */ + + public void + addItem (int itemID, String itemName, boolean isEnabled) + { + Item item; + + item = new Item (); + item.itemID = itemID; + item.itemName = itemName; + item.isEnabled = isEnabled; + + menuItems.add (item); + } + + /* Create a disabled menu item with the name ITEMNAME. */ + + public void + addPane (String itemName) + { + Item item; + + item = new Item (); + item.itemName = itemName; + + menuItems.add (item); + } + + /* Add a submenu to the context menu with the specified title and + item name. */ + + public EmacsContextMenu + addSubmenu (String itemName, String title) + { + EmacsContextMenu submenu; + Item item; + + item = new Item (); + item.itemID = 0; + item.itemName = itemName; + item.subMenu = createContextMenu (title); + item.subMenu.parent = this; + + menuItems.add (item); + return item.subMenu; + } + + /* Add the contents of this menu to MENU. */ + + private void + inflateMenuItems (Menu menu) + { + Intent intent; + MenuItem menuItem; + Menu submenu; + + for (Item item : menuItems) + { + if (item.subMenu != null) + { + /* This is a submenu. Create the submenu and add the + contents of the menu to it. */ + submenu = menu.addSubMenu (item.itemName); + inflateMenuItems (submenu); + } + else + { + menuItem = menu.add (item.itemName); + + /* If the item ID is zero, then disable the item. */ + if (item.itemID == 0 || !item.isEnabled) + menuItem.setEnabled (false); + } + } + } + + /* Enter the items in this context menu to MENU. Create each menu + item with an Intent containing a Bundle, where the key + "emacs:menu_item_hi" maps to the high 16 bits of the + corresponding item ID, and the key "emacs:menu_item_low" maps to + the low 16 bits of the item ID. */ + + public void + expandTo (Menu menu) + { + inflateMenuItems (menu); + } + + /* Return the parent or NULL. */ + + public EmacsContextMenu + parent () + { + return parent; + } + + /* Like display, but does the actual work and runs in the main + thread. */ + + private boolean + display1 (EmacsWindow window, int xPosition, int yPosition) + { + return window.view.popupMenu (this, xPosition, yPosition); + } + + /* Display this context menu on WINDOW, at xPosition and + yPosition. */ + + public boolean + display (final EmacsWindow window, final int xPosition, + final int yPosition) + { + Runnable runnable; + final Holder rc; + + rc = new Holder (); + + runnable = new Runnable () { + @Override + public void + run () + { + synchronized (this) + { + rc.thing = display1 (window, xPosition, yPosition); + notify (); + } + } + }; + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + + return rc.thing; + } +}; diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index b42e9556e8c..84ff498847b 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -59,7 +59,7 @@ public class EmacsDrawRectangle } paint = gc.gcPaint; - rect = new Rect (x, y, x + width, y + height); + rect = new Rect (x + 1, y + 1, x + width, y + height); paint.setStyle (Paint.Style.STROKE); diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index caa5c91edd4..c579625f3f7 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -93,6 +93,7 @@ public class EmacsGC extends EmacsHandleObject else real_clip_rects = clip_rects; + gcPaint.setStrokeWidth (1f); gcPaint.setColor (foreground | 0xff000000); gcPaint.setXfermode (function == GC_XOR ? xorAlu : srcInAlu); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index c008300dd3a..f935b63fa0d 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -29,6 +29,7 @@ import android.graphics.Point; import android.view.View; import android.view.InputDevice; +import android.view.KeyEvent; import android.annotation.TargetApi; import android.app.Service; @@ -150,13 +151,13 @@ public class EmacsService extends Service /* Functions from here on must only be called from the Emacs thread. */ - void + public void runOnUiThread (Runnable runnable) { handler.post (runnable); } - EmacsView + public EmacsView getEmacsView (final EmacsWindow window, final int visibility, final boolean isFocusedByDefault) { @@ -196,6 +197,38 @@ public class EmacsService extends Service return view.thing; } + public void + getLocationOnScreen (final EmacsView view, final int[] coordinates) + { + Runnable runnable; + + runnable = new Runnable () { + public void + run () + { + synchronized (this) + { + view.getLocationOnScreen (coordinates); + notify (); + } + } + }; + + synchronized (runnable) + { + runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + } + public void fillRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) @@ -368,4 +401,10 @@ public class EmacsService extends Service return false; } + + public String + nameKeysym (int keysym) + { + return KeyEvent.keyCodeToString (keysym); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 41acabab97b..1391f630be0 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -21,6 +21,7 @@ package org.gnu.emacs; import android.content.res.ColorStateList; +import android.view.ContextMenu; import android.view.View; import android.view.KeyEvent; import android.view.MotionEvent; @@ -73,6 +74,12 @@ public class EmacsView extends ViewGroup next call to getBitmap. */ private Rect bitmapDirty; + /* Whether or not a popup is active. */ + private boolean popupActive; + + /* The current context menu. */ + private EmacsContextMenu contextMenu; + public EmacsView (EmacsWindow window) { @@ -98,6 +105,10 @@ public class EmacsView extends ViewGroup /* Get rid of the foreground and background tint. */ setBackgroundTintList (null); setForegroundTintList (null); + + /* Get rid of the default focus highlight. */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) + setDefaultFocusHighlightEnabled (false); } private void @@ -423,4 +434,40 @@ public class EmacsView extends ViewGroup removeView (surfaceView); addView (surfaceView, 0); } + + @Override + protected void + onCreateContextMenu (ContextMenu menu) + { + if (contextMenu == null) + return; + + contextMenu.expandTo (menu); + } + + public boolean + popupMenu (EmacsContextMenu menu, int xPosition, + int yPosition) + { + if (popupActive) + return false; + + contextMenu = menu; + + /* On API 21 or later, use showContextMenu (float, float). */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) + return showContextMenu ((float) xPosition, (float) yPosition); + else + return showContextMenu (); + } + + public void + cancelPopupMenu () + { + if (!popupActive) + throw new IllegalStateException ("cancelPopupMenu called without" + + " popupActive set"); + + contextMenu = null; + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 1f8596dba50..6effa79d1a4 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -24,16 +24,22 @@ import java.util.ArrayList; import java.util.List; import java.util.HashMap; +import android.content.Context; + import android.graphics.Rect; import android.graphics.Canvas; import android.graphics.Bitmap; import android.graphics.Point; +import android.graphics.PixelFormat; import android.view.View; +import android.view.ViewManager; import android.view.ViewGroup; +import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.InputDevice; +import android.view.WindowManager; import android.content.Intent; import android.util.Log; @@ -110,9 +116,17 @@ public class EmacsWindow extends EmacsHandleObject not the window should be focusable. */ private boolean dontFocusOnMap, dontAcceptFocus; + /* Whether or not the window is override-redirect. An + override-redirect window always has its own system window. */ + private boolean overrideRedirect; + + /* The window manager that is the parent of this window. NULL if + there is no such window manager. */ + private WindowManager windowManager; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, - int width, int height) + int width, int height, boolean overrideRedirect) { super (handle); @@ -124,6 +138,7 @@ public class EmacsWindow extends EmacsHandleObject view = EmacsService.SERVICE.getEmacsView (this, View.GONE, parent == null); this.parent = parent; + this.overrideRedirect = overrideRedirect; /* Create the list of children. */ children = new ArrayList (); @@ -180,7 +195,7 @@ public class EmacsWindow extends EmacsHandleObject public void run () { - View parent; + ViewManager parent; EmacsWindowAttachmentManager manager; if (EmacsActivity.focusedWindow == EmacsWindow.this) @@ -189,10 +204,15 @@ public class EmacsWindow extends EmacsHandleObject manager = EmacsWindowAttachmentManager.MANAGER; view.setVisibility (View.GONE); - parent = (View) view.getParent (); + /* If the window manager is set, use that instead. */ + if (windowManager != null) + parent = windowManager; + else + parent = (ViewManager) view.getParent (); + windowManager = null; if (parent != null) - ((ViewGroup) parent).removeView (view); + parent.removeView (view); manager.detachWindow (EmacsWindow.this); } @@ -247,6 +267,10 @@ public class EmacsWindow extends EmacsHandleObject public void run () { + if (overrideRedirect) + /* Set the layout parameters again. */ + view.setLayoutParams (getWindowLayoutParams ()); + view.mustReportLayout = true; view.requestLayout (); } @@ -284,6 +308,39 @@ public class EmacsWindow extends EmacsHandleObject } } + private WindowManager.LayoutParams + getWindowLayoutParams () + { + WindowManager.LayoutParams params; + int flags, type; + Rect rect; + + flags = 0; + rect = getGeometry (); + flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + + params + = new WindowManager.LayoutParams (rect.width (), rect.height (), + rect.left, rect.top, + type, flags, + PixelFormat.RGBA_8888); + params.gravity = Gravity.TOP | Gravity.LEFT; + return params; + } + + private Context + findSuitableActivityContext () + { + /* Find a recently focused activity. */ + if (!EmacsActivity.focusedActivities.isEmpty ()) + return EmacsActivity.focusedActivities.get (0); + + /* Return the service context, which probably won't work. */ + return EmacsService.SERVICE; + } + public void mapWindow () { @@ -300,20 +357,60 @@ public class EmacsWindow extends EmacsHandleObject run () { EmacsWindowAttachmentManager manager; + WindowManager windowManager; + Context ctx; + Object tem; + WindowManager.LayoutParams params; /* Make the view visible, first of all. */ view.setVisibility (View.VISIBLE); - manager = EmacsWindowAttachmentManager.MANAGER; - - /* If parent is the root window, notice that there are new - children available for interested activites to pick - up. */ - manager.registerWindow (EmacsWindow.this); - - if (!getDontFocusOnMap ()) - /* Eventually this should check no-focus-on-map. */ - view.requestFocus (); + if (!overrideRedirect) + { + manager = EmacsWindowAttachmentManager.MANAGER; + + /* If parent is the root window, notice that there are new + children available for interested activites to pick + up. */ + manager.registerWindow (EmacsWindow.this); + + if (!getDontFocusOnMap ()) + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); + } + else + { + /* But if the window is an override-redirect window, + then: + + - Find an activity that is currently active. + + - Map the window as a panel on top of that + activity using the system window manager. */ + + ctx = findSuitableActivityContext (); + tem = ctx.getSystemService (Context.WINDOW_SERVICE); + windowManager = (WindowManager) tem; + + /* Calculate layout parameters. */ + params = getWindowLayoutParams (); + view.setLayoutParams (params); + + /* Attach the view. */ + try + { + windowManager.addView (view, params); + + /* Record the window manager being used in the + EmacsWindow object. */ + EmacsWindow.this.windowManager = windowManager; + } + catch (Exception e) + { + Log.w (TAG, + "failed to attach override-redirect window, " + e); + } + } } }); } @@ -355,6 +452,11 @@ public class EmacsWindow extends EmacsHandleObject view.setVisibility (View.GONE); + /* Detach the view from the window manager if possible. */ + if (windowManager != null) + windowManager.removeView (view); + windowManager = null; + /* Now that the window is unmapped, unregister it as well. */ manager.detachWindow (EmacsWindow.this); @@ -756,17 +858,23 @@ public class EmacsWindow extends EmacsHandleObject run () { EmacsWindowAttachmentManager manager; - View parent; + ViewManager parent; /* First, detach this window if necessary. */ manager = EmacsWindowAttachmentManager.MANAGER; manager.detachWindow (EmacsWindow.this); /* Also unparent this view. */ - parent = (View) view.getParent (); + + /* If the window manager is set, use that instead. */ + if (windowManager != null) + parent = windowManager; + else + parent = (ViewManager) view.getParent (); + windowManager = null; if (parent != null) - ((ViewGroup) parent).removeView (view); + parent.removeView (view); /* Next, either add this window as a child of the new parent's view, or make it available again. */ @@ -899,4 +1007,23 @@ public class EmacsWindow extends EmacsHandleObject { return dontFocusOnMap; } + + public int[] + translateCoordinates (int x, int y) + { + int[] array; + + /* This is supposed to translate coordinates to the root + window. */ + array = new int[2]; + EmacsService.SERVICE.getLocationOnScreen (view, array); + + /* Now, the coordinates of the view should be in array. Offset X + and Y by them. */ + array[0] += x; + array[1] += y; + + /* Return the resulting coordinates. */ + return array; + } }; -- cgit v1.2.1 From 6e2bc91d924fbeb0ad5728e0424eabc905c0d366 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 15 Jan 2023 11:57:10 +0800 Subject: Implement toolkit menus on Android * java/org/gnu/emacs/EmacsActivity.java (onContextMenuClosed): New function. * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New field `itemAlreadySelected'. (onMenuItemClick): New function. (inflateMenuItems): Attach onClickListener as appropriate. (display1): Clear itemAlreadySelected. (display): Fix runnable synchronization. * java/org/gnu/emacs/EmacsNative.java (sendContextMenu): New function. * java/org/gnu/emacs/EmacsView.java (popupMenu): (cancelPopupMenu): Set popupactive correctly. * src/android.c (android_run_select_thread): Fix android_select again. (android_wait_event): New function. * src/android.h: Update prototypes. * src/androidgui.h (enum android_event_type): New `ANDROID_CONTEXT_MENU' event. (struct android_menu_event, union android_event): Add new event. * src/androidmenu.c (struct android_emacs_context_menu): New structure. (android_init_emacs_context_menu): Add `dismiss' method. (struct android_dismiss_menu_data): New structure. (android_dismiss_menu, android_process_events_for_menu): New functions. (android_menu_show): Set an actual item ID. (popup_activated): Define when stubify as well. (Fmenu_or_popup_active_p): New function. (syms_of_androidmenu): New function. * src/androidterm.c (handle_one_android_event): Handle context menu events. * src/androidterm.h (struct android_display_info): New field for menu item ID. * src/emacs.c (android_emacs_init): Call syms_of_androidmenu. * src/xdisp.c (note_mouse_highlight): Return if popup_activated on Android as well. --- java/org/gnu/emacs/EmacsActivity.java | 15 ++++++++ java/org/gnu/emacs/EmacsContextMenu.java | 64 ++++++++++++++++++++++++++++---- java/org/gnu/emacs/EmacsNative.java | 3 ++ java/org/gnu/emacs/EmacsView.java | 2 + 4 files changed, 77 insertions(+), 7 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 4cd286d1e89..4b96a376987 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.util.Log; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; +import android.view.Menu; public class EmacsActivity extends Activity implements EmacsWindowAttachmentManager.WindowConsumer @@ -227,4 +228,18 @@ public class EmacsActivity extends Activity EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); super.onResume (); } + + @Override + public void + onContextMenuClosed (Menu menu) + { + Log.d (TAG, "onContextMenuClosed: " + menu); + + /* Send a context menu event given that no menu item has already + been selected. */ + if (!EmacsContextMenu.itemAlreadySelected) + EmacsNative.sendContextMenu ((short) 0, 0); + + super.onContextMenuClosed (menu); + } }; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 8d7ae08b257..02dd1c7efa9 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -31,6 +31,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.util.Log; + import android.widget.PopupMenu; /* Context menu implementation. This object is built from JNI and @@ -40,12 +42,31 @@ import android.widget.PopupMenu; public class EmacsContextMenu { - private class Item + private static final String TAG = "EmacsContextMenu"; + + /* Whether or not an item was selected. */ + public static boolean itemAlreadySelected; + + private class Item implements MenuItem.OnMenuItemClickListener { public int itemID; public String itemName; public EmacsContextMenu subMenu; public boolean isEnabled; + + @Override + public boolean + onMenuItemClick (MenuItem item) + { + Log.d (TAG, "onMenuItemClick: " + itemName + " (" + itemID + ")"); + + /* Send a context menu event. */ + EmacsNative.sendContextMenu ((short) 0, itemID); + + /* Say that an item has already been selected. */ + itemAlreadySelected = true; + return true; + } }; public List menuItems; @@ -137,6 +158,7 @@ public class EmacsContextMenu else { menuItem = menu.add (item.itemName); + menuItem.setOnMenuItemClickListener (item); /* If the item ID is zero, then disable the item. */ if (item.itemID == 0 || !item.isEnabled) @@ -171,6 +193,10 @@ public class EmacsContextMenu private boolean display1 (EmacsWindow window, int xPosition, int yPosition) { + /* Set this flag to false. It is used to decide whether or not to + send 0 in response to the context menu being closed. */ + itemAlreadySelected = false; + return window.view.popupMenu (this, xPosition, yPosition); } @@ -199,15 +225,39 @@ public class EmacsContextMenu } }; - try + synchronized (runnable) { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); + EmacsService.SERVICE.runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } } return rc.thing; } + + /* Dismiss this context menu. WINDOW is the window where the + context menu is being displayed. */ + + public void + dismiss (final EmacsWindow window) + { + Runnable runnable; + + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + window.view.cancelPopupMenu (); + itemAlreadySelected = false; + } + }); + } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index a11e509cd7f..4a80f88edcf 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -124,6 +124,9 @@ public class EmacsNative /* Send an ANDROID_DEICONIFIED event. */ public static native void sendDeiconified (short window); + /* Send an ANDROID_CONTEXT_MENU event. */ + public static native void sendContextMenu (short window, int menuEventID); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 1391f630be0..445d8ffa023 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -453,6 +453,7 @@ public class EmacsView extends ViewGroup return false; contextMenu = menu; + popupActive = true; /* On API 21 or later, use showContextMenu (float, float). */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) @@ -469,5 +470,6 @@ public class EmacsView extends ViewGroup + " popupActive set"); contextMenu = null; + popupActive = false; } }; -- cgit v1.2.1 From da9ae10636b84b88e9eb9c827b03cdaabd1611d1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 15 Jan 2023 15:45:29 +0800 Subject: Implement submenus on Android * java/org/gnu/emacs/EmacsActivity.java (onCreate): Set the default theme to Theme.DeviceDefault.NoActionBar if possible. (onContextMenuClosed): Add hack for Android bug. * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu) (onMenuItemClick): Set flag upon submenu selection. (inflateMenuItems): Set onClickListener for submenus as well. (display1): Clear new flag. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix rectangle bounds. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsService.java (onCreate): Pass cache directory. (sync): New function. * src/android.c (struct android_emacs_service): New method `sync'. (setEmacsParams, initEmacs): Handle cache directory. (android_init_emacs_service): Initialize new method `sync'. (android_sync): New function. * src/androidfns.c (Fx_show_tip): Call both functions. * src/androidgui.h: Update prototypes. * src/androidmenu.c (struct android_menu_subprefix) (android_free_subprefixes, android_menu_show): Handle submenu prefixes correctly. * src/androidterm.c (handle_one_android_event): Clear help echo on MotionNotify like on X. * src/menu.c (single_menu_item): Enable submenus on Android. --- java/org/gnu/emacs/EmacsActivity.java | 12 +++++++++- java/org/gnu/emacs/EmacsContextMenu.java | 31 ++++++++++++++++++++++--- java/org/gnu/emacs/EmacsDrawRectangle.java | 2 +- java/org/gnu/emacs/EmacsNative.java | 4 ++++ java/org/gnu/emacs/EmacsService.java | 36 ++++++++++++++++++++++++++++-- 5 files changed, 78 insertions(+), 7 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 4b96a376987..79c0991a5d3 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -27,6 +27,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Build; import android.util.Log; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; @@ -162,7 +163,11 @@ public class EmacsActivity extends Activity FrameLayout.LayoutParams params; /* Set the theme to one without a title bar. */ - setTheme (android.R.style.Theme_NoTitleBar); + + 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); params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -235,6 +240,11 @@ public class EmacsActivity extends Activity { Log.d (TAG, "onContextMenuClosed: " + menu); + /* See the comment inside onMenuItemClick. */ + if (EmacsContextMenu.wasSubmenuSelected + && menu.toString ().contains ("ContextMenuBuilder")) + return; + /* Send a context menu event given that no menu item has already been selected. */ if (!EmacsContextMenu.itemAlreadySelected) diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 02dd1c7efa9..00e204c9949 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.SubMenu; import android.util.Log; @@ -47,6 +48,9 @@ public class EmacsContextMenu /* Whether or not an item was selected. */ public static boolean itemAlreadySelected; + /* Whether or not a submenu was selected. */ + public static boolean wasSubmenuSelected; + private class Item implements MenuItem.OnMenuItemClickListener { public int itemID; @@ -60,6 +64,20 @@ public class EmacsContextMenu { Log.d (TAG, "onMenuItemClick: " + itemName + " (" + itemID + ")"); + if (subMenu != null) + { + /* After opening a submenu within a submenu, Android will + send onContextMenuClosed for a ContextMenuBuilder. This + will normally confuse Emacs into thinking that the + context menu has been dismissed. Wrong! + + Setting this flag makes EmacsActivity to only handle + SubMenuBuilder being closed, which always means the menu + has actually been dismissed. */ + wasSubmenuSelected = true; + return false; + } + /* Send a context menu event. */ EmacsNative.sendContextMenu ((short) 0, itemID); @@ -144,7 +162,7 @@ public class EmacsContextMenu { Intent intent; MenuItem menuItem; - Menu submenu; + SubMenu submenu; for (Item item : menuItems) { @@ -153,7 +171,11 @@ public class EmacsContextMenu /* This is a submenu. Create the submenu and add the contents of the menu to it. */ submenu = menu.addSubMenu (item.itemName); - inflateMenuItems (submenu); + item.subMenu.inflateMenuItems (submenu); + + /* This is still needed to set wasSubmenuSelected. */ + menuItem = submenu.getItem (); + menuItem.setOnMenuItemClickListener (item); } else { @@ -184,7 +206,7 @@ public class EmacsContextMenu public EmacsContextMenu parent () { - return parent; + return this.parent; } /* Like display, but does the actual work and runs in the main @@ -197,6 +219,9 @@ public class EmacsContextMenu send 0 in response to the context menu being closed. */ itemAlreadySelected = false; + /* No submenu has been selected yet. */ + wasSubmenuSelected = false; + return window.view.popupMenu (this, xPosition, yPosition); } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 84ff498847b..b42e9556e8c 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -59,7 +59,7 @@ public class EmacsDrawRectangle } paint = gc.gcPaint; - rect = new Rect (x + 1, y + 1, x + width, y + height); + rect = new Rect (x, y, x + width, y + height); paint.setStyle (Paint.Style.STROKE); diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 4a80f88edcf..2f3a732ea7c 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -38,6 +38,9 @@ public class EmacsNative 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. @@ -45,6 +48,7 @@ public class EmacsNative public static native void setEmacsParams (AssetManager assetManager, String filesDir, String libDir, + String cacheDir, float pixelDensityX, float pixelDensityY, EmacsService emacsService); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index f935b63fa0d..ca38f93dc98 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -108,7 +108,7 @@ public class EmacsService extends Service { AssetManager manager; Context app_context; - String filesDir, libDir; + String filesDir, libDir, cacheDir; double pixelDensityX; double pixelDensityY; @@ -126,12 +126,13 @@ public class EmacsService extends Service parameters. */ filesDir = app_context.getFilesDir ().getCanonicalPath (); libDir = getLibraryDirectory (); + cacheDir = app_context.getCacheDir ().getCanonicalPath (); Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir + " and libDir = " + libDir); EmacsNative.setEmacsParams (manager, filesDir, libDir, - (float) pixelDensityX, + cacheDir, (float) pixelDensityX, (float) pixelDensityY, this); @@ -407,4 +408,35 @@ public class EmacsService extends Service { return KeyEvent.keyCodeToString (keysym); } + + public void + sync () + { + Runnable runnable; + + runnable = new Runnable () { + public void + run () + { + synchronized (this) + { + notify (); + } + } + }; + + synchronized (runnable) + { + runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + } }; -- cgit v1.2.1 From ad59d8986aee4498c0427449e024341d1a195a2d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 16 Jan 2023 19:50:02 +0800 Subject: Update Android port * doc/emacs/android.texi (Android, Android Environment): Improve documentation. * doc/lispref/commands.texi (Touchscreen Events): Document changes to touchscreen support. * doc/lispref/display.texi (Defining Faces, Window Systems): * doc/lispref/frames.texi (Frame Layout, Font and Color Parameters): * doc/lispref/os.texi (System Environment): Document Android in various places. * java/org/gnu/emacs/EmacsWindow.java (figureChange): Fix crash. * lisp/loadup.el: ("touch-screen"): Load touch-screen.el. * lisp/pixel-scroll.el: Autoload two functions. * lisp/term/android-win.el: Add require 'touch-screen. * lisp/touch-screen.el (touch-screen-current-tool) (touch-screen-current-timer, touch-screen-delay) (touch-screen-relative-xy, touch-screen-handle-scroll) (touch-screen-handle-timeout, touch-screen-handle-point-update) (touch-screen-handle-point-up, touch-screen-handle-touch) (global-map, touch-screen): New file. * src/android.c (android_run_debug_thread): Fix build on 64 bit systems. (JNICALL, android_put_pixel): Likewise. (android_transform_coordinates, android_four_corners_bilinear) (android_fetch_pixel_bilinear, android_project_image_bilinear) (android_fetch_pixel_nearest_24, android_fetch_pixel_nearest_1) (android_project_image_nearest): New functions. * src/androidgui.h (struct android_transform): New structure. * src/androidterm.c (android_note_mouse_movement): Remove obsolete TODO. (android_get_scale_factor): New function. (android_draw_underwave): Scale underwave correctly. * src/dispextern.h: Support native image transforms on Android. * src/image.c (matrix_identity, matrix_rotate) (matrix_mirror_horizontal, matrix_translate): New functions. (image_set_transform): Implement native image transforms on Android. (Fimage_transforms_p): Implement on Android. * src/keyboard.c (make_lispy_event, syms_of_keyboard): Handle touch screen- menu bar events. * src/sfnt.c: Fix typo in comment. * src/sfntfont-android.c (sfntfont_android_blend, U255TO256) (sfntfont_android_put_glyphs): Avoid redundant swizzling. * src/sfntfont.c (sfntfont_lookup_char): Fix build on 64 bit systems. --- java/org/gnu/emacs/EmacsWindow.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 6effa79d1a4..7181bc89fea 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -637,8 +637,8 @@ public class EmacsWindow extends EmacsHandleObject pointerIndex = event.getActionIndex (); pointerID = event.getPointerId (pointerIndex); pointerMap.put (pointerID, - new Coordinate ((int) event.getX (pointerID), - (int) event.getY (pointerID))); + new Coordinate ((int) event.getX (pointerIndex), + (int) event.getY (pointerIndex))); break; case MotionEvent.ACTION_POINTER_UP: -- cgit v1.2.1 From 1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 17 Jan 2023 22:10:43 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Fonts): Document that TTC format fonts are now supported. * doc/emacs/emacs.texi (Top): Fix menus. * doc/lispref/commands.texi (Touchscreen Events) (Key Sequence Input): Document changes to touchscreen events. * etc/DEBUG: Describe how to debug 64 bit binaries on Android. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog): New class. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Use 5 point PolyLine like X, because Android behaves like Postscript on some devices and X elsewhere. * java/org/gnu/emacs/EmacsFillRectangle.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsPixmap.java (destroyHandle): Explicitly recycle bitmap and GC if it is big. * java/org/gnu/emacs/EmacsView.java (EmacsView): Make `bitmapDirty' a boolean. (handleDirtyBitmap): Reimplement in terms of that boolean. Explicitly recycle old bitmap and GC. (onLayout): Fix lock up. (onDetachedFromWindow): Recycle bitmap and GC. * java/org/gnu/emacs/EmacsWindow.java (requestViewLayout): Update call to explicitlyDirtyBitmap. * src/android.c (android_run_select_thread, android_select): Really fix android_select. (android_build_jstring): New function. * src/android.h: Update prototypes. * src/androidmenu.c (android_process_events_for_menu): Totally unblock input before process_pending_signals. (android_menu_show): Remove redundant unblock_input and debugging code. (struct android_emacs_dialog, android_init_emacs_dialog) (android_dialog_show, android_popup_dialog, init_androidmenu): Implement popup dialogs on Android. * src/androidterm.c (android_update_tools) (handle_one_android_event, android_frame_up_to_date): Allow tapping tool bar items. (android_create_terminal): Add dialog hook. (android_wait_for_event): Adjust call to android_select. * src/androidterm.h (struct android_touch_point): New field `tool_bar_p'. * src/keyboard.c (read_key_sequence, head_table) (syms_of_keyboard): Prefix touchscreen events with posn. * src/keyboard.h (EVENT_HEAD): Handle touchscreen events. * src/process.c (wait_reading_process_output): Adjust call to android_select. * src/sfnt.c (sfnt_read_table_directory): If the first long turns out to be ttcf, return -1. (sfnt_read_ttc_header): New function. (main): Test TTC support. * src/sfnt.h (struct sfnt_ttc_header): New structure. (enum sfnt_ttc_tag): New enum. * src/sfntfont-android.c (struct sfntfont_android_scanline_buffer): New structure. (GET_SCANLINE_BUFFER): New macro. Try to avoid so much malloc upon accessing the scanline buffer. (sfntfont_android_put_glyphs): Do not use SAFE_ALLOCA to allocate the scaline buffer. (Fandroid_enumerate_fonts): Enumerate ttc fonts too. * src/sfntfont.c (struct sfnt_font_desc): New field `offset'. (sfnt_enum_font_1): Split out enumeration code from sfnt_enum_font. (sfnt_enum_font): Read TTC tables and enumerate each font therein. (sfntfont_open): Seek to the offset specified. * xcompile/Makefile.in (maintainer-clean): Fix depends here. --- java/org/gnu/emacs/EmacsCopyArea.java | 4 + java/org/gnu/emacs/EmacsDialog.java | 333 +++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsDrawRectangle.java | 32 ++- java/org/gnu/emacs/EmacsFillRectangle.java | 3 + java/org/gnu/emacs/EmacsPixmap.java | 23 ++ java/org/gnu/emacs/EmacsView.java | 84 ++++++-- java/org/gnu/emacs/EmacsWindow.java | 2 +- 7 files changed, 452 insertions(+), 29 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsDialog.java (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 00e817bb97d..5d72a7860c8 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -116,6 +116,7 @@ public class EmacsCopyArea src_x, src_y, width, height); canvas.drawBitmap (bitmap, null, rect, paint); + bitmap.recycle (); } else { @@ -183,6 +184,9 @@ public class EmacsCopyArea paint.setXfermode (overAlu); canvas.drawBitmap (maskBitmap, null, maskRect, paint); gc.resetXfermode (); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java new file mode 100644 index 00000000000..5bc8efa5978 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -0,0 +1,333 @@ +/* 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.util.List; +import java.util.ArrayList; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Context; +import android.util.Log; + +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.FrameLayout; + +import android.view.View; +import android.view.ViewGroup; + +/* Toolkit dialog implementation. This object is built from JNI and + describes a single alert dialog. Then, `inflate' turns it into + AlertDialog. */ + +public class EmacsDialog implements DialogInterface.OnDismissListener +{ + private static final String TAG = "EmacsDialog"; + + /* List of buttons in this dialog. */ + private List buttons; + + /* Dialog title. */ + private String title; + + /* Dialog text. */ + private String text; + + /* Whether or not a selection has already been made. */ + private boolean wasButtonClicked; + + /* Dialog to dismiss after click. */ + private AlertDialog dismissDialog; + + private class EmacsButton implements View.OnClickListener, + DialogInterface.OnClickListener + { + /* Name of this button. */ + public String name; + + /* ID of this button. */ + public int id; + + /* Whether or not the button is enabled. */ + public boolean enabled; + + @Override + public void + onClick (View view) + { + Log.d (TAG, "onClicked " + this); + + wasButtonClicked = true; + EmacsNative.sendContextMenu ((short) 0, id); + dismissDialog.dismiss (); + } + + @Override + public void + onClick (DialogInterface dialog, int which) + { + Log.d (TAG, "onClicked " + this); + + wasButtonClicked = true; + EmacsNative.sendContextMenu ((short) 0, id); + } + }; + + /* Create a popup dialog with the title TITLE and the text TEXT. + TITLE may be NULL. */ + + public static EmacsDialog + createDialog (String title, String text) + { + EmacsDialog dialog; + + dialog = new EmacsDialog (); + dialog.buttons = new ArrayList (); + dialog.title = title; + dialog.text = text; + + return dialog; + } + + /* Add a button named NAME, with the identifier ID. If DISABLE, + disable the button. */ + + public void + addButton (String name, int id, boolean disable) + { + EmacsButton button; + + button = new EmacsButton (); + button.name = name; + button.id = id; + button.enabled = !disable; + buttons.add (button); + } + + /* Turn this dialog into an AlertDialog for the specified + CONTEXT. + + Upon a button being selected, the dialog will send an + ANDROID_CONTEXT_MENU event with the id of that button. + + Upon the dialog being dismissed, an ANDROID_CONTEXT_MENU event + will be sent with an id of 0. */ + + public AlertDialog + toAlertDialog (Context context) + { + AlertDialog dialog; + int size; + EmacsButton button; + LinearLayout layout; + Button buttonView; + ViewGroup.LayoutParams layoutParams; + + size = buttons.size (); + + if (size <= 3) + { + dialog = new AlertDialog.Builder (context).create (); + dialog.setMessage (text); + dialog.setCancelable (true); + dialog.setOnDismissListener (this); + + if (title != null) + dialog.setTitle (title); + + /* There are less than 4 buttons. Add the buttons the way + Android intends them to be added. */ + + if (size >= 1) + { + button = buttons.get (0); + dialog.setButton (DialogInterface.BUTTON_POSITIVE, + button.name, button); + } + + if (size >= 2) + { + button = buttons.get (1); + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + button.name, button); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + buttonView.setEnabled (button.enabled); + } + + if (size >= 3) + { + button = buttons.get (2); + dialog.setButton (DialogInterface.BUTTON_NEGATIVE, + button.name, button); + } + } + else + { + /* There are more than 4 buttons. Add them all to a + LinearLayout. */ + layout = new LinearLayout (context); + layoutParams + = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + for (EmacsButton emacsButton : buttons) + { + buttonView = new Button (context); + buttonView.setText (emacsButton.name); + buttonView.setOnClickListener (emacsButton); + buttonView.setLayoutParams (layoutParams); + buttonView.setEnabled (emacsButton.enabled); + layout.addView (buttonView); + } + + layoutParams + = new FrameLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layout.setLayoutParams (layoutParams); + + /* Add that layout to the dialog's custom view. + + android.R.id.custom is documented to work. But looking it + up returns NULL, so setView must be used instead. */ + + dialog = new AlertDialog.Builder (context).setView (layout).create (); + dialog.setMessage (text); + dialog.setCancelable (true); + dialog.setOnDismissListener (this); + + if (title != null) + dialog.setTitle (title); + } + + return dialog; + } + + /* Internal helper for display run on the main thread. */ + + private boolean + display1 () + { + EmacsActivity activity; + int size; + Button buttonView; + EmacsButton button; + AlertDialog dialog; + + if (EmacsActivity.focusedActivities.isEmpty ()) + return false; + + activity = EmacsActivity.focusedActivities.get (0); + dialog = dismissDialog = toAlertDialog (activity); + dismissDialog.show (); + + /* If there are less than four buttons, then they must be + individually enabled or disabled after the dialog is + displayed. */ + size = buttons.size (); + + if (size <= 3) + { + if (size >= 1) + { + button = buttons.get (0); + buttonView + = dialog.getButton (DialogInterface.BUTTON_POSITIVE); + buttonView.setEnabled (button.enabled); + } + + if (size >= 2) + { + button = buttons.get (1); + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + button.name, button); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + buttonView.setEnabled (button.enabled); + } + + if (size >= 3) + { + button = buttons.get (2); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEGATIVE); + buttonView.setEnabled (button.enabled); + } + } + + return true; + } + + /* Display this dialog for a suitable activity. + Value is false if the dialog could not be displayed, + and true otherwise. */ + + public boolean + display () + { + Runnable runnable; + final Holder rc; + + rc = new Holder (); + runnable = new Runnable () { + @Override + public void + run () + { + synchronized (this) + { + rc.thing = display1 (); + notify (); + } + } + }; + + synchronized (runnable) + { + EmacsService.SERVICE.runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + + return rc.thing; + } + + + + @Override + public void + onDismiss (DialogInterface dialog) + { + Log.d (TAG, "onDismiss: " + this); + + if (wasButtonClicked) + return; + + EmacsNative.sendContextMenu ((short) 0, 0); + } +}; diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index b42e9556e8c..c29d413f66e 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -36,10 +36,10 @@ public class EmacsDrawRectangle Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect; Rect maskRect, dstRect; Canvas canvas; Bitmap clipBitmap; + Rect clipRect; /* TODO implement stippling. */ if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) @@ -58,13 +58,29 @@ public class EmacsDrawRectangle canvas.clipRect (gc.real_clip_rects[i]); } - paint = gc.gcPaint; - rect = new Rect (x, y, x + width, y + height); + /* Clip to the clipRect because some versions of Android draw an + overly wide line. */ + clipRect = new Rect (x, y, x + width + 1, + y + height + 1); + canvas.clipRect (clipRect); - paint.setStyle (Paint.Style.STROKE); + paint = gc.gcPaint; if (gc.clip_mask == null) - canvas.drawRect (rect, paint); + { + /* canvas.drawRect just doesn't work on Android, producing + different results on various devices. Do a 5 point + PolyLine instead. */ + canvas.drawLine ((float) x, (float) y, (float) x + width, + (float) y, paint); + canvas.drawLine ((float) x + width, (float) y, + (float) x + width, (float) y + height, + paint); + canvas.drawLine ((float) x + width, (float) y + height, + (float) x, (float) y + height, paint); + canvas.drawLine ((float) x, (float) y + height, + (float) x, (float) y, paint); + } else { /* Drawing with a clip mask involves calculating the @@ -116,10 +132,12 @@ public class EmacsDrawRectangle /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (null); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); - drawable.damageRect (new Rect (x, y, x + width + 1, - y + height + 1)); + drawable.damageRect (clipRect); } } diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index b733b417d6b..7cc55d3db96 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -115,6 +115,9 @@ public class EmacsFillRectangle /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (null); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 15452f007c4..85931c2abd4 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -25,6 +25,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Build; + /* Drawable backed by bitmap. */ public class EmacsPixmap extends EmacsHandleObject @@ -123,4 +125,25 @@ public class EmacsPixmap extends EmacsHandleObject { return bitmap; } + + @Override + public void + destroyHandle () + { + boolean needCollect; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + needCollect = (bitmap.getByteCount () + >= 1024 * 512); + else + needCollect = (bitmap.getAllocationByteCount () + >= 1024 * 512); + + bitmap.recycle (); + bitmap = null; + + /* Collect the bitmap storage if the bitmap is big. */ + if (needCollect) + Runtime.getRuntime ().gc (); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 445d8ffa023..6137fd74a7f 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -70,9 +70,9 @@ public class EmacsView extends ViewGroup event regardless of what changed. */ public boolean mustReportLayout; - /* If non-null, whether or not bitmaps must be recreated upon the - next call to getBitmap. */ - private Rect bitmapDirty; + /* Whether or not bitmaps must be recreated upon the next call to + getBitmap. */ + private boolean bitmapDirty; /* Whether or not a popup is active. */ private boolean popupActive; @@ -80,6 +80,9 @@ public class EmacsView extends ViewGroup /* The current context menu. */ private EmacsContextMenu contextMenu; + /* The last measured width and height. */ + private int measuredWidth, measuredHeight; + public EmacsView (EmacsWindow window) { @@ -116,13 +119,27 @@ public class EmacsView extends ViewGroup { Bitmap oldBitmap; + if (measuredWidth == 0 || measuredHeight == 0) + return; + + /* If bitmap is the same width and height as the measured width + and height, there is no need to do anything. Avoid allocating + the extra bitmap. */ + if (bitmap != null + && (bitmap.getWidth () == measuredWidth + && bitmap.getHeight () == measuredHeight)) + { + bitmapDirty = false; + return; + } + /* Save the old bitmap. */ oldBitmap = bitmap; /* Recreate the front and back buffer bitmaps. */ bitmap - = Bitmap.createBitmap (bitmapDirty.width (), - bitmapDirty.height (), + = Bitmap.createBitmap (measuredWidth, + measuredHeight, Bitmap.Config.ARGB_8888); bitmap.eraseColor (0xffffffff); @@ -133,23 +150,27 @@ public class EmacsView extends ViewGroup if (oldBitmap != null) canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ()); - bitmapDirty = null; + bitmapDirty = false; + + /* Explicitly free the old bitmap's memory. */ + if (oldBitmap != null) + oldBitmap.recycle (); + + /* Some Android versions still don't free the bitmap until the + next GC. */ + Runtime.getRuntime ().gc (); } public synchronized void - explicitlyDirtyBitmap (Rect rect) + explicitlyDirtyBitmap () { - if (bitmapDirty == null - && (bitmap == null - || rect.width () != bitmap.getWidth () - || rect.height () != bitmap.getHeight ())) - bitmapDirty = rect; + bitmapDirty = true; } public synchronized Bitmap getBitmap () { - if (bitmapDirty != null) + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); return bitmap; @@ -158,7 +179,7 @@ public class EmacsView extends ViewGroup public synchronized Canvas getCanvas () { - if (bitmapDirty != null) + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); return canvas; @@ -196,8 +217,12 @@ public class EmacsView extends ViewGroup super.setMeasuredDimension (width, height); } + /* Note that the monitor lock for the window must never be held from + within the lock for the view, because the window also locks the + other way around. */ + @Override - protected synchronized void + protected void onLayout (boolean changed, int left, int top, int right, int bottom) { @@ -213,12 +238,13 @@ public class EmacsView extends ViewGroup window.viewLayout (left, top, right, bottom); } - if (changed - /* Check that a change has really happened. */ - && (bitmapDirty == null - || bitmapDirty.width () != right - left - || bitmapDirty.height () != bottom - top)) - bitmapDirty = new Rect (left, top, right, bottom); + measuredWidth = right - left; + measuredHeight = bottom - top; + + /* Dirty the back buffer. */ + + if (changed) + explicitlyDirtyBitmap (); for (i = 0; i < count; ++i) { @@ -472,4 +498,20 @@ public class EmacsView extends ViewGroup contextMenu = null; popupActive = false; } + + @Override + public synchronized void + onDetachedFromWindow () + { + synchronized (this) + { + /* Recycle the bitmap and call GC. */ + bitmap.recycle (); + bitmap = null; + canvas = null; + + /* Collect the bitmap storage; it could be large. */ + Runtime.getRuntime ().gc (); + } + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 7181bc89fea..f5b50f11f14 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -260,7 +260,7 @@ public class EmacsWindow extends EmacsHandleObject { /* This is necessary because otherwise subsequent drawing on the Emacs thread may be lost. */ - view.explicitlyDirtyBitmap (rect); + view.explicitlyDirtyBitmap (); EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override -- cgit v1.2.1 From a496509cedb17109d0e6297a74e2ff8ed526333c Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 19 Jan 2023 22:19:06 +0800 Subject: Update Android port * .gitignore: Add new files. * INSTALL.android: Explain how to build Emacs for ancient versions of Android. * admin/merge-gnulib (GNULIB_MODULES): Add getdelim. * build-aux/config.guess (timestamp, version): * build-aux/config.sub (timestamp, version): Autoupdate. * configure.ac (BUILD_DETAILS, ANDROID_MIN_SDK): (ANDROID_STUBIFY): Allow specifying CFLAGS via ANDROID_CFLAGS. Add new configure tests for Android API version when not explicitly specified. * doc/emacs/android.texi (Android): Add reference to ``Other Input Devices''. (Android File System): Remove restrictions on directory-files on the assets directory. * doc/emacs/emacs.texi (Top): Add Other Input Devices to menu. * doc/emacs/input.texi (Other Input Devices): New node. * doc/lispref/commands.texi (Touchscreen Events): Document changes to touchscreen input events. * doc/lispref/frames.texi (Pop-Up Menus): Likewise. * etc/NEWS: Announce changes. * java/Makefile.in: Use lib-src/asset-directory-tool to generate an `directory-tree' file placed in /assets. * java/debug.sh: Large adjustments to support Android 2.2 and later. * java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems): * java/org/gnu/emacs/EmacsCopyArea.java (perform): * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): * java/org/gnu/emacs/EmacsDrawLine.java (perform): * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): * java/org/gnu/emacs/EmacsDrawable.java (EmacsDrawable): * java/org/gnu/emacs/EmacsFillPolygon.java (perform): * java/org/gnu/emacs/EmacsFillRectangle.java (perform): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): (destroyHandle): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (draw): Avoid redundant canvas saves and restores. * java/org/gnu/emacs/EmacsService.java (run): * java/org/gnu/emacs/EmacsView.java (EmacsView): (handleDirtyBitmap): * java/org/gnu/emacs/EmacsWindow.java (changeWindowBackground) (EmacsWindow): Make compatible with Android 2.2 and later. * lib-src/Makefile.in (DONT_INSTALL): Add asset-directory-tool on Android.:(asset-directory-tool{EXEEXT}): New target. * lib-src/asset-directory-tool.c (struct directory_tree, xmalloc) (main_1, main_2, main): New file. * lib, m4: Merge from gnulib. This will be reverted before merging to master. * lisp/button.el (button-map): (push-button): * lisp/frame.el (display-popup-menus-p): Improve touchscreen support. * lisp/subr.el (event-start): (event-end): Handle touchscreen events. * lisp/touch-screen.el (touch-screen-handle-timeout): (touch-screen-handle-point-update): (touch-screen-handle-point-up): (touch-screen-track-tap): (touch-screen-track-drag): (touch-screen-drag-mode-line-1): (touch-screen-drag-mode-line): New functions. ([mode-line touchscreen-begin]): ([bottom-divider touchscreen-begin]): Bind new events. * lisp/wid-edit.el (widget-event-point): (widget-keymap): (widget-event-start): (widget-button--check-and-call-button): (widget-button-click): Improve touchscreen support. * src/alloc.c (make_lisp_symbol): Avoid ICE on Android NDK GCC. (mark_pinned_symbols): Likewise. * src/android.c (struct android_emacs_window): New struct. (window_class): New variable. (android_run_select_thread): Add workaround for Android platform bug. (android_extract_long, android_scan_directory_tree): New functions. (android_file_access_p): Use those functions instead. (android_init_emacs_window): New function. (android_init_emacs_gc_class): Update signature of `markDirty'. (android_change_gc, android_set_clip_rectangles): Tell the GC whether or not clip rects were dirtied. (android_swap_buffers): Do not look up method every time. (struct android_dir): Adjust for new directory tree lookup. (android_opendir, android_readdir, android_closedir): Likewise. (android_four_corners_bilinear): Fix coding style. (android_ftruncate): New function. * src/android.h: Update prototypes. Replace ftruncate with android_ftruncate when necessary. * src/androidterm.c (handle_one_android_event): Pacify GCC. Fix touch screen tool bar bug. * src/emacs.c (using_utf8): Fix compilation error. * src/fileio.c (Ffile_system_info): Return Qnil when fsusage.o is not built. * src/filelock.c (BOOT_TIME_FILE): Fix definition for Android. * src/frame.c (Fx_parse_geometry): Fix uninitialized variable uses. * src/keyboard.c (lispy_function_keys): Fix `back'. * src/menu.c (x_popup_menu_1): Handle touch screen events. (Fx_popup_menu): Document changes. * src/sfnt.c (main): Improve tests. * src/sfntfont-android.c (sfntfont_android_put_glyphs): Fix minor problem. (init_sfntfont_android): Check for HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL. * src/sfntfont.c (struct sfnt_font_desc): New fields `adstyle' and `languages'. (sfnt_parse_style): Append tokens to adstyle. (sfnt_parse_languages): New function. (sfnt_enum_font_1): Parse supported languages and adstyle. (sfntfont_list_1): Handle new fields. (sfntfont_text_extents): Fix uninitialized variable use. (syms_of_sfntfont, mark_sfntfont): Adjust accordingly. --- java/Makefile.in | 37 ++++++- java/debug.sh | 155 ++++++++++++++++++---------- java/org/gnu/emacs/EmacsContextMenu.java | 20 +++- java/org/gnu/emacs/EmacsCopyArea.java | 11 +- java/org/gnu/emacs/EmacsDialog.java | 3 - java/org/gnu/emacs/EmacsDrawLine.java | 11 +- java/org/gnu/emacs/EmacsDrawRectangle.java | 45 +++----- java/org/gnu/emacs/EmacsDrawable.java | 2 +- java/org/gnu/emacs/EmacsFillPolygon.java | 11 +- java/org/gnu/emacs/EmacsFillRectangle.java | 12 +-- java/org/gnu/emacs/EmacsGC.java | 33 ++++-- java/org/gnu/emacs/EmacsPixmap.java | 77 +++++++++++--- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 11 +- java/org/gnu/emacs/EmacsService.java | 7 +- java/org/gnu/emacs/EmacsView.java | 34 +++++- java/org/gnu/emacs/EmacsWindow.java | 80 ++++++++++---- 16 files changed, 343 insertions(+), 206 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index c539fb0f1fb..22c912fdce5 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -21,6 +21,10 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ version = @version@ +# This is the host lib-src and lib, not the cross compiler's lib-src. +libsrc = ../lib-src +EXEEXT = @EXEEXT@ + -include ${top_builddir}/src/verbose.mk SHELL = @SHELL@ @@ -29,14 +33,25 @@ AAPT = @AAPT@ D8 = @D8@ ZIPALIGN = @ZIPALIGN@ JARSIGNER = @JARSIGNER@ +JARSIGNER_FLAGS = ANDROID_JAR = @ANDROID_JAR@ ANDROID_ABI = @ANDROID_ABI@ +ANDROID_SDK_18_OR_EARLIER = @ANDROID_SDK_18_OR_EARLIER@ WARN_JAVAFLAGS = -Xlint:deprecation JAVAFLAGS = -classpath "$(ANDROID_JAR):." -target 1.7 -source 1.7 \ $(WARN_JAVAFLAGS) -SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 +# Android 4.3 and earlier require Emacs to be signed with a different +# digital signature algorithm. + +ifneq (,$(ANDROID_SDK_18_OR_EARLIER)) +JARSIGNER_FLAGS = -sigalg MD5withRSA -digestalg SHA1 +else +JARSIGNER_FLAGS = +endif + +SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 $(JARSIGNER_FLAGS) JAVA_FILES = $(shell find . -type f -name *.java) CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) @@ -82,7 +97,14 @@ CROSS_LIBS = ../xcompile/src/libemacs.so ../xcompile/lib-src/ctags ../xcompile/lib-src/ebrowse &: make -C ../xcompile lib-src/$(notdir $@) -emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml +# This is needed to generate the ``.directory-tree'' file used by the +# Android emulations of readdir and faccessat. + +$(libsrc)/asset-directory-tool: + $(MAKE) -C $(libsrc) $(notdir $@) + +emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ + AndroidManifest.xml # Make the working directory for this stuff rm -rf install_temp mkdir -p install_temp/lib/$(ANDROID_ABI) @@ -106,6 +128,9 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml rm -rf $${subdir}/[mM]akefile*[.-]in ; \ rm -rf $${subdir}/Makefile; \ done +# Generate the directory tree for those directories. + $(libsrc)/asset-directory-tool install_temp/assets \ + install_temp/assets/directory-tree # Install architecture dependents to lib/$(ANDROID_ABI). This # perculiar naming scheme is required to make Android preserve these # binaries upon installation. @@ -120,10 +145,12 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done -# Package everything. - $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml +# Package everything. Specifying the assets on this command line is +# necessary for AAssetManager_getNextFileName to work on old versions +# of Android. + $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml \ + -A install_temp/assets pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd - pushd install_temp; $(AAPT) add ../$@ `find assets -type f`; popd rm -rf install_temp # Makefile itself. diff --git a/java/debug.sh b/java/debug.sh index aa80aeeebcd..7008664c049 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -93,7 +93,7 @@ while [ $# -gt 0 ]; do shift done -if [ -z $devices ]; then +if [ -z "$devices" ]; then echo "No devices are available." exit 1 fi @@ -117,25 +117,43 @@ if [ -z $app_data_dir ]; then echo "Is it installed?" fi -echo "Found application data directory at $app_data_dir..." - -# Find which PIDs are associated with org.gnu.emacs -package_uid=`adb -s $device shell run-as $package id -u` - -if [ -z $package_uid ]; then - echo "Failed to obtain UID of packages named $package" - exit 1 -fi - -# First, run ps -u $package_uid -o PID,CMD to fetch the list of -# process IDs. -package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` - -# Next, remove lines matching "ps" itself. -package_pids=`awk -- '{ - if (!match ($0, /(PID|ps)/)) - print $1 -}' <<< $package_pids` +echo "Found application data directory at" "$app_data_dir" + +# Generate an awk script to extract PIDs from Android ps output. It +# is enough to run `ps' as the package user on newer versions of +# Android, but that doesn't work on Android 2.3. +cat << EOF > tmp.awk +BEGIN { + pid = 0; + pid_column = 2; +} + +{ + # Remove any trailing carriage return from the input line. + gsub ("\r", "", \$NF) + + # If this is line 1, figure out which column contains the PID. + if (NR == 1) + { + for (n = 1; n <= NF; ++n) + { + if (\$n == "PID") + pid_column=n; + } + } + else if (\$NF == "$package") + print \$pid_column +} +EOF + +# Make sure that file disappears once this script exits. +trap "rm -f $(pwd)/tmp.awk" 0 + +# First, run ps to fetch the list of process IDs. +package_pids=`adb -s $device shell ps` + +# Next, extract the list of PIDs currently running. +package_pids=`awk -f tmp.awk <<< $package_pids` if [ "$attach_existing" != "yes" ]; then # Finally, kill each existing process. @@ -149,19 +167,20 @@ if [ "$attach_existing" != "yes" ]; then echo "Starting activity $activity and attaching debugger" # Exit if the activity could not be started. - adb -s $device shell am start -D "$package/$activity" + adb -s $device shell am start -D -n "$package/$activity" if [ ! $? ]; then exit 1; fi + # Sleep for a bit. Otherwise, the process may not have started + # yet. + sleep 1 + # Now look for processes matching the package again. - package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` + package_pids=`adb -s $device shell ps` # Next, remove lines matching "ps" itself. - package_pids=`awk -- '{ - if (!match ($0, /(PID|ps)/)) - print $1 -}' <<< $package_pids` + package_pids=`awk -f tmp.awk <<< $package_pids` fi pid=$package_pids @@ -170,10 +189,10 @@ num_pids=`wc -w <<< "$package_pids"` if [ $num_pids -gt 1 ]; then echo "More than one process was started:" echo "" - adb -s $device shell run-as $package ps -u $package_uid | awk -- '{ - if (!match ($0, /ps/)) - print $0 - }' + adb -s $device shell run-as $package ps | awk -- "{ + if (!match (\$0, /ps/) && match (\$0, /$package/)) + print \$0 + }" echo "" printf "Which one do you want to attach to? " read pid @@ -182,10 +201,12 @@ elif [ -z $package_pids ]; then exit 1 fi -# This isn't necessary when attaching gdb to an existing process. +# If either --jdb was specified or debug.sh is not connecting to an +# existing process, then store a suitable JDB invocation in +# jdb_command. GDB will then run JDB to unblock the application from +# the wait dialog after startup. + if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then - # Start JDB to make the wait dialog disappear. - echo "Attaching JDB to unblock the application." adb -s $device forward --remove-all adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" @@ -203,20 +224,42 @@ if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then $jdb_command exit 1 fi +fi - exec 4<> /tmp/file-descriptor-stamp +if [ -n "$jdb_command" ]; then + echo "Starting JDB to unblock application." - # Now run JDB with IO redirected to file descriptor 4 in a subprocess. - $jdb_command <&4 >&4 & + # Start JDB to unblock the application. + coproc JDB { $jdb_command; } - character= - # Next, wait until the prompt is found. - while read -n1 -u 4 character; do - if [ "$character" = ">" ]; then - echo "JDB attached successfully" - break; + # Tell JDB to first suspend all threads. + echo "suspend" >&${JDB[1]} + + # Tell JDB to print a magic string once the program is + # initialized. + echo "print \"__verify_jdb_has_started__\"" >&${JDB[1]} + + # Now wait for JDB to give the string back. + line= + while :; do + read -u ${JDB[0]} line + if [ ! $? ]; then + echo "Failed to read JDB output" + exit 1 fi + + case "$line" in + *__verify_jdb_has_started__*) + # Android only polls for a Java debugger every 200ms, so + # the debugger must be connected for at least that long. + echo "Pausing 1 second for the program to continue." + sleep 1 + break + ;; + esac done + + # Note that JDB does not exit until GDB is fully attached! fi # See if gdbserver has to be uploaded @@ -234,18 +277,19 @@ fi echo "Attaching gdbserver to $pid on $device..." exec 5<> /tmp/file-descriptor-stamp +rm -f /tmp/file-descriptor-stamp if [ -z "$gdbserver" ]; then adb -s $device shell run-as $package $gdbserver_bin --once \ - "+debug.$package_uid.socket" --attach $pid >&5 & - gdb_socket="localfilesystem:$app_data_dir/debug.$package_uid.socket" + "+debug.$package.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" else # Normally the program cannot access $gdbserver_bin when it is # placed in /data/local/tmp. adb -s $device shell $gdbserver_bin --once \ - "+/data/local/tmp/debug.$package_uid.socket" \ + "+/data/local/tmp/debug.$package.socket" \ --attach $pid >&5 & - gdb_socket="localfilesystem:/data/local/tmp/debug.$package_uid.socket" + gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" fi # Wait until gdbserver successfully runs. @@ -256,7 +300,7 @@ while read -u 5 line; do break; ;; *error* | *Error* | failed ) - echo $line + echo "GDB error:" $line exit 1 ;; * ) @@ -264,19 +308,18 @@ while read -u 5 line; do esac done -if [ "$attach_existing" != "yes" ]; then - # Send EOF to JDB to make it go away. This will also cause - # Android to allow Emacs to continue executing. - echo "Making JDB go away..." - echo "exit" >&4 - read -u 4 line - echo "JDB has gone away with $line" +# Now that GDB is attached, tell the Java debugger to resume execution +# and then exit. + +if [ -n "$jdb_command" ]; then + echo "resume" >&${JDB[1]} + echo "exit" >&${JDB[1]} fi # Forward the gdb server port here. adb -s $device forward "tcp:$gdb_port" $gdb_socket if [ ! $? ]; then - echo "Failed to forward $app_data_dir/debug.$package_uid.socket" + echo "Failed to forward $app_data_dir/debug.$package.socket" echo "to $gdb_port! Perhaps you need to specify a different port" echo "with --port?" exit 1; @@ -284,4 +327,4 @@ fi # Finally, start gdb with any extra arguments needed. cd "$oldpwd" -gdb --eval-command "" --eval-command "target remote localhost:$gdb_port" $gdbargs +gdb --eval-command "target remote localhost:$gdb_port" $gdbargs diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 00e204c9949..ac67ebe4aa0 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -168,10 +168,22 @@ public class EmacsContextMenu { if (item.subMenu != null) { - /* This is a submenu. Create the submenu and add the - contents of the menu to it. */ - submenu = menu.addSubMenu (item.itemName); - item.subMenu.inflateMenuItems (submenu); + try + { + /* This is a submenu. On versions of Android which + support doing so, create the submenu and add the + contents of the menu to it. */ + submenu = menu.addSubMenu (item.itemName); + } + catch (UnsupportedOperationException exception) + { + /* This version of Android has a restriction + preventing submenus from being added to submenus. + Inflate everything into the parent menu + instead. */ + item.subMenu.inflateMenuItems (menu); + continue; + } /* This is still needed to set wasSubmenuSelected. */ menuItem = submenu.getItem (); diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 5d72a7860c8..7a97d706794 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -66,19 +66,11 @@ public class EmacsCopyArea paint = gc.gcPaint; - canvas = destination.lockCanvas (); + canvas = destination.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - /* A copy must be created or drawBitmap could end up overwriting itself. */ srcBitmap = source.getBitmap (); @@ -189,7 +181,6 @@ public class EmacsCopyArea maskBitmap.recycle (); } - canvas.restore (); destination.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index 5bc8efa5978..7d88a23c58f 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -168,9 +168,6 @@ public class EmacsDialog implements DialogInterface.OnDismissListener button = buttons.get (1); dialog.setButton (DialogInterface.BUTTON_NEUTRAL, button.name, button); - buttonView - = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); - buttonView.setEnabled (button.enabled); } if (size >= 3) diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 8941d4c217f..827feb96dfb 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -49,19 +49,11 @@ public class EmacsDrawLine Math.min (y, y2 + 1), Math.max (x2 + 1, x), Math.max (y2 + 1, y)); - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - paint.setStyle (Paint.Style.STROKE); if (gc.clip_mask == null) @@ -71,7 +63,6 @@ public class EmacsDrawLine /* DrawLine with clip mask not implemented; it is not used by Emacs. */ - canvas.restore (); drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index c29d413f66e..695a8c6ea44 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -23,6 +23,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.util.Log; @@ -36,51 +37,31 @@ public class EmacsDrawRectangle Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; + Rect rect; Rect maskRect, dstRect; Canvas canvas; Bitmap clipBitmap; - Rect clipRect; /* TODO implement stippling. */ if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - - /* Clip to the clipRect because some versions of Android draw an - overly wide line. */ - clipRect = new Rect (x, y, x + width + 1, - y + height + 1); - canvas.clipRect (clipRect); - paint = gc.gcPaint; + paint.setStyle (Paint.Style.STROKE); + rect = new Rect (x, y, x + width, y + height); if (gc.clip_mask == null) - { - /* canvas.drawRect just doesn't work on Android, producing - different results on various devices. Do a 5 point - PolyLine instead. */ - canvas.drawLine ((float) x, (float) y, (float) x + width, - (float) y, paint); - canvas.drawLine ((float) x + width, (float) y, - (float) x + width, (float) y + height, - paint); - canvas.drawLine ((float) x + width, (float) y + height, - (float) x, (float) y + height, paint); - canvas.drawLine ((float) x, (float) y + height, - (float) x, (float) y, paint); - } + /* Use canvas.drawRect with a RectF. That seems to reliably + get PostScript behavior. */ + canvas.drawRect (new RectF (x + 0.5f, y + 0.5f, + x + width + 0.5f, + y + height + 0.5f), + paint); else { /* Drawing with a clip mask involves calculating the @@ -137,7 +118,7 @@ public class EmacsDrawRectangle maskBitmap.recycle (); } - canvas.restore (); - drawable.damageRect (clipRect); + drawable.damageRect (new Rect (x, y, x + width + 1, + y + height + 1)); } } diff --git a/java/org/gnu/emacs/EmacsDrawable.java b/java/org/gnu/emacs/EmacsDrawable.java index 6a6199ff214..f2f8885e976 100644 --- a/java/org/gnu/emacs/EmacsDrawable.java +++ b/java/org/gnu/emacs/EmacsDrawable.java @@ -25,7 +25,7 @@ import android.graphics.Canvas; public interface EmacsDrawable { - public Canvas lockCanvas (); + public Canvas lockCanvas (EmacsGC gc); public void damageRect (Rect damageRect); public Bitmap getBitmap (); public boolean isDestroyed (); diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index 42b73886dff..22e2dd0d8a9 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -41,21 +41,13 @@ public class EmacsFillPolygon RectF rectF; int i; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; paint = gc.gcPaint; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - /* Build the path from the given array of points. */ path = new Path (); @@ -83,7 +75,6 @@ public class EmacsFillPolygon if (gc.clip_mask == null) canvas.drawPath (path, paint); - canvas.restore (); drawable.damageRect (rect); /* FillPolygon with clip mask not implemented; it is not used by diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index 7cc55d3db96..aed0a540c8f 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -32,7 +32,6 @@ public class EmacsFillRectangle perform (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - int i; Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; @@ -45,19 +44,11 @@ public class EmacsFillRectangle if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - paint = gc.gcPaint; rect = new Rect (x, y, x + width, y + height); @@ -120,7 +111,6 @@ public class EmacsFillRectangle maskBitmap.recycle (); } - canvas.restore (); drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index c579625f3f7..bdc27a1ca5b 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -47,6 +47,14 @@ public class EmacsGC extends EmacsHandleObject public EmacsPixmap clip_mask, stipple; public Paint gcPaint; + /* ID incremented every time the clipping rectangles of any GC + changes. */ + private static long clip_serial; + + /* The value of clipRectID after the last time this GCs clip + rectangles changed. 0 if there are no clip rectangles. */ + public long clipRectID; + static { xorAlu = new PorterDuffXfermode (Mode.XOR); @@ -75,23 +83,28 @@ public class EmacsGC extends EmacsHandleObject recompute real_clip_rects. */ public void - markDirty () + markDirty (boolean clipRectsChanged) { int i; - if ((ts_origin_x != 0 || ts_origin_y != 0) - && clip_rects != null) + if (clipRectsChanged) { - real_clip_rects = new Rect[clip_rects.length]; - - for (i = 0; i < clip_rects.length; ++i) + if ((ts_origin_x != 0 || ts_origin_y != 0) + && clip_rects != null) { - real_clip_rects[i] = new Rect (clip_rects[i]); - real_clip_rects[i].offset (ts_origin_x, ts_origin_y); + real_clip_rects = new Rect[clip_rects.length]; + + for (i = 0; i < clip_rects.length; ++i) + { + real_clip_rects[i] = new Rect (clip_rects[i]); + real_clip_rects[i].offset (ts_origin_x, ts_origin_y); + } } + else + real_clip_rects = clip_rects; + + clipRectID = ++clip_serial; } - else - real_clip_rects = clip_rects; gcPaint.setStrokeWidth (1f); gcPaint.setColor (foreground | 0xff000000); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 85931c2abd4..a83d8f25542 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -42,6 +42,14 @@ public class EmacsPixmap extends EmacsHandleObject /* The canvas used to draw to BITMAP. */ public Canvas canvas; + /* Whether or not GC should be explicitly triggered upon + release. */ + private boolean needCollect; + + /* ID used to determine whether or not the GC clip rects + changed. */ + private long gcClipRectID; + public EmacsPixmap (short handle, int colors[], int width, int height, int depth) @@ -83,18 +91,41 @@ public class EmacsPixmap extends EmacsHandleObject switch (depth) { case 1: - bitmap = Bitmap.createBitmap (width, height, - Bitmap.Config.ALPHA_8, - false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ALPHA_8, + false); + else + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ALPHA_8); break; case 24: - bitmap = Bitmap.createBitmap (width, height, - Bitmap.Config.ARGB_8888, - false); + + /* Emacs doesn't just use the first kind of `createBitmap' + because the latter allows specifying that the pixmap is + always opaque, which really increases efficiency. */ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ARGB_8888); + else + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ARGB_8888, + false); break; } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) + /* On these old versions of Android, Bitmap.recycle frees bitmap + contents immediately. */ + needCollect = false; + else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + needCollect = (bitmap.getByteCount () + >= 1024 * 512); + else + needCollect = (bitmap.getAllocationByteCount () + >= 1024 * 512); + bitmap.eraseColor (0xff000000); this.width = width; @@ -104,11 +135,32 @@ public class EmacsPixmap extends EmacsHandleObject @Override public Canvas - lockCanvas () + lockCanvas (EmacsGC gc) { + int i; + if (canvas == null) - canvas = new Canvas (bitmap); + { + canvas = new Canvas (bitmap); + canvas.save (); + } + /* Now see if clipping has to be redone. */ + if (gc.clipRectID == gcClipRectID) + return canvas; + + /* It does have to be redone. Reapply gc.real_clip_rects. */ + canvas.restore (); + canvas.save (); + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } + + /* Save the clip rect ID again. */ + gcClipRectID = gc.clipRectID; return canvas; } @@ -130,15 +182,6 @@ public class EmacsPixmap extends EmacsHandleObject public void destroyHandle () { - boolean needCollect; - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - needCollect = (bitmap.getByteCount () - >= 1024 * 512); - else - needCollect = (bitmap.getAllocationByteCount () - >= 1024 * 512); - bitmap.recycle (); bitmap = null; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index c0f24c7433a..a964cadb74c 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -510,20 +510,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver backgroundRect.right = x + backgroundWidth; backgroundRect.bottom = y + sdk7FontObject.descent; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return 0; - canvas.save (); paint = gc.gcPaint; - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - paint.setStyle (Paint.Style.FILL); if (withBackground) @@ -538,7 +530,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver paint.setAntiAlias (true); canvas.drawText (charsArray, 0, chars.length, x, y, paint); - canvas.restore (); bounds = new Rect (); paint.getTextBounds (charsArray, 0, chars.length, bounds); bounds.offset (x, y); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index ca38f93dc98..bcf8d9ff6e8 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -175,7 +175,12 @@ public class EmacsService extends Service { view.thing = new EmacsView (window); view.thing.setVisibility (visibility); - view.thing.setFocusedByDefault (isFocusedByDefault); + + /* The following function is only present on Android 26 + or later. */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + view.thing.setFocusedByDefault (isFocusedByDefault); + notify (); } } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 6137fd74a7f..82f44acaebe 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -83,6 +83,9 @@ public class EmacsView extends ViewGroup /* The last measured width and height. */ private int measuredWidth, measuredHeight; + /* The serial of the last clip rectangle change. */ + private long lastClipSerial; + public EmacsView (EmacsWindow window) { @@ -105,10 +108,6 @@ public class EmacsView extends ViewGroup on Android? */ setChildrenDrawingOrderEnabled (true); - /* Get rid of the foreground and background tint. */ - setBackgroundTintList (null); - setForegroundTintList (null); - /* Get rid of the default focus highlight. */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) setDefaultFocusHighlightEnabled (false); @@ -145,6 +144,11 @@ public class EmacsView extends ViewGroup /* And canvases. */ canvas = new Canvas (bitmap); + canvas.save (); + + /* Since the clip rectangles have been cleared, clear the clip + rectangle ID. */ + lastClipSerial = 0; /* Copy over the contents of the old bitmap. */ if (oldBitmap != null) @@ -177,11 +181,31 @@ public class EmacsView extends ViewGroup } public synchronized Canvas - getCanvas () + getCanvas (EmacsGC gc) { + int i; + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); + if (canvas == null) + return null; + + /* Update clip rectangles if necessary. */ + if (gc.clipRectID != lastClipSerial) + { + canvas.restore (); + canvas.save (); + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } + + lastClipSerial = gc.clipRectID; + } + return canvas; } diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index f5b50f11f14..c5b1522086c 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -164,7 +164,7 @@ public class EmacsWindow extends EmacsHandleObject { /* scratchGC is used as the argument to a FillRectangles req. */ scratchGC.foreground = pixel; - scratchGC.markDirty (); + scratchGC.markDirty (false); } public Rect @@ -466,9 +466,9 @@ public class EmacsWindow extends EmacsHandleObject @Override public Canvas - lockCanvas () + lockCanvas (EmacsGC gc) { - return view.getCanvas (); + return view.getCanvas (gc); } @Override @@ -512,37 +512,75 @@ public class EmacsWindow extends EmacsHandleObject public void onKeyDown (int keyCode, KeyEvent event) { - int state; + int state, state_1; - state = event.getModifiers (); - state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) + state = event.getModifiers (); + else + { + /* Replace this with getMetaState and manual + normalization. */ + state = event.getMetaState (); + + /* Normalize the state by setting the generic modifier bit if + either a left or right modifier is pressed. */ + + if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 + || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) + state |= KeyEvent.META_ALT_MASK; + + if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 + || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) + state |= KeyEvent.META_CTRL_MASK; + } + + /* Ignore meta-state understood by Emacs for now, or Ctrl+C will + not be recognized as an ASCII key press event. */ + state_1 + = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); EmacsNative.sendKeyPress (this.handle, event.getEventTime (), - event.getModifiers (), - keyCode, - /* Ignore meta-state understood by Emacs - for now, or Ctrl+C will not be - recognized as an ASCII key press - event. */ - event.getUnicodeChar (state)); - lastModifiers = event.getModifiers (); + state, keyCode, + event.getUnicodeChar (state_1)); + lastModifiers = state; } public void onKeyUp (int keyCode, KeyEvent event) { - int state; + int state, state_1; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) + state = event.getModifiers (); + else + { + /* Replace this with getMetaState and manual + normalization. */ + state = event.getMetaState (); + + /* Normalize the state by setting the generic modifier bit if + either a left or right modifier is pressed. */ + + if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 + || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) + state |= KeyEvent.META_ALT_MASK; + + if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 + || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) + state |= KeyEvent.META_CTRL_MASK; + } - state = event.getModifiers (); - state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + /* Ignore meta-state understood by Emacs for now, or Ctrl+C will + not be recognized as an ASCII key press event. */ + state_1 + = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); EmacsNative.sendKeyRelease (this.handle, event.getEventTime (), - event.getModifiers (), - keyCode, - event.getUnicodeChar (state)); - lastModifiers = event.getModifiers (); + state, keyCode, + event.getUnicodeChar (state_1)); + lastModifiers = state; } public void -- cgit v1.2.1 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. --- 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 +- 11 files changed, 412 insertions(+), 76 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsPreferencesActivity.java (limited to 'java') 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); -- cgit v1.2.1 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. --- java/org/gnu/emacs/EmacsThread.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java') 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; } -- cgit v1.2.1 From 4de6b187933479ce93b6079f42a485e5868f01a5 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 24 Jan 2023 10:34:40 +0800 Subject: Update Android port * .gitignore: Update with new files. Do not ignore std*.in.h. * INSTALL.android: Explain how to build Emacs with external dependencies. * Makefile.in (xcompile, cross): Rename to `cross'. (clean_dirs): Clean cross, not xcompile. * README: Document new directories. * build-aux/ndk-build-helper-1.mk (build_kind, NDK_SO_NAMES): * build-aux/ndk-build-helper-2.mk (build_kind, NDK_SO_NAMES): * build-aux/ndk-build-helper-3.mk (build_kind): * build-aux/ndk-build-helper-4.mk: * build-aux/ndk-build-helper.mk (NDK_BUILD_DIR, my-dir): * build-aux/ndk-module-extract.awk: New files. * configure.ac: Set up libgif, libwebp, and libpng for ndk-build. * cross/ndk-build/Makefile.in (srcdir, NDK_BUILD_ANDROID_MK): * cross/ndk-build/ndk-build-executable.mk: * cross/ndk-build/ndk-build-shared-library.mk (eq, objname): * cross/ndk-build/ndk-build-static-library.mk (eq, objname): * cross/ndk-build/ndk-build.in (NDK_BUILD_MODULES): * cross/ndk-build/ndk-build.mk.in (NDK_BUILD_MODULES) (NDK_BUILD_SHARED): * cross/ndk-build/ndk-clear-vars.mk: * cross/ndk-build/ndk-prebuilt-shared-library.mk: * cross/ndk-build/ndk-prebuilt-static-library.mk: New files. * doc/emacs/android.texi (Android, Android Environment): Document clipboard support on Android. * doc/emacs/emacs.texi (Top): Update menus. * etc/MACHINES: Document Android. * java/AndroidManifest.xml.in: Respect new `--with-android-debug' option. * java/Makefile.in (CROSS_BINS, CROSS_LIBS): Adjust for rename. Include ndk-build.mk.:(emacs.apk-in): Depend on shared libraries. Then, package shared libraries. * java/org/gnu/emacs/EmacsClipboard.java (EmacsClipboard): New class. * java/org/gnu/emacs/EmacsFontDriver.java: Update comment to say this is unused. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function `sendExpose'. * java/org/gnu/emacs/EmacsSdk11Clipboard.java (EmacsSdk11Clipboard): * java/org/gnu/emacs/EmacsSdk8Clipboard.java (EmacsSdk8Clipboard): New classes. * java/org/gnu/emacs/EmacsView.java (EmacsView, handleDirtyBitmap) (onDetachedFromWindow): When window is reattached, expose the frame. * lib/Makefile.in (VPATH): (ALL_CFLAGS): Adjust for rename. * lisp/term/android-win.el (android-clipboard-exists-p) (android-get-clipboard, android-set-clipboard) (android-clipboard-owner-p, android-primary-selection) (android-get-clipboard-1, android-get-primary) (android-selection-bounds, android-encode-select-string) (gui-backend-get-selection, gui-backend-selection-exists-p) (gui-backend-selection-owner-p, gui-backend-set-selection): New functions. * m4/ndk-build.m4: New file. * src/Makefile.in (GIF_CFLAGS, ANDROID_LDFLAGS): New variables. (EMACS_CFLAGS): Add GIF_CFLAGS. Include ndk-build.mk. (libemacs.so): Depend on and link with required libraries. * src/android.c (android_check_compressed_file): New function. (android_open): Work around Android platform bug. (sendExpose): New function. (android_readdir): Set d_type if this is a directory. * src/androidgui.h (enum android_event_type) (struct android_expose_event, union android_event): Add expose events. * src/androidselect.c (struct android_emacs_clipboard) (android_init_emacs_clipboard, Fandroid_clipboard_owner_p) (Fandroid_set_clipboard, Fandroid_get_clipboard) (Fandroid_clipboard_exists_p, init_androidselect) (syms_of_androidselect): New file. * src/androidterm.c (handle_one_android_event): Handle exposures. * src/androidterm.h: Update prototypes. * src/emacs.c (android_emacs_init): Initialize androidselect. --- java/AndroidManifest.xml.in | 5 +- java/Makefile.in | 30 +++--- java/org/gnu/emacs/EmacsClipboard.java | 44 ++++++++ java/org/gnu/emacs/EmacsFontDriver.java | 3 + java/org/gnu/emacs/EmacsNative.java | 4 + java/org/gnu/emacs/EmacsSdk11Clipboard.java | 156 ++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsSdk8Clipboard.java | 116 +++++++++++++++++++++ java/org/gnu/emacs/EmacsView.java | 23 ++++ 8 files changed, 368 insertions(+), 13 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsClipboard.java create mode 100644 java/org/gnu/emacs/EmacsSdk11Clipboard.java create mode 100644 java/org/gnu/emacs/EmacsSdk8Clipboard.java (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 74f69d2a8e5..c4a9d1f5177 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -60,12 +60,13 @@ along with GNU Emacs. If not, see . --> android:hardwareAccelerated="true" android:supportsRtl="true" android:theme="@android:style/Theme" - android:debuggable="true" + android:debuggable="@ANDROID_DEBUGGABLE@" android:extractNativeLibs="true"> @@ -76,10 +77,12 @@ along with GNU Emacs. If not, see . --> diff --git a/java/Makefile.in b/java/Makefile.in index 22c912fdce5..d27775ea3db 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -81,21 +81,24 @@ APK_NAME = emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk all: $(APK_NAME) # Binaries to cross-compile. -CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \ - ../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ - ../xcompile/lib-src/ebrowse ../xcompile/lib-src/emacsclient +CROSS_BINS = ../cross/src/android-emacs ../cross/lib-src/ctags \ + ../cross/lib-src/hexl ../cross/lib-src/movemail \ + ../cross/lib-src/ebrowse ../cross/lib-src/emacsclient # Libraries to cross-compile. -CROSS_LIBS = ../xcompile/src/libemacs.so +CROSS_LIBS = ../cross/src/libemacs.so + +# Third party libraries to compile. +include $(top_builddir)/cross/ndk-build/ndk-build.mk .PHONY: $(CROSS_BINS) $(CROSS_LIBS) -../xcompile/src/android-emacs ../xcompile/src/libemacs.so: - make -C ../xcompile src/$(notdir $@) +../cross/src/android-emacs ../cross/src/libemacs.so: + make -C ../cross src/$(notdir $@) -../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ -../xcompile/lib-src/ctags ../xcompile/lib-src/ebrowse &: - make -C ../xcompile lib-src/$(notdir $@) +../cross/lib-src/hexl ../cross/lib-src/movemail \ +../cross/lib-src/ctags ../cross/lib-src/ebrowse &: + make -C ../cross lib-src/$(notdir $@) # This is needed to generate the ``.directory-tree'' file used by the # Android emulations of readdir and faccessat. @@ -104,7 +107,7 @@ $(libsrc)/asset-directory-tool: $(MAKE) -C $(libsrc) $(notdir $@) emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ - AndroidManifest.xml + AndroidManifest.xml $(NDK_BUILD_SHARED) # Make the working directory for this stuff rm -rf install_temp mkdir -p install_temp/lib/$(ANDROID_ABI) @@ -145,11 +148,13 @@ 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)) # Package everything. Specifying the assets on this command line is # necessary for AAssetManager_getNextFileName to work on old versions # of Android. - $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml \ - -A install_temp/assets + $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f \ + -M AndroidManifest.xml -A install_temp/assets pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd rm -rf install_temp @@ -196,3 +201,4 @@ clean: find . -name '*.class' -delete maintainer-clean distclean bootstrap-clean: clean + rm -f Makefile ndk-build.mk diff --git a/java/org/gnu/emacs/EmacsClipboard.java b/java/org/gnu/emacs/EmacsClipboard.java new file mode 100644 index 00000000000..cd6bcebfe0e --- /dev/null +++ b/java/org/gnu/emacs/EmacsClipboard.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 android.os.Build; + +/* This class provides helper code for accessing the clipboard, + abstracting between the different interfaces on API 8 and 11. */ + +public abstract class EmacsClipboard +{ + public abstract void setClipboard (byte[] bytes); + public abstract int ownsClipboard (); + public abstract boolean clipboardExists (); + public abstract byte[] getClipboard (); + + /* Create the correct kind of clipboard for this system. */ + + public static EmacsClipboard + makeClipboard () + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + return new EmacsSdk11Clipboard (); + else + return new EmacsSdk8Clipboard (); + } +}; diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index 1d1e6f7b33f..39bda5a456d 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -23,6 +23,9 @@ import java.util.List; import android.os.Build; +/* This code is mostly unused. See sfntfont-android.c for the code + that is actually used. */ + public abstract class EmacsFontDriver { /* Font weights. */ diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 3efdc0cff9a..962538bef7b 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -132,6 +132,10 @@ public class EmacsNative /* Send an ANDROID_CONTEXT_MENU event. */ public static native long sendContextMenu (short window, int menuEventID); + /* Send an ANDROID_EXPOSE event. */ + public static native long sendExpose (short window, int x, int y, + int width, int height); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java new file mode 100644 index 00000000000..0a725200723 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -0,0 +1,156 @@ +/* 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.content.ClipboardManager; +import android.content.Context; +import android.content.ClipData; + +import android.util.Log; + +import java.io.UnsupportedEncodingException; + +/* This class implements EmacsClipboard for Android 3.0 and later + systems. */ + +public class EmacsSdk11Clipboard extends EmacsClipboard + implements ClipboardManager.OnPrimaryClipChangedListener +{ + private static final String TAG = "EmacsSdk11Clipboard"; + private ClipboardManager manager; + private boolean ownsClipboard; + private int clipboardChangedCount; + private int monitoredClipboardChangedCount; + + public + EmacsSdk11Clipboard () + { + String what; + Context context; + + what = Context.CLIPBOARD_SERVICE; + context = EmacsService.SERVICE; + manager + = (ClipboardManager) context.getSystemService (what); + manager.addPrimaryClipChangedListener (this); + } + + @Override + public synchronized void + onPrimaryClipChanged () + { + Log.d (TAG, ("onPrimaryClipChanged: " + + monitoredClipboardChangedCount + + " " + clipboardChangedCount)); + + /* Increment monitoredClipboardChangeCount. If it is now greater + than clipboardChangedCount, then Emacs no longer owns the + clipboard. */ + monitoredClipboardChangedCount++; + + if (monitoredClipboardChangedCount > clipboardChangedCount) + { + ownsClipboard = false; + + /* Reset both values back to 0. */ + monitoredClipboardChangedCount = 0; + clipboardChangedCount = 0; + } + } + + /* Set the clipboard text to CLIPBOARD, a string in UTF-8 + encoding. */ + + @Override + public synchronized void + setClipboard (byte[] bytes) + { + ClipData data; + String string; + + try + { + string = new String (bytes, "UTF-8"); + data = ClipData.newPlainText ("Emacs", string); + manager.setPrimaryClip (data); + ownsClipboard = true; + + /* onPrimaryClipChanged will be called again. Use this + variable to keep track of how many times the clipboard has + been changed. */ + ++clipboardChangedCount; + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "setClipboard: " + exception); + } + } + + /* Return whether or not Emacs owns the clipboard. Value is 1 if + Emacs does, 0 if Emacs does not, and -1 if that information is + unavailable. */ + + @Override + public synchronized int + ownsClipboard () + { + return ownsClipboard ? 1 : 0; + } + + /* Return whether or not clipboard content currently exists. */ + + @Override + public boolean + clipboardExists () + { + return manager.hasPrimaryClip (); + } + + /* Return the current content of the clipboard, as plain text, or + NULL if no content is available. */ + + @Override + public byte[] + getClipboard () + { + ClipData clip; + CharSequence text; + Context context; + + clip = manager.getPrimaryClip (); + + if (clip == null || clip.getItemCount () < 1) + return null; + + context = EmacsService.SERVICE; + + try + { + text = clip.getItemAt (0).coerceToText (context); + return text.toString ().getBytes ("UTF-8"); + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "getClipboard: " + exception); + } + + return null; + } +}; diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java b/java/org/gnu/emacs/EmacsSdk8Clipboard.java new file mode 100644 index 00000000000..34e66912562 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java @@ -0,0 +1,116 @@ +/* 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; + +/* Importing the entire package avoids the deprecation warning. */ +import android.text.*; + +import android.content.Context; +import android.util.Log; + +import java.io.UnsupportedEncodingException; + +/* This class implements EmacsClipboard for Android 2.2 and other + similarly old systems. */ + +@SuppressWarnings ("deprecation") +public class EmacsSdk8Clipboard extends EmacsClipboard +{ + private static final String TAG = "EmacsSdk8Clipboard"; + private ClipboardManager manager; + + public + EmacsSdk8Clipboard () + { + String what; + Context context; + + what = Context.CLIPBOARD_SERVICE; + context = EmacsService.SERVICE; + manager + = (ClipboardManager) context.getSystemService (what); + } + + /* Set the clipboard text to CLIPBOARD, a string in UTF-8 + encoding. */ + + @Override + public void + setClipboard (byte[] bytes) + { + try + { + manager.setText (new String (bytes, "UTF-8")); + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "setClipboard: " + exception); + } + } + + /* Return whether or not Emacs owns the clipboard. Value is 1 if + Emacs does, 0 if Emacs does not, and -1 if that information is + unavailable. */ + + @Override + public int + ownsClipboard () + { + return -1; + } + + /* Return whether or not clipboard content currently exists. */ + + @Override + public boolean + clipboardExists () + { + return manager.hasText (); + } + + /* Return the current content of the clipboard, as plain text, or + NULL if no content is available. */ + + @Override + public byte[] + getClipboard () + { + String string; + CharSequence text; + + text = manager.getText (); + + if (text == null) + return null; + + string = text.toString (); + + try + { + return string.getBytes ("UTF-8"); + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "getClipboard: " + exception); + } + + return null; + } +}; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 74bbb7b3ecc..881cbc363ba 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -99,6 +99,9 @@ public class EmacsView extends ViewGroup yet responded to. 0 if there is no such outstanding event. */ public long pendingConfigure; + /* Whether or not this view is attached to a window. */ + public boolean isAttachedToWindow; + public EmacsView (EmacsWindow window) { @@ -140,6 +143,9 @@ public class EmacsView extends ViewGroup if (measuredWidth == 0 || measuredHeight == 0) return; + if (!isAttachedToWindow) + return; + /* If bitmap is the same width and height as the measured width and height, there is no need to do anything. Avoid allocating the extra bitmap. */ @@ -547,6 +553,8 @@ public class EmacsView extends ViewGroup public synchronized void onDetachedFromWindow () { + isAttachedToWindow = false; + synchronized (this) { /* Recycle the bitmap and call GC. */ @@ -559,6 +567,21 @@ public class EmacsView extends ViewGroup } } + @Override + public synchronized void + onAttachedToWindow () + { + isAttachedToWindow = true; + + /* Dirty the bitmap, as it was destroyed when onDetachedFromWindow + was called. */ + bitmapDirty = true; + + /* Now expose the view contents again. */ + EmacsNative.sendExpose (this.window.handle, 0, 0, + measuredWidth, measuredHeight); + } + public void showOnScreenKeyboard () { -- cgit v1.2.1 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. --- 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 +++++++-- 7 files changed, 132 insertions(+), 8 deletions(-) (limited to 'java') 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); -- cgit v1.2.1 From 9082b4e6ee27b995cbb61668cb437d7b91c7f5f8 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 24 Jan 2023 21:37:22 +0800 Subject: Make binaries distributed with Emacs work on Android * doc/lispref/processes.texi (Subprocess Creation): Document variables containing program names. * etc/NEWS: Document new variables. * java/Makefile.in (CROSS_BINS): Add missing etags binary. * lisp/cedet/semantic/db-ebrowse.el (semanticdb-create-ebrowse-database): * lisp/gnus/mail-source.el (mail-source-movemail-program): * lisp/hexl.el (hexl-program): * lisp/htmlfontify.el (hfy-etags-bin): * lisp/ielm.el (inferior-emacs-lisp-mode): * lisp/mail/rmail.el (rmail-autodetect): (rmail-insert-inbox-text): * lisp/org/org-ctags.el (org-ctags-path-to-ctags): * lisp/progmodes/cperl-mode.el (cperl-etags): * lisp/speedbar.el (speedbar-fetch-etags-command): * lisp/textmodes/reftex-global.el (reftex-create-tags-file): Use new variables. * src/callproc.c (syms_of_callproc): New variables naming binaries redistributed with Emacs. --- java/Makefile.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index b5e0cd7bb56..b32b2442305 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -69,6 +69,7 @@ APK_NAME = emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk # lib/$(ANDROID_ABI)/libemacs.so # lib/$(ANDROID_ABI)/libandroid-emacs.so # lib/$(ANDROID_ABI)/libctags.so +# lib/$(ANDROID_ABI)/libetags.so # lib/$(ANDROID_ABI)/libhexl.so # lib/$(ANDROID_ABI)/libmovemail.so # lib/$(ANDROID_ABI)/librcs2log.so @@ -83,7 +84,8 @@ all: $(APK_NAME) # Binaries to cross-compile. CROSS_BINS = ../cross/src/android-emacs ../cross/lib-src/ctags \ ../cross/lib-src/hexl ../cross/lib-src/movemail \ - ../cross/lib-src/ebrowse ../cross/lib-src/emacsclient + ../cross/lib-src/ebrowse ../cross/lib-src/emacsclient \ + ../cross/lib-src/etags # Libraries to cross-compile. CROSS_LIBS = ../cross/src/libemacs.so -- cgit v1.2.1 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. --- 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 +- 6 files changed, 1067 insertions(+), 46 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsNoninteractive.java (limited to 'java') 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); } }; -- cgit v1.2.1 From 888d3514967a90423d60dfa82bfd59b7f3df6c90 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 25 Jan 2023 19:15:30 +0800 Subject: Minor fixes to Android port * java/Makefile.in: (emacs.apk-in): Don't call cp with empty args. * java/org/gnu/emacs/EmacsDrawLine.java (perform): Fix for PostScript filling semantics. * src/Makefile.in (android-emacs): Build android-emacs directly. --- java/Makefile.in | 2 ++ java/org/gnu/emacs/EmacsDrawLine.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index b32b2442305..755995b93b1 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -150,7 +150,9 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done +ifneq ($(NDK_BUILD_SHARED),) cp -f $(NDK_BUILD_SHARED) install_temp/lib/$(ANDROID_ABI) +endif # 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/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 827feb96dfb..717e2279a7d 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -56,9 +56,12 @@ public class EmacsDrawLine paint.setStyle (Paint.Style.STROKE); + /* Since drawLine has PostScript style behavior, adjust the + coordinates appropriately. */ + if (gc.clip_mask == null) - canvas.drawLine ((float) x, (float) y, - (float) x2, (float) y2, + canvas.drawLine ((float) x + 0.5f, (float) y + 0.5f, + (float) x2 + 0.5f, (float) y2 + 0.5f, paint); /* DrawLine with clip mask not implemented; it is not used by -- cgit v1.2.1 From 0b1ef9ea31ce039001546b3ed34494332e5e3629 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 25 Jan 2023 22:07:51 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsDrawLine.java: Fix this again. Gosh, how does Android do this. * java/org/gnu/emacs/EmacsNoninteractive.java (main): Port to Android 2.3.3. * java/org/gnu/emacs/EmacsSdk11Clipboard.java (EmacsSdk11Clipboard): Port to Android 4.0.3. * java/org/gnu/emacs/EmacsService.java (getClipboardManager): New function. * src/alloc.c (find_string_data_in_pure): Fix Android alignment issue. * src/android-emacs.c (main): Port to Android 4.4. * src/android.c (initEmacs): Align stack to 32 bytes, so it ends up aligned to 16 even though gcc thinks the stack is already aligned to 16 bytes. * src/callproc.c (init_callproc): Use /system/bin/sh instead of /bin/sh by default. --- java/org/gnu/emacs/EmacsDrawLine.java | 2 +- java/org/gnu/emacs/EmacsNoninteractive.java | 30 +++++++++++++++---- java/org/gnu/emacs/EmacsSdk11Clipboard.java | 8 +---- java/org/gnu/emacs/EmacsService.java | 46 +++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 14 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 717e2279a7d..c6e5123bfca 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -60,7 +60,7 @@ public class EmacsDrawLine coordinates appropriately. */ if (gc.clip_mask == null) - canvas.drawLine ((float) x + 0.5f, (float) y + 0.5f, + canvas.drawLine ((float) x, (float) y + 0.5f, (float) x2 + 0.5f, (float) y2 + 0.5f, paint); diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index a3aefee5e0b..b4854d8323f 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -95,7 +95,7 @@ public class EmacsNoninteractive On Android 2.3.3 and earlier, there is no ``compatibilityInfo'' argument to getPackageInfo. */ - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { method = activityThreadClass.getMethod ("getPackageInfo", @@ -123,11 +123,29 @@ public class EmacsNoninteractive /* 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); + + try + { + method = contextImplClass.getDeclaredMethod ("createAppContext", + activityThreadClass, + loadedApkClass); + method.setAccessible (true); + context = (Context) method.invoke (null, activityThread, loadedApk); + } + catch (NoSuchMethodException exception) + { + /* Older Android versions don't have createAppContext, but + instead require creating a ContextImpl, and then + calling createPackageContext. */ + method = activityThreadClass.getDeclaredMethod ("getSystemContext"); + context = (Context) method.invoke (activityThread); + method = contextImplClass.getDeclaredMethod ("createPackageContext", + String.class, + int.class); + method.setAccessible (true); + context = (Context) method.invoke (context, "org.gnu.emacs", + 0); + } /* Don't actually start the looper or anything. Instead, obtain an AssetManager. */ diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java index 0a725200723..2df2015c9c1 100644 --- a/java/org/gnu/emacs/EmacsSdk11Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -42,13 +42,7 @@ public class EmacsSdk11Clipboard extends EmacsClipboard public EmacsSdk11Clipboard () { - String what; - Context context; - - what = Context.CLIPBOARD_SERVICE; - context = EmacsService.SERVICE; - manager - = (ClipboardManager) context.getSystemService (what); + manager = EmacsService.SERVICE.getClipboardManager (); manager.addPrimaryClipChangedListener (this); } diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 91db76b08e3..eb9b61dd876 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -39,6 +39,7 @@ import android.app.NotificationChannel; import android.app.PendingIntent; import android.app.Service; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -565,4 +566,49 @@ public class EmacsService extends Service return null; } + + /* Get a SDK 11 ClipboardManager. + + Android 4.0.x requires that this be called from the main + thread. */ + + public ClipboardManager + getClipboardManager () + { + final Holder manager; + Runnable runnable; + + manager = new Holder (); + + runnable = new Runnable () { + public void + run () + { + Object tem; + + synchronized (this) + { + tem = getSystemService (Context.CLIPBOARD_SERVICE); + manager.thing = (ClipboardManager) tem; + notify (); + } + } + }; + + synchronized (runnable) + { + runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + + return manager.thing; + } }; -- cgit v1.2.1 From 4255d7f0514c5fa1badded6b0bc445ec2d2764c0 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 26 Jan 2023 15:37:04 +0800 Subject: Update Android port * .gitignore: Ignore lib/math.h. * INSTALL.android: Update accordingly. * build-aux/ndk-build-helper-1.mk: * build-aux/ndk-build-helper-2.mk: * build-aux/ndk-build-helper.mk: * build-aux/ndk-module-extract.awk: Handle C++ modules. * configure.ac: Enable libxml2 on Android. * cross/ndk-build/Makefile.in: * cross/ndk-build/ndk-build-shared-library.mk: * cross/ndk-build/ndk-build-static-library.mk: * cross/ndk-build/ndk-build.mk.in: * cross/ndk-build/ndk-resolve.mk: Fix dependency resolution of includes. * java/org/gnu/emacs/EmacsView.java (popupMenu): Fix minimum SDK version for actual popup menus. * lib/math.h: Delete file. * m4/ndk-build.m4 (ndk_SEARCH_MODULE, ndk_CHECK_MODULES): Look for nasm and C++ libraries. * src/android.c (faccessat): Rename to `android_faccessat'. * src/android.h: Update prototypes. * src/dired.c (file_name_completion_dirp): * src/fileio.c (file_access_p, Faccess_file, file_directory_p): * src/lisp.h: * src/lread.c (openp): * src/process.c (allocate_pty): Use sys_faccessat. * src/sysdep.c (sys_faccessat): New function. --- java/org/gnu/emacs/EmacsView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 881cbc363ba..a1953f683bd 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -531,8 +531,9 @@ public class EmacsView extends ViewGroup contextMenu = menu; popupActive = true; - /* On API 21 or later, use showContextMenu (float, float). */ - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) + /* Use showContextMenu (float, float) on N to get actual popup + behavior. */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) return showContextMenu ((float) xPosition, (float) yPosition); else return showContextMenu (); -- cgit v1.2.1 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. --- 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 ++++- 5 files changed, 19 insertions(+), 7 deletions(-) (limited to 'java') 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"/> = Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -544,6 +549,20 @@ public class EmacsWindow extends EmacsHandleObject state, keyCode, event.getUnicodeChar (state_1)); lastModifiers = state; + + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) + { + /* Check if this volume down press should quit Emacs. + Most Android devices have no physical keyboard, so it + is unreasonably hard to press C-g. */ + + time = event.getEventTime (); + + if (lastVolumeButtonPress - time < 350) + EmacsNative.quit (); + + lastVolumeButtonPress = time; + } } public void -- cgit v1.2.1 From e3b50ec8ec1b8d1246aa2496f3fe8bf98d354545 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 28 Jan 2023 14:29:51 +0800 Subject: Update Android port * INSTALL.android: Document support for gnutls and libgmp. * build-aux/ndk-build-helper-1.mk (NDK_SO_NAMES, NDK_INCLUDES) (SYSTEM_LIBRARIES): * build-aux/ndk-build-helper-2.mk: Recursively resolve and add shared library dependencies; even those of static libraries. * build-aux/ndk-module-extract.awk: Fix makefile_imports code. * configure.ac (ANDROID_SDK_18_OR_EARLIER, XCONFIGURE) (LIBGMP_CFLAGS): Enable GMP and gnutls on Android. * cross/ndk-build/Makefile.in (LOCAL_EXPORT_C_INCLUDES): * cross/ndk-build/ndk-build-shared-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix)::($(LOCAL_MODULE_FILENAME)): * cross/ndk-build/ndk-build-static-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix): * cross/ndk-build/ndk-clear-vars.mk: * cross/ndk-build/ndk-resolve.mk (NDK_SYSTEM_LIBRARIES): (NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE)): (NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)): Implement ``LOCAL_ASM_RULE'' and ``LOCAL_C_ADDITIONAL_FLAGS'' extensions for libgmp. * doc/emacs/input.texi (Touchscreens): Document how to horizontally scroll. * java/org/gnu/emacs/EmacsActivity.java (attachWindow): Give the view focus again if necessary. (onPause): Call right super function. * java/org/gnu/emacs/EmacsPreferencesActivity.java (onClick): Clear dumpFileName lest Emacs try to load a nonexistent dump file. * java/org/gnu/emacs/EmacsView.java (onDetachedFromWindow) (onAttachedToWindow): Call super functions. (onCreateInputConnection): Make sure the IME never obscures Emacs. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyDown) (onKeyUp): Improve tracking of quit keys. * lisp/isearch.el (isearch-mode): Bring up the onscreen keyboard. * lisp/touch-screen.el (touch-screen-current-tool): Add three fields. (touch-screen-handle-scroll): Allow hscrolling as well. (touch-screen-handle-touch): Add additional fields to `touch-screen-current-tool'. * src/Makefile.in (LIBGMP_CFLAGS, EMACS_CFLAGS): Add new variable. * src/android.c (android_run_select_thread): (android_write_event): Use pthread_cond_broadcast because pthread_cond_signal does nothing on some Android versions/devices? --- java/org/gnu/emacs/EmacsActivity.java | 6 +++- java/org/gnu/emacs/EmacsPreferencesActivity.java | 5 ++++ java/org/gnu/emacs/EmacsView.java | 21 ++++++++++++++ java/org/gnu/emacs/EmacsWindow.java | 36 ++++++++++++------------ 4 files changed, 49 insertions(+), 19 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index d377cf982ef..30eaf85805a 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -134,6 +134,10 @@ public class EmacsActivity extends Activity layout.addView (window.view); child.setConsumer (this); + /* If the window isn't no-focus-on-map, focus its view. */ + if (!child.getDontFocusOnMap ()) + window.view.requestFocus (); + /* If the activity is iconified, send that to the window. */ if (isPaused) window.noticeIconified (); @@ -233,7 +237,7 @@ public class EmacsActivity extends Activity isPaused = true; EmacsWindowAttachmentManager.MANAGER.noticeIconified (this); - super.onResume (); + super.onPause (); } @Override diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index ed1db68f732..6cef7c37516 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -116,6 +116,11 @@ public class EmacsPreferencesActivity extends Activity if (file.exists ()) file.delete (); + + /* Make sure to clear EmacsApplication.dumpFileName, or + starting Emacs without restarting this program will + make Emacs try to load a nonexistent dump file. */ + EmacsApplication.dumpFileName = null; } }); layout.addView (textView); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index a1953f683bd..730ed02d4f1 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -22,12 +22,16 @@ package org.gnu.emacs; import android.content.Context; import android.content.res.ColorStateList; +import android.text.InputType; + import android.view.ContextMenu; import android.view.View; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.graphics.Bitmap; @@ -566,6 +570,8 @@ public class EmacsView extends ViewGroup /* Collect the bitmap storage; it could be large. */ Runtime.getRuntime ().gc (); } + + super.onDetachedFromWindow (); } @Override @@ -581,6 +587,8 @@ public class EmacsView extends ViewGroup /* Now expose the view contents again. */ EmacsNative.sendExpose (this.window.handle, 0, 0, measuredWidth, measuredHeight); + + super.onAttachedToWindow (); } public void @@ -615,4 +623,17 @@ public class EmacsView extends ViewGroup drawingFinished = null; } } + + @Override + public InputConnection + onCreateInputConnection (EditorInfo info) + { + /* Make sure the input method never displays a full screen input + box that obscures Emacs. */ + info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; + + /* But don't return an InputConnection, in order to force the on + screen keyboard to work correctly. */ + return null; + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 39eaf2fff80..5c2b77b0125 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -124,9 +124,9 @@ public class EmacsWindow extends EmacsHandleObject there is no such window manager. */ private WindowManager windowManager; - /* The time of the last KEYCODE_VOLUME_DOWN press. This is used to - quit Emacs. */ - private long lastVolumeButtonPress; + /* The time of the last KEYCODE_VOLUME_DOWN release. This is used + to quit Emacs. */ + private long lastVolumeButtonRelease; public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, @@ -517,7 +517,6 @@ public class EmacsWindow extends EmacsHandleObject onKeyDown (int keyCode, KeyEvent event) { int state, state_1; - long time; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -549,26 +548,13 @@ public class EmacsWindow extends EmacsHandleObject state, keyCode, event.getUnicodeChar (state_1)); lastModifiers = state; - - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) - { - /* Check if this volume down press should quit Emacs. - Most Android devices have no physical keyboard, so it - is unreasonably hard to press C-g. */ - - time = event.getEventTime (); - - if (lastVolumeButtonPress - time < 350) - EmacsNative.quit (); - - lastVolumeButtonPress = time; - } } public void onKeyUp (int keyCode, KeyEvent event) { int state, state_1; + long time; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -600,6 +586,20 @@ public class EmacsWindow extends EmacsHandleObject state, keyCode, event.getUnicodeChar (state_1)); lastModifiers = state; + + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) + { + /* Check if this volume down press should quit Emacs. + Most Android devices have no physical keyboard, so it + is unreasonably hard to press C-g. */ + + time = event.getEventTime (); + + if (time - lastVolumeButtonRelease < 350) + EmacsNative.quit (); + + lastVolumeButtonRelease = time; + } } public void -- cgit v1.2.1 From 198b8160cfeeb178d3b2073c8d03afdafe338908 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 28 Jan 2023 16:29:22 +0800 Subject: Update Android port * doc/emacs/android.texi (Android File System): Describe an easier way to disable scoped storage. * java/AndroidManifest.xml.in: Add new permission to allow that. * java/README: Add more text describing Java. * java/org/gnu/emacs/EmacsContextMenu.java (Item): New fields `isCheckable' and `isChecked'. (EmacsContextMenu, addItem): New arguments. (inflateMenuItems): Set checked status as appropriate. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Disallow operations where width and height are less than or equal to zero. * lisp/menu-bar.el (menu-bar-edit-menu): Make execute-extended-command available as a menu item. * src/androidmenu.c (android_init_emacs_context_menu) (android_menu_show): * src/menu.c (have_boxes): Implement menu check boxes. --- java/AndroidManifest.xml.in | 4 ++ java/README | 63 ++++++++++++++++++++++++++++++-- java/org/gnu/emacs/EmacsContextMenu.java | 22 +++++++++-- java/org/gnu/emacs/EmacsCopyArea.java | 6 +++ 4 files changed, 88 insertions(+), 7 deletions(-) (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 527ce74c474..09e4e788e0b 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -52,6 +52,10 @@ along with GNU Emacs. If not, see . --> + + + + diff --git a/java/README b/java/README index 44f5a415162..3bce2556403 100644 --- a/java/README +++ b/java/README @@ -292,15 +292,15 @@ public class EmacsFrobinicator } } -Java arrays are similar to C arrays in that they can not grow. But +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 +(as opposed to decaying into pointers in only some 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: +Instead, it may simply iterate over the array like so: int i, k; @@ -339,10 +339,65 @@ struct emacs_array_container or, possibly even better, -typedef int my_array[10]; +typedef int emacs_array_container[10]; Alas, Java has no equivalent of `typedef'. +Like in C, Java string literals are delimited by double quotes. +Unlike C, however, strings are not NULL-terminated arrays of +characters, but a distinct type named ``String''. They store their +own length, characters in Java's 16-bit ``char'' type, and are capable +of holding NULL bytes. + +Instead of writing: + +wchar_t character; +extern char *s; +size_t s; + + for (/* determine n, s in a loop. */) + s += mbstowc (&character, s, n); + +or: + +const char *byte; + +for (byte = my_string; *byte; ++byte) + /* do something with *byte. */; + +or perhaps even: + +size_t length, i; +char foo; + +length = strlen (my_string); + +for (i = 0; i < length; ++i) + foo = my_string[i]; + +you write: + +char foo; +int i; + +for (i = 0; i < myString.length (); ++i) + foo = myString.charAt (0); + +Java also has stricter rules on what can be used as a truth value in a +conditional. While in C, any non-zero value is true, Java requires +that every truth value be of the boolean type ``boolean''. + +What this means is that instead of simply writing: + + if (foo || bar) + +where foo can either be 1 or 0, and bar can either be NULL or a +pointer to something, you must explicitly write: + + if (foo != 0 || bar != null) + +in Java. + JAVA NATIVE INTERFACE Java also provides an interface for C code to interface with Java. diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 056d8fb692c..92429410d03 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -56,7 +56,7 @@ public class EmacsContextMenu public int itemID; public String itemName; public EmacsContextMenu subMenu; - public boolean isEnabled; + public boolean isEnabled, isCheckable, isChecked; @Override public boolean @@ -108,10 +108,15 @@ public class EmacsContextMenu /* Add a normal menu item to the context menu with the id ITEMID and the name ITEMNAME. Enable it if ISENABLED, else keep it - disabled. */ + disabled. + + If this is not a submenu and ISCHECKABLE is set, make the item + checkable. Likewise, if ISCHECKED is set, make the item + checked. */ public void - addItem (int itemID, String itemName, boolean isEnabled) + addItem (int itemID, String itemName, boolean isEnabled, + boolean isCheckable, boolean isChecked) { Item item; @@ -119,6 +124,8 @@ public class EmacsContextMenu item.itemID = itemID; item.itemName = itemName; item.isEnabled = isEnabled; + item.isCheckable = isCheckable; + item.isChecked = isChecked; menuItems.add (item); } @@ -198,6 +205,15 @@ public class EmacsContextMenu /* If the item ID is zero, then disable the item. */ if (item.itemID == 0 || !item.isEnabled) menuItem.setEnabled (false); + + /* Now make the menu item display a checkmark as + appropriate. */ + + if (item.isCheckable) + menuItem.setCheckable (true); + + if (item.isChecked) + menuItem.setChecked (true); } } } diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 7a97d706794..f8974e17c2e 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -99,6 +99,12 @@ public class EmacsCopyArea if (src_y + height > srcBitmap.getHeight ()) height = srcBitmap.getHeight () - src_y; + /* If width and height are empty or negative, then skip the entire + CopyArea operation lest createBitmap throw an exception. */ + + if (width <= 0 || height <= 0) + return; + rect = new Rect (dest_x, dest_y, dest_x + width, dest_y + height); -- cgit v1.2.1 From f9e32ce1575da69cc3a9e4690b6df2dbee41d14d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 28 Jan 2023 21:21:45 +0800 Subject: Implement `restart-emacs' on Android * java/org/gnu/emacs/EmacsService.java (restartEmacs): New function. * src/android.c (struct android_emacs_service) (android_init_emacs_service): Add new method. (android_restart_emacs): New function. * src/android.h: Update prototypes. * src/emacs.c (Fkill_emacs): Call android_restart_emacs whenever appropriate. --- java/org/gnu/emacs/EmacsService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index eb9b61dd876..d17f6d1286c 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -611,4 +611,16 @@ public class EmacsService extends Service return manager.thing; } + + public void + restartEmacs () + { + Intent intent; + + intent = new Intent (this, EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity (intent); + System.exit (0); + } }; -- cgit v1.2.1 From 492aa74b13bb2c9d5a751d043fed2ef9e9365781 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 29 Jan 2023 11:21:07 +0800 Subject: Update Android port * INSTALL.android (module_target): Clarify documentation. * cross/ndk-build/ndk-build-shared-library.mk: * cross/ndk-build/ndk-build-static-library.mk: Fix building Neon objects. * java/AndroidManifest.xml.in: Add a version code. --- java/AndroidManifest.xml.in | 1 + 1 file changed, 1 insertion(+) (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 09e4e788e0b..3833e1735c3 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -24,6 +24,7 @@ along with GNU Emacs. If not, see . --> package="org.gnu.emacs" android:targetSandboxVersion="1" android:installLocation="auto" + android:versionCode="@emacs_major_version@" android:versionName="@version@"> . --> + -- cgit v1.2.1 From 420533a8f9b345699dad9eeafeb3ccecfed516b2 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 4 Feb 2023 23:32:07 +0800 Subject: Add emacsclient desktop file equivalent on Android * doc/emacs/android.texi (Android File System): * java/AndroidManifest.xml.in: Update with new activity. Remove Android 10 restrictions through a special flag. * java/org/gnu/emacs/EmacsNative.java (getProcName): New function. * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity): New file. * java/org/gnu/emacs/EmacsService.java (getLibraryDirection): Remove unused annotation. * lib-src/emacsclient.c (decode_options): Set alt_display on Android. * src/android.c (android_proc_name): New function. (NATIVE_NAME): Export via JNI. --- java/AndroidManifest.xml.in | 79 +++++++ java/org/gnu/emacs/EmacsNative.java | 4 + java/org/gnu/emacs/EmacsOpenActivity.java | 357 ++++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsService.java | 1 - 4 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 java/org/gnu/emacs/EmacsOpenActivity.java (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 544c87e1f1e..923c5a005d5 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -24,6 +24,7 @@ along with GNU Emacs. If not, see . --> package="org.gnu.emacs" android:targetSandboxVersion="1" android:installLocation="auto" + android:requestLegacyExternalStorage="true" android:versionCode="@emacs_major_version@" android:versionName="@version@"> @@ -82,6 +83,84 @@ along with GNU Emacs. If not, see . --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . */ + +package org.gnu.emacs; + +/* This class makes the Emacs server work reasonably on Android. + + There is no way to make the Unix socket publicly available on + Android. + + Instead, this activity tries to connect to the Emacs server, to + make it open files the system asks Emacs to open, and to emulate + some reasonable behavior when Emacs has not yet started. + + First, Emacs registers itself as an application that can open text + and image files. + + Then, when the user is asked to open a file and selects ``Emacs'' + as the application that will open the file, the system pops up a + window, this activity, and calls the `onCreate' function. + + `onCreate' then tries very to find the file name of the file that + was selected, and give it to emacsclient. + + If emacsclient successfully opens the file, then this activity + starts EmacsActivity (to bring it on to the screen); otherwise, it + displays the output of emacsclient or any error message that occurs + and exits. */ + +import android.app.AlertDialog; +import android.app.Activity; + +import android.content.Context; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; + +import android.net.Uri; + +import android.os.Build; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.io.FileReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +public class EmacsOpenActivity extends Activity + implements DialogInterface.OnClickListener +{ + private class EmacsClientThread extends Thread + { + private ProcessBuilder builder; + + public + EmacsClientThread (ProcessBuilder processBuilder) + { + builder = processBuilder; + } + + @Override + public void + run () + { + Process process; + InputStream error; + String errorText; + + try + { + /* Start emacsclient. */ + process = builder.start (); + process.waitFor (); + + /* Now figure out whether or not starting the process was + successful. */ + if (process.exitValue () == 0) + finishSuccess (); + else + finishFailure ("Error opening file", null); + } + catch (IOException exception) + { + finishFailure ("Internal error", exception.toString ()); + } + catch (InterruptedException exception) + { + finishFailure ("Internal error", exception.toString ()); + } + } + } + + @Override + public void + onClick (DialogInterface dialog, int which) + { + finish (); + } + + public String + readEmacsClientLog () + { + File file, cache; + FileReader reader; + char[] buffer; + int rc; + String what; + + cache = getCacheDir (); + file = new File (cache, "emacsclient.log"); + what = ""; + + try + { + reader = new FileReader (file); + buffer = new char[2048]; + + while ((rc = reader.read (buffer, 0, 2048)) != -1) + what += String.valueOf (buffer, 0, 2048); + + reader.close (); + return what; + } + catch (IOException exception) + { + return ("Couldn't read emacsclient.log: " + + exception.toString ()); + } + } + + private void + displayFailureDialog (String title, String text) + { + AlertDialog.Builder builder; + AlertDialog dialog; + + builder = new AlertDialog.Builder (this); + dialog = builder.create (); + dialog.setTitle (title); + + if (text == null) + /* Read in emacsclient.log instead. */ + text = readEmacsClientLog (); + + dialog.setMessage (text); + dialog.setButton (DialogInterface.BUTTON_POSITIVE, "OK", this); + dialog.show (); + } + + /* Finish this activity in response to emacsclient having + successfully opened a file. + + In the main thread, close this window, and open a window + belonging to an Emacs frame. */ + + public void + finishSuccess () + { + runOnUiThread (new Runnable () { + @Override + public void + run () + { + Intent intent; + + intent = new Intent (EmacsOpenActivity.this, + EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity (intent); + + EmacsOpenActivity.this.finish (); + } + }); + } + + /* Finish this activity after displaying a dialog associated with + failure to open a file. + + Use TITLE as the title of the dialog. If TEXT is non-NULL, + display that text in the dialog. Otherwise, use the contents of + emacsclient.log in the cache directory instead. */ + + public void + finishFailure (final String title, final String text) + { + runOnUiThread (new Runnable () { + @Override + public void + run () + { + displayFailureDialog (title, text); + } + }); + } + + public String + getLibraryDirectory () + { + int apiLevel; + Context context; + + context = getApplicationContext (); + 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 void + startEmacsClient (String fileName) + { + String libDir; + ProcessBuilder builder; + Process process; + EmacsClientThread thread; + File file; + + file = new File (getCacheDir (), "emacsclient.log"); + + libDir = getLibraryDirectory (); + builder = new ProcessBuilder (libDir + "/libemacsclient.so", + fileName, "--reuse-frame", + "--timeout=10", "--no-wait"); + + /* Redirect standard error to a file so that errors can be + meaningfully reported. */ + + if (file.exists ()) + file.delete (); + + builder.redirectError (file); + + /* Track process output in a new thread, since this is the UI + thread and doing so here can cause deadlocks when EmacsService + decides to wait for something. */ + + thread = new EmacsClientThread (builder); + thread.start (); + } + + @Override + public void + onCreate (Bundle savedInstanceState) + { + String action, fileName; + Intent intent; + Uri uri; + ContentResolver resolver; + ParcelFileDescriptor fd; + byte[] names; + String errorBlurb; + + super.onCreate (savedInstanceState); + + /* Obtain the intent that started Emacs. */ + intent = getIntent (); + action = intent.getAction (); + + if (action == null) + { + finish (); + return; + } + + /* Now see if the action specified is supported by Emacs. */ + + if (action.equals ("android.intent.action.VIEW") + || action.equals ("android.intent.action.EDIT") + || action.equals ("android.intent.action.PICK")) + { + /* Obtain the URI of the action. */ + uri = intent.getData (); + + if (uri == null) + { + finish (); + return; + } + + /* Now, try to get the file name. */ + + if (uri.getScheme ().equals ("file")) + fileName = uri.getPath (); + else + { + fileName = null; + + if (uri.getScheme ().equals ("content")) + { + /* This is one of the annoying Android ``content'' + URIs. Most of the time, there is actually an + underlying file, but it cannot be found without + opening the file and doing readlink on its file + descriptor in /proc/self/fd. */ + resolver = getContentResolver (); + + try + { + fd = resolver.openFileDescriptor (uri, "r"); + names = EmacsNative.getProcName (fd.getFd ()); + fd.close (); + + /* What is the right encoding here? */ + + if (names != null) + fileName = new String (names, "UTF-8"); + } + catch (FileNotFoundException exception) + { + /* Do nothing. */ + } + catch (IOException exception) + { + /* Do nothing. */ + } + } + + if (fileName == null) + { + errorBlurb = ("The URI: " + uri + " could not be opened" + + ", as it does not encode file name inform" + + "ation."); + displayFailureDialog ("Error opening file", errorBlurb); + return; + } + } + + /* And start emacsclient. */ + startEmacsClient (fileName); + } + else + finish (); + } +} diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index d17f6d1286c..2ec2ddf9bda 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -152,7 +152,6 @@ public class EmacsService extends Service } } - @TargetApi (Build.VERSION_CODES.GINGERBREAD) private String getLibraryDirectory () { -- cgit v1.2.1 From 98c90135fe5f3729a3fa6e9a724d04a78b2cd514 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 5 Feb 2023 23:02:14 +0800 Subject: Update Android port * INSTALL.android: Explain how to build selinux. * configure.ac: Enable selinux on Android. * cross/ndk-build/ndk-build-shared-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix)::(NDK_CFLAGS_$(LOCAL_MODULE)): * cross/ndk-build/ndk-build-static-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix)::(NDK_CFLAGS_$(LOCAL_MODULE)): Correctly handle files with a .cc suffix, and clang-specific asflags. * cross/ndk-build/ndk-clear-vars.mk: Handle AOSP extensions LOCAL_ADDITIONAL_DEPENDENCIES, LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH) and LOCAL_IS_HOST_MODULE. * doc/emacs/android.texi (Android Startup): Explain emacsclient wrapper. * java/org/gnu/emacs/EmacsView.java (EmacsView): New flag `isCurrentlyTextEditor'. (showOnScreenKeyboard, hideOnScreenKeyboard): Set as appropriate. (onCheckIsTextEditor): Return its value. * lisp/touch-screen.el (touch-screen-handle-scroll): Don't ding at buffer limits. * m4/ndk-build.m4: Improve doc. * src/Makefile.in (LIBSELINUX_CFLAGS): New variable. (EMACS_CFLAGS): Add it. --- java/org/gnu/emacs/EmacsView.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 730ed02d4f1..873124c86d1 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -106,6 +106,10 @@ public class EmacsView extends ViewGroup /* Whether or not this view is attached to a window. */ public boolean isAttachedToWindow; + /* Whether or not this view should have the on screen keyboard + displayed whenever possible. */ + public boolean isCurrentlyTextEditor; + public EmacsView (EmacsWindow window) { @@ -597,6 +601,7 @@ public class EmacsView extends ViewGroup /* Specifying no flags at all tells the system the user asked for the input method to be displayed. */ imManager.showSoftInput (this, 0); + isCurrentlyTextEditor = true; } public void @@ -604,6 +609,7 @@ public class EmacsView extends ViewGroup { imManager.hideSoftInputFromWindow (this.getWindowToken (), 0); + isCurrentlyTextEditor = false; } public void @@ -636,4 +642,13 @@ public class EmacsView extends ViewGroup screen keyboard to work correctly. */ return null; } + + @Override + public boolean + onCheckIsTextEditor () + { + /* If value is true, then the system will display the on screen + keyboard. */ + return isCurrentlyTextEditor; + } }; -- cgit v1.2.1 From fc82efc1fe99415d60d9aa06f2ff8e7e92566870 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 6 Feb 2023 22:00:08 +0800 Subject: Update Android port * java/AndroidManifest.xml.in: Prevent the Emacs activity from being overlayed by the emacsclient wrapper. * java/org/gnu/emacs/EmacsOpenActivity.java (run): Likewise. (onCreate): Set an appropriate theme on ICS and up. * java/org/gnu/emacs/EmacsWindow.java (onTouchEvent): Handle ACTION_CANCEL correctly. --- java/AndroidManifest.xml.in | 5 ++++- java/org/gnu/emacs/EmacsOpenActivity.java | 9 ++++++++- java/org/gnu/emacs/EmacsWindow.java | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 923c5a005d5..3c9e30713b6 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -72,7 +72,7 @@ along with GNU Emacs. If not, see . --> android:extractNativeLibs="true"> @@ -84,6 +84,8 @@ along with GNU Emacs. If not, see . --> @@ -137,6 +139,7 @@ along with GNU Emacs. If not, see . --> + diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index 268a9abd7b1..e987e067a73 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -184,7 +184,9 @@ public class EmacsOpenActivity extends Activity intent = new Intent (EmacsOpenActivity.this, EmacsActivity.class); - intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + + /* This means only an existing frame will be displayed. */ + intent.addFlags (Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity (intent); EmacsOpenActivity.this.finish (); @@ -285,6 +287,11 @@ public class EmacsOpenActivity extends Activity return; } + /* Set an appropriate theme. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + setTheme (android.R.style.Theme_DeviceDefault); + /* Now see if the action specified is supported by Emacs. */ if (action.equals ("android.intent.action.VIEW") diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 5c2b77b0125..e921b972c2c 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -794,7 +794,10 @@ public class EmacsWindow extends EmacsHandleObject case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: - /* Touch up event. */ + case MotionEvent.ACTION_CANCEL: + /* Touch up event. Android documentation says ACTION_CANCEL + should be treated as more or less equivalent to ACTION_UP, + so that is what is done here. */ EmacsNative.sendTouchUp (this.handle, (int) event.getX (index), (int) event.getY (index), event.getEventTime (), pointerID); -- cgit v1.2.1 From 9b79f429edd173efd6ecbdcb2f0a5fafa8d6e523 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 6 Feb 2023 22:55:42 +0800 Subject: Port emacsclient wrapper to Android 7.1 and earlier * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Load every native library on which Emacs depends prior to loading libemacs itself. * java/org/gnu/emacs/EmacsOpenActivity.java (readEmacsClientLog) (EmacsOpenActivity, startEmacsClient): Don't use redirectError on Android 7.1 and earlier. --- java/org/gnu/emacs/EmacsNative.java | 152 ++++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsOpenActivity.java | 33 +++++-- 2 files changed, 177 insertions(+), 8 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index aba356051cd..939348ba420 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -159,6 +159,158 @@ public class EmacsNative static { + /* Older versions of Android cannot link correctly with shared + libraries that link with other shared libraries built along + Emacs unless all requisite shared libraries are explicitly + loaded from Java. + + Every time you add a new shared library dependency to Emacs, + please add it here as well. */ + + try + { + System.loadLibrary ("png_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("selinux_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("crypto_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("pcre_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("packagelistparser_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("gnutls_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("gmp_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("nettle_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("p11-kit_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("tasn1_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("hogweed_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("jansson_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("jpeg_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("tiff_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("xml2_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + + try + { + System.loadLibrary ("icuuc_emacs"); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } + System.loadLibrary ("emacs"); }; }; diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index e987e067a73..baf31039ecd 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -125,6 +125,16 @@ public class EmacsOpenActivity extends Activity int rc; String what; + /* Because the ProcessBuilder functions necessary to redirect + process output are not implemented on Android 7 and earlier, + print a generic error message. */ + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + return ("This is likely because the Emacs server" + + " is not running, or because you did" + + " not grant Emacs permission to access" + + " external storage."); + cache = getCacheDir (); file = new File (cache, "emacsclient.log"); what = ""; @@ -199,7 +209,8 @@ public class EmacsOpenActivity extends Activity Use TITLE as the title of the dialog. If TEXT is non-NULL, display that text in the dialog. Otherwise, use the contents of - emacsclient.log in the cache directory instead. */ + emacsclient.log in the cache directory instead, or describe why + that file cannot be read. */ public void finishFailure (final String title, final String text) @@ -240,20 +251,26 @@ public class EmacsOpenActivity extends Activity EmacsClientThread thread; File file; - file = new File (getCacheDir (), "emacsclient.log"); - libDir = getLibraryDirectory (); builder = new ProcessBuilder (libDir + "/libemacsclient.so", fileName, "--reuse-frame", "--timeout=10", "--no-wait"); - /* Redirect standard error to a file so that errors can be - meaningfully reported. */ + /* Redirection is unfortunately not possible in Android 7 and + earlier. */ - if (file.exists ()) - file.delete (); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + file = new File (getCacheDir (), "emacsclient.log"); - builder.redirectError (file); + /* Redirect standard error to a file so that errors can be + meaningfully reported. */ + + if (file.exists ()) + file.delete (); + + builder.redirectError (file); + } /* Track process output in a new thread, since this is the UI thread and doing so here can cause deadlocks when EmacsService -- cgit v1.2.1 From 0bd4b7fdab2fdf437c4a759d53dfdc9f667aefb1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 8 Feb 2023 22:40:10 +0800 Subject: Update Android port * doc/lispref/frames.texi (On-Screen Keyboards): Describe return value of `frame-toggle-on-screen-keyboard'. * java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged) (surfaceCreated, EmacsSurfaceView): Remove unuseful synchronization code. The framework doesn't seem to look at this at all. * java/org/gnu/emacs/EmacsView.java (EmacsView): (onLayout): Lay out the window after children. (swapBuffers): Properly implement `force'. (windowUpdated): Delete function. * lisp/frame.el (frame-toggle-on-screen-keyboard): Return whether or not the on screen keyboard might've been displayed. * lisp/minibuffer.el (minibuffer-on-screen-keyboard-timer): (minibuffer-on-screen-keyboard-displayed): (minibuffer-setup-on-screen-keyboard): (minibuffer-exit-on-screen-keyboard): Improve OSK dismissal when there are consecutive minibuffers. * lisp/touch-screen.el (touch-screen-window-selection-changed): New function. (touch-screen-handle-point-up): Register it as a window selection changed function. * src/android.c (struct android_emacs_window) (android_init_emacs_window): Remove references to `windowUpdated'. (android_window_updated): Delete function. * src/android.h (struct android_output): Remove `last_configure_serial'. * src/androidterm.c (handle_one_android_event) (android_frame_up_to_date): * src/androidterm.h (struct android_output): Remove frame synchronization, as that does not work on Android. --- java/org/gnu/emacs/EmacsSurfaceView.java | 54 +++-------------------------- java/org/gnu/emacs/EmacsView.java | 58 ++++++++++++-------------------- 2 files changed, 27 insertions(+), 85 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 2fe9e103b2b..f6cb77bb2b8 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -45,14 +45,11 @@ public class EmacsSurfaceView extends SurfaceView surfaceChanged (SurfaceHolder holder, int format, int width, int height) { - Log.d (TAG, "surfaceChanged: " + view + ", " + view.pendingConfigure); + Canvas canvas; - /* Make sure not to swap buffers if there is pending - configuration, because otherwise the redraw callback will not - run correctly. */ + Log.d (TAG, "surfaceChanged: " + view + ", "); - if (view.pendingConfigure == 0) - view.swapBuffers (); + view.swapBuffers (true); } @Override @@ -67,7 +64,7 @@ public class EmacsSurfaceView extends SurfaceView /* Drop the lock when doing this, or a deadlock can result. */ - view.swapBuffers (); + view.swapBuffers (true); } @Override @@ -82,44 +79,6 @@ public class EmacsSurfaceView extends SurfaceView } } - /* 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) { @@ -128,10 +87,7 @@ public class EmacsSurfaceView extends SurfaceView 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 ()); + getHolder ().addCallback (new Callback ()); } public boolean diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 873124c86d1..fac11870ebf 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -96,13 +96,6 @@ public class EmacsView extends ViewGroup /* 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; - /* Whether or not this view is attached to a window. */ public boolean isAttachedToWindow; @@ -110,6 +103,14 @@ public class EmacsView extends ViewGroup displayed whenever possible. */ public boolean isCurrentlyTextEditor; + /* An empty rectangle. */ + public static final Rect emptyRect; + + static + { + emptyRect = new Rect (); + }; + public EmacsView (EmacsWindow window) { @@ -286,16 +287,10 @@ public class EmacsView extends ViewGroup int count, i; View child; Rect windowRect; + int wantedWidth, wantedHeight; count = getChildCount (); - if (changed || mustReportLayout) - { - mustReportLayout = false; - pendingConfigure - = window.viewLayout (left, top, right, bottom); - } - measuredWidth = right - left; measuredHeight = bottom - top; @@ -311,8 +306,6 @@ public class EmacsView extends ViewGroup Log.d (TAG, "onLayout: " + child); if (child == surfaceView) - /* The child is the surface view, so give it the entire - view. */ child.layout (0, 0, right - left, bottom - top); else if (child.getVisibility () != GONE) { @@ -326,6 +319,14 @@ public class EmacsView extends ViewGroup windowRect.right, windowRect.bottom); } } + + /* Now report the layout change to the window. */ + + if (changed || mustReportLayout) + { + mustReportLayout = false; + window.viewLayout (left, top, right, bottom); + } } public void @@ -352,7 +353,7 @@ public class EmacsView extends ViewGroup synchronized (damageRegion) { - if (damageRegion.isEmpty ()) + if (!force && damageRegion.isEmpty ()) return; bitmap = getBitmap (); @@ -363,7 +364,10 @@ public class EmacsView extends ViewGroup synchronized (surfaceView.surfaceChangeLock) { - damageRect = damageRegion.getBounds (); + if (!force) + damageRect = damageRegion.getBounds (); + else + damageRect = emptyRect; if (!surfaceView.isCreated ()) return; @@ -612,24 +616,6 @@ public class EmacsView extends ViewGroup isCurrentlyTextEditor = false; } - 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; - } - } - @Override public InputConnection onCreateInputConnection (EditorInfo info) -- cgit v1.2.1 From 209ae003b7444d2e9b195db9475ddbdefa8f9c64 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 9 Feb 2023 22:56:41 +0800 Subject: Allow other text editors to edit files in Emacs' home directory * java/AndroidManifest.xml.in: Declare the new documents provider. * java/README: Describe the meaning of files in res/values. * java/org/gnu/emacs/EmacsDocumentsProvider.java (EmacsDocumentsProvider): New file. * java/res/values-v19/bool.xml: * java/res/values/bool.xml: New files. --- java/AndroidManifest.xml.in | 12 + java/README | 13 +- java/org/gnu/emacs/EmacsDocumentsProvider.java | 381 +++++++++++++++++++++++++ java/res/values-v19/bool.xml | 22 ++ java/res/values/bool.xml | 22 ++ 5 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 java/org/gnu/emacs/EmacsDocumentsProvider.java create mode 100644 java/res/values-v19/bool.xml create mode 100644 java/res/values/bool.xml (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 3c9e30713b6..1da3646e2f5 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -179,6 +179,18 @@ along with GNU Emacs. If not, see . --> + + + + + + . */ + +package org.gnu.emacs; + +import android.content.Context; + +import android.database.Cursor; +import android.database.MatrixCursor; + +import android.os.Build; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; + +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; +import static android.provider.DocumentsContract.buildChildDocumentsUri; +import android.provider.DocumentsProvider; + +import android.webkit.MimeTypeMap; + +import android.net.Uri; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +/* ``Documents provider''. This allows Emacs's home directory to be + modified by other programs holding permissions to manage system + storage, which is useful to (for example) correct misconfigurations + which prevent Emacs from starting up. + + This functionality is only available on Android 19 and later. */ + +public class EmacsDocumentsProvider extends DocumentsProvider +{ + /* Home directory. This is the directory whose contents are + initially returned to requesting applications. */ + private File baseDir; + + /* The default projection for requests for the root directory. */ + private static final String[] DEFAULT_ROOT_PROJECTION; + + /* The default projection for requests for a file. */ + private static final String[] DEFAULT_DOCUMENT_PROJECTION; + + static + { + DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, + Root.COLUMN_MIME_TYPES, + Root.COLUMN_FLAGS, + Root.COLUMN_TITLE, + Root.COLUMN_SUMMARY, + Root.COLUMN_DOCUMENT_ID, + Root.COLUMN_AVAILABLE_BYTES, + }; + + DEFAULT_DOCUMENT_PROJECTION = new String[] { + Document.COLUMN_DOCUMENT_ID, + Document.COLUMN_MIME_TYPE, + Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_LAST_MODIFIED, + Document.COLUMN_FLAGS, + Document.COLUMN_SIZE, + }; + } + + @Override + public boolean + onCreate () + { + /* Set the base directory to Emacs's files directory. */ + baseDir = getContext ().getFilesDir (); + return true; + } + + @Override + public Cursor + queryRoots (String[] projection) + { + MatrixCursor result; + MatrixCursor.RowBuilder row; + + /* If the requestor asked for nothing at all, then it wants some + data by default. */ + + if (projection == null) + projection = DEFAULT_ROOT_PROJECTION; + + result = new MatrixCursor (projection); + row = result.newRow (); + + /* Now create and add a row for each file in the base + directory. */ + row.add (Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath ()); + row.add (Root.COLUMN_SUMMARY, "Emacs home directory"); + + /* Add the appropriate flags. */ + + row.add (Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE); + row.add (Root.FLAG_LOCAL_ONLY); + row.add (Root.COLUMN_TITLE, "Emacs"); + row.add (Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath ()); + + return result; + } + + /* Return the MIME type of a file FILE. */ + + private String + getMimeType (File file) + { + String name, extension, mime; + int extensionSeparator; + + if (file.isDirectory ()) + return Document.MIME_TYPE_DIR; + + /* Abuse WebView stuff to get the file's MIME type. */ + name = file.getName (); + extensionSeparator = name.lastIndexOf ('.'); + + if (extensionSeparator > 0) + { + extension = name.substring (extensionSeparator + 1); + mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension (extension); + + if (mime != null) + return mime; + } + + return "application/octet-stream"; + } + + /* Append the specified FILE to the query result RESULT. + Handle both directories and ordinary files. */ + + private void + queryDocument1 (MatrixCursor result, File file) + { + MatrixCursor.RowBuilder row; + String fileName, displayName, mimeType; + int flags; + + row = result.newRow (); + flags = 0; + + /* fileName is a string that the system will ask for some time in + the future. Here, it is just the absolute name of the file. */ + fileName = file.getAbsolutePath (); + + /* If file is a directory, add the right flags for that. */ + + if (file.isDirectory ()) + { + if (file.canWrite ()) + { + flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + flags |= Document.FLAG_SUPPORTS_DELETE; + } + } + else if (file.canWrite ()) + { + /* Apply the correct flags for a writable file. */ + flags |= Document.FLAG_SUPPORTS_WRITE; + flags |= Document.FLAG_SUPPORTS_DELETE; + + /* TODO: implement these + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + flags |= Document.FLAG_SUPPORTS_RENAME; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + flags |= Document.FLAG_SUPPORTS_REMOVE; */ + } + + displayName = file.getName (); + mimeType = getMimeType (file); + + row.add (Document.COLUMN_DOCUMENT_ID, fileName); + row.add (Document.COLUMN_DISPLAY_NAME, displayName); + row.add (Document.COLUMN_SIZE, file.length ()); + row.add (Document.COLUMN_MIME_TYPE, mimeType); + row.add (Document.COLUMN_LAST_MODIFIED, file.lastModified ()); + row.add (Document.COLUMN_FLAGS, flags); + } + + @Override + public Cursor + queryDocument (String documentId, String[] projection) + throws FileNotFoundException + { + MatrixCursor result; + + if (projection == null) + projection = DEFAULT_DOCUMENT_PROJECTION; + + result = new MatrixCursor (projection); + queryDocument1 (result, new File (documentId)); + + return result; + } + + @Override + public Cursor + queryChildDocuments (String parentDocumentId, String[] projection, + String sortOrder) throws FileNotFoundException + { + MatrixCursor result; + File directory; + + if (projection == null) + projection = DEFAULT_DOCUMENT_PROJECTION; + + result = new MatrixCursor (projection); + + /* Try to open the file corresponding to the location being + requested. */ + directory = new File (parentDocumentId); + + /* Now add each child. */ + for (File child : directory.listFiles ()) + queryDocument1 (result, child); + + return result; + } + + @Override + public ParcelFileDescriptor + openDocument (String documentId, String mode, + CancellationSignal signal) throws FileNotFoundException + { + return ParcelFileDescriptor.open (new File (documentId), + ParcelFileDescriptor.parseMode (mode)); + } + + @Override + public String + createDocument (String documentId, String mimeType, + String displayName) throws FileNotFoundException + { + File file; + boolean rc; + Uri updatedUri; + Context context; + + context = getContext (); + file = new File (documentId, displayName); + + try + { + rc = false; + + if (Document.MIME_TYPE_DIR.equals (mimeType)) + { + file.mkdirs (); + + if (file.isDirectory ()) + rc = true; + } + else + { + file.createNewFile (); + + if (file.isFile () + && file.setWritable (true) + && file.setReadable (true)) + rc = true; + } + + if (!rc) + throw new FileNotFoundException ("rc != 1"); + } + catch (IOException e) + { + throw new FileNotFoundException (e.toString ()); + } + + updatedUri + = buildChildDocumentsUri ("org.gnu.emacs", documentId); + /* Tell the system about the change. */ + context.getContentResolver ().notifyChange (updatedUri, null); + + return file.getAbsolutePath (); + } + + private void + deleteDocument1 (File child) + { + File[] children; + + /* Don't delete symlinks recursively. + + Calling readlink or stat is problematic due to file name + encoding problems, so try to delete the file first, and only + try to delete files recursively afterword. */ + + if (child.delete ()) + return; + + children = child.listFiles (); + + if (children != null) + { + for (File file : children) + deleteDocument1 (file); + } + + child.delete (); + } + + @Override + public void + deleteDocument (String documentId) + throws FileNotFoundException + { + File file, parent; + File[] children; + Uri updatedUri; + Context context; + + /* Java makes recursively deleting a file hard. File name + encoding issues also prevent easily calling into C... */ + + context = getContext (); + file = new File (documentId); + parent = file.getParentFile (); + + if (parent == null) + throw new RuntimeException ("trying to delete file without" + + " parent!"); + + updatedUri + = buildChildDocumentsUri ("org.gnu.emacs", + parent.getAbsolutePath ()); + + if (file.delete ()) + { + /* Tell the system about the change. */ + context.getContentResolver ().notifyChange (updatedUri, null); + return; + } + + children = file.listFiles (); + + if (children != null) + { + for (File child : children) + deleteDocument1 (child); + } + + if (file.delete ()) + /* Tell the system about the change. */ + context.getContentResolver ().notifyChange (updatedUri, null); + } + + @Override + public void + removeDocument (String documentId, String parentDocumentId) + throws FileNotFoundException + { + deleteDocument (documentId); + } +} diff --git a/java/res/values-v19/bool.xml b/java/res/values-v19/bool.xml new file mode 100644 index 00000000000..a4e3a87ae71 --- /dev/null +++ b/java/res/values-v19/bool.xml @@ -0,0 +1,22 @@ + + + + true + diff --git a/java/res/values/bool.xml b/java/res/values/bool.xml new file mode 100644 index 00000000000..d37eab745c0 --- /dev/null +++ b/java/res/values/bool.xml @@ -0,0 +1,22 @@ + + + + false + -- cgit v1.2.1 From a1941cd7a7dc9a6f6b7239ec7d4bd3bdf5d55fc9 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 18:57:51 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Windowing): Remove yet another limitation. * java/debug.sh: Make this work on systems which prohibit attaching to app processes from adbd. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Avoid creating copies whenever possible. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): Remove SurfaceView based implementation and use manual double buffering with invalidate instead. * java/org/gnu/emacs/EmacsView.java (EmacsView, handleDirtyBitmap) (raise, lower, onDetachedFromWindow): Adjust accordingly. * java/org/gnu/emacs/EmacsWindow.java (windowUpdated): Remove function. * src/sfntfont.c (sfntfont_open): Set font->max_width correctly. --- java/debug.sh | 13 ++- java/org/gnu/emacs/EmacsCopyArea.java | 24 ++++- java/org/gnu/emacs/EmacsSurfaceView.java | 159 ++++++++++++++----------------- java/org/gnu/emacs/EmacsView.java | 93 +++--------------- java/org/gnu/emacs/EmacsWindow.java | 23 ----- 5 files changed, 112 insertions(+), 200 deletions(-) (limited to 'java') diff --git a/java/debug.sh b/java/debug.sh index 7008664c049..2e95f9738c7 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -267,10 +267,14 @@ if [ -z "$gdbserver" ]; then gdbserver_bin=/system/bin/gdbserver else gdbserver_bin=/data/local/tmp/gdbserver + gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \ + \"tee gdbserver > /dev/null\"" # Upload the specified gdbserver binary to the device. adb -s $device push "$gdbserver" "$gdbserver_bin" - adb -s $device shell chmod +x "$gdbserver_bin" + # Copy it to the user directory. + adb -s $device shell "$gdbserver_cat" + adb -s $device shell "run-as $package chmod +x gdbserver" fi # Now start gdbserver on the device asynchronously. @@ -286,10 +290,9 @@ if [ -z "$gdbserver" ]; then else # Normally the program cannot access $gdbserver_bin when it is # placed in /data/local/tmp. - adb -s $device shell $gdbserver_bin --once \ - "+/data/local/tmp/debug.$package.socket" \ - --attach $pid >&5 & - gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" + adb -s $device shell run-as $package "./gdbserver" --once \ + "0.0.0.0:7654" --attach $pid >&5 & + gdb_socket="tcp:7654" fi # Wait until gdbserver successfully runs. diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index f8974e17c2e..11dc22e0456 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -110,11 +110,25 @@ public class EmacsCopyArea if (gc.clip_mask == null) { - bitmap = Bitmap.createBitmap (srcBitmap, - src_x, src_y, width, - height); - canvas.drawBitmap (bitmap, null, rect, paint); - bitmap.recycle (); + if (source == destination) + { + /* Create a copy of the bitmap, since Android can't handle + overlapping copies. */ + bitmap = Bitmap.createBitmap (srcBitmap, + src_x, src_y, width, + height); + canvas.drawBitmap (bitmap, null, rect, paint); + bitmap.recycle (); + } + else + { + /* But here the bitmaps are known to not overlap, so avoid + that extra consing overhead. */ + + srcRect = new Rect (src_x, src_y, src_x + width, + src_y + height); + canvas.drawBitmap (srcBitmap, null, rect, paint); + } } else { diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index f6cb77bb2b8..e9bae623930 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -19,127 +19,114 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import android.view.SurfaceView; -import android.view.SurfaceHolder; +import android.view.View; import android.os.Build; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.Paint; -import android.util.Log; +/* This originally extended SurfaceView. However, doing so proved to + be too slow, and Android's surface view keeps up to three of its + own back buffers, which use too much memory (up to 96 MB for a + single frame.) */ -public class EmacsSurfaceView extends SurfaceView +public class EmacsSurfaceView extends View { private static final String TAG = "EmacsSurfaceView"; - public Object surfaceChangeLock; - private boolean created; private EmacsView view; - - /* This is the callback used on Android 8 to 25. */ - - private class Callback implements SurfaceHolder.Callback - { - @Override - public void - surfaceChanged (SurfaceHolder holder, int format, - int width, int height) - { - Canvas canvas; - - Log.d (TAG, "surfaceChanged: " + view + ", "); - - view.swapBuffers (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 (true); - } - - @Override - public void - surfaceDestroyed (SurfaceHolder holder) - { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceDestroyed: " + view); - created = false; - } - } - } + private Bitmap frontBuffer; + private Canvas bitmapCanvas; + private Bitmap bitmap; + private Paint bitmapPaint; public EmacsSurfaceView (final EmacsView view) { super (view.getContext ()); - this.surfaceChangeLock = new Object (); this.view = view; - - getHolder ().addCallback (new Callback ()); + this.bitmapPaint = new Paint (); } - public boolean - isCreated () + private void + copyToFrontBuffer (Rect damageRect) { - return created; + if (damageRect != null) + bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect, + bitmapPaint); + else + bitmapCanvas.drawBitmap (bitmap, 0f, 0f, bitmapPaint); } - public Canvas - lockCanvas (Rect damage) + private void + reconfigureFrontBuffer (Bitmap bitmap) { - SurfaceHolder holder; - - holder = getHolder (); + /* First, remove the old front buffer. */ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + if (frontBuffer != null) { - damage.setEmpty (); - return holder.lockHardwareCanvas (); + frontBuffer.recycle (); + frontBuffer = null; + bitmapCanvas = null; } - return holder.lockCanvas (damage); - } + this.bitmap = bitmap; - @Override - protected void - onLayout (boolean changed, int left, int top, int right, - int bottom) - { - Log.d (TAG, ("onLayout: " + left + " " + top + " " + right - + " " + bottom + " -- " + changed + " visibility " - + getVisibility ())); - } + /* Next, create the new front buffer if necessary. */ - /* This method is only used during debugging when it seems damage - isn't working correctly. */ + if (bitmap != null && frontBuffer == null) + { + frontBuffer = Bitmap.createBitmap (bitmap.getWidth (), + bitmap.getHeight (), + Bitmap.Config.ARGB_8888, + false); + bitmapCanvas = new Canvas (frontBuffer); + + /* And copy over the bitmap contents. */ + copyToFrontBuffer (null); + } + else if (bitmap != null) + /* Just copy over the bitmap contents. */ + copyToFrontBuffer (null); + } - public Canvas - lockCanvas () + public synchronized void + setBitmap (Bitmap bitmap, Rect damageRect) { - SurfaceHolder holder; + if (bitmap != this.bitmap) + reconfigureFrontBuffer (bitmap); + else if (bitmap != null) + copyToFrontBuffer (damageRect); - holder = getHolder (); - return holder.lockCanvas (); + if (bitmap != null) + { + /* In newer versions of Android, the invalid rectangle is + supposedly internally calculated by the system. How that + is done is unknown, but calling `invalidateRect' is now + deprecated. + + Fortunately, nobody has deprecated the version of + `postInvalidate' that accepts a dirty rectangle. */ + + if (damageRect != null) + postInvalidate (damageRect.left, damageRect.top, + damageRect.right, damageRect.bottom); + else + postInvalidate (); + } } - public void - unlockCanvasAndPost (Canvas canvas) + @Override + public synchronized void + onDraw (Canvas canvas) { - SurfaceHolder holder; + /* Paint the view's bitmap; the bitmap might be recycled right + now. */ - holder = getHolder (); - holder.unlockCanvasAndPost (canvas); + if (frontBuffer != null) + canvas.drawBitmap (frontBuffer, 0f, 0f, bitmapPaint); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index fac11870ebf..0416301101c 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -103,14 +103,6 @@ public class EmacsView extends ViewGroup displayed whenever possible. */ public boolean isCurrentlyTextEditor; - /* An empty rectangle. */ - public static final Rect emptyRect; - - static - { - emptyRect = new Rect (); - }; - public EmacsView (EmacsWindow window) { @@ -127,14 +119,8 @@ public class EmacsView extends ViewGroup /* Create the surface view. */ this.surfaceView = new EmacsSurfaceView (this); - this.surfaceView.setZOrderMediaOverlay (true); addView (this.surfaceView); - /* Not sure exactly what this does but it makes things magically - work. Why is something as simple as XRaiseWindow so involved - on Android? */ - setChildrenDrawingOrderEnabled (true); - /* Get rid of the default focus highlight. */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) setDefaultFocusHighlightEnabled (false); @@ -191,8 +177,12 @@ public class EmacsView extends ViewGroup bitmapDirty = false; /* Explicitly free the old bitmap's memory. */ + if (oldBitmap != null) - oldBitmap.recycle (); + { + oldBitmap.recycle (); + surfaceView.setBitmap (null, null); + } /* Some Android versions still don't free the bitmap until the next GC. */ @@ -342,67 +332,27 @@ public class EmacsView extends ViewGroup thread. */ public void - swapBuffers (boolean force) + swapBuffers () { Canvas canvas; Rect damageRect; Bitmap bitmap; - /* Code must always take damageRegion, and then surfaceChangeLock, - never the other way around! */ + damageRect = null; synchronized (damageRegion) { - if (!force && damageRegion.isEmpty ()) + if (damageRegion.isEmpty ()) return; bitmap = getBitmap (); - /* Emacs must take the following lock to ensure the access to the - canvas occurs with the surface created. Otherwise, Android - will throttle calls to lockCanvas. */ - - synchronized (surfaceView.surfaceChangeLock) - { - if (!force) - damageRect = damageRegion.getBounds (); - else - damageRect = emptyRect; - - if (!surfaceView.isCreated ()) - return; - - if (bitmap == null) - return; - - /* Lock the canvas with the specified damage. */ - canvas = surfaceView.lockCanvas (damageRect); - - /* Return if locking the canvas failed. */ - if (canvas == null) - return; - - /* Copy from the back buffer to the canvas. If damageRect was - made empty, then draw the entire back buffer. */ - - if (damageRect.isEmpty ()) - canvas.drawBitmap (bitmap, 0f, 0f, paint); - else - canvas.drawBitmap (bitmap, damageRect, damageRect, paint); - - /* Unlock the canvas and clear the damage. */ - surfaceView.unlockCanvasAndPost (canvas); - damageRegion.setEmpty (); - } + /* Transfer the bitmap to the surface view, then invalidate + it. */ + surfaceView.setBitmap (bitmap, damageRect); } } - public void - swapBuffers () - { - swapBuffers (false); - } - @Override public boolean onKeyDown (int keyCode, KeyEvent event) @@ -486,16 +436,6 @@ public class EmacsView extends ViewGroup return; parent.bringChildToFront (this); - - /* Yes, all of this is really necessary! */ - parent.requestLayout (); - parent.invalidate (); - requestLayout (); - invalidate (); - - /* The surface view must be destroyed and recreated. */ - removeView (surfaceView); - addView (surfaceView, 0); } public void @@ -511,16 +451,6 @@ public class EmacsView extends ViewGroup return; parent.moveChildToBack (this); - - /* Yes, all of this is really necessary! */ - parent.requestLayout (); - parent.invalidate (); - requestLayout (); - invalidate (); - - /* The surface view must be removed and attached again. */ - removeView (surfaceView); - addView (surfaceView, 0); } @Override @@ -574,6 +504,7 @@ public class EmacsView extends ViewGroup bitmap.recycle (); bitmap = null; canvas = null; + surfaceView.setBitmap (null, null); /* Collect the bitmap storage; it could be large. */ Runtime.getRuntime ().gc (); diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index e921b972c2c..9e2f2f53270 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -57,12 +57,6 @@ import android.os.Build; Views are also drawables, meaning they can accept drawing requests. */ -/* Help wanted. What does not work includes `EmacsView.raise', - `EmacsView.lower', reparenting a window onto another window. - - All three are likely undocumented restrictions within - EmacsSurface. */ - public class EmacsWindow extends EmacsHandleObject implements EmacsDrawable { @@ -1111,21 +1105,4 @@ public class EmacsWindow extends EmacsHandleObject } }); } - - /* 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); - } - }); - } }; -- cgit v1.2.1 From 664140fc26240726f6ec9babe7deb633dd8c0bd2 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 19:06:38 +0800 Subject: Fix buffer swapping on Android 7.1 and earlier * java/org/gnu/emacs/EmacsSurfaceView.java (reconfigureFrontBuffer): Don't use function only present on Android 8.0 and later. --- java/org/gnu/emacs/EmacsSurfaceView.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index e9bae623930..2d80be0881a 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -79,10 +79,16 @@ public class EmacsSurfaceView extends View if (bitmap != null && frontBuffer == null) { - frontBuffer = Bitmap.createBitmap (bitmap.getWidth (), - bitmap.getHeight (), - Bitmap.Config.ARGB_8888, - false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + frontBuffer = Bitmap.createBitmap (bitmap.getWidth (), + bitmap.getHeight (), + Bitmap.Config.ARGB_8888, + false); + else + frontBuffer = Bitmap.createBitmap (bitmap.getWidth (), + bitmap.getHeight (), + Bitmap.Config.ARGB_8888); + bitmapCanvas = new Canvas (frontBuffer); /* And copy over the bitmap contents. */ -- cgit v1.2.1 From 9b50500b22ff1f71e80bf6aee98c44493c5ebcce Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 19:25:10 +0800 Subject: ; * java/org/gnu/emacs/EmacsCopyArea.java (perform): Fix typo. --- java/org/gnu/emacs/EmacsCopyArea.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 11dc22e0456..1daa2190542 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -127,7 +127,7 @@ public class EmacsCopyArea srcRect = new Rect (src_x, src_y, src_x + width, src_y + height); - canvas.drawBitmap (srcBitmap, null, rect, paint); + canvas.drawBitmap (srcBitmap, srcRect, rect, paint); } } else -- cgit v1.2.1 From 2489126e6856bf1b06a26127b73e4bfff857f68f Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 21:07:14 +0800 Subject: Implement more features for the Emacs ``documents provider'' * java/org/gnu/emacs/EmacsDocumentsProvider.java (queryRoots): Implement isChild. (getNotificationUri, notifyChange): New functions. (queryDocument1): Set rename and remove flags. (queryDocument, queryChildDocuments): Allow the requester to detect changes in the directory hierarchy. (createDocument, deleteDocument, removeDocument): Signal changes to the directory hierarchy. --- java/org/gnu/emacs/EmacsDocumentsProvider.java | 124 ++++++++++++++++++++----- 1 file changed, 101 insertions(+), 23 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsDocumentsProvider.java b/java/org/gnu/emacs/EmacsDocumentsProvider.java index f12b302ff84..3c3c7ead3c5 100644 --- a/java/org/gnu/emacs/EmacsDocumentsProvider.java +++ b/java/org/gnu/emacs/EmacsDocumentsProvider.java @@ -114,7 +114,8 @@ public class EmacsDocumentsProvider extends DocumentsProvider /* Add the appropriate flags. */ - row.add (Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE); + row.add (Root.COLUMN_FLAGS, (Root.FLAG_SUPPORTS_CREATE + | Root.FLAG_SUPPORTS_IS_CHILD)); row.add (Root.FLAG_LOCAL_ONLY); row.add (Root.COLUMN_TITLE, "Emacs"); row.add (Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath ()); @@ -122,6 +123,36 @@ public class EmacsDocumentsProvider extends DocumentsProvider return result; } + private Uri + getNotificationUri (File file) + { + Uri updatedUri; + Context context; + + context = getContext (); + updatedUri + = buildChildDocumentsUri ("org.gnu.emacs", + file.getAbsolutePath ()); + + return updatedUri; + } + + /* Inform the system that FILE's contents (or FILE itself) has + changed. */ + + private void + notifyChange (File file) + { + Uri updatedUri; + Context context; + + context = getContext (); + updatedUri + = buildChildDocumentsUri ("org.gnu.emacs", + file.getAbsolutePath ()); + context.getContentResolver ().notifyChange (updatedUri, null); + } + /* Return the MIME type of a file FILE. */ private String @@ -174,6 +205,9 @@ public class EmacsDocumentsProvider extends DocumentsProvider { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_SUPPORTS_DELETE; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + flags |= Document.FLAG_SUPPORTS_RENAME; } } else if (file.canWrite ()) @@ -182,13 +216,11 @@ public class EmacsDocumentsProvider extends DocumentsProvider flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_DELETE; - /* TODO: implement these + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + flags |= Document.FLAG_SUPPORTS_RENAME; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - flags |= Document.FLAG_SUPPORTS_RENAME; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - flags |= Document.FLAG_SUPPORTS_REMOVE; */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + flags |= Document.FLAG_SUPPORTS_REMOVE; } displayName = file.getName (); @@ -208,12 +240,21 @@ public class EmacsDocumentsProvider extends DocumentsProvider throws FileNotFoundException { MatrixCursor result; + File file; + Context context; + + file = new File (documentId); + context = getContext (); if (projection == null) projection = DEFAULT_DOCUMENT_PROJECTION; result = new MatrixCursor (projection); - queryDocument1 (result, new File (documentId)); + queryDocument1 (result, file); + + /* Now allow interested applications to detect changes. */ + result.setNotificationUri (context.getContentResolver (), + getNotificationUri (file)); return result; } @@ -225,6 +266,7 @@ public class EmacsDocumentsProvider extends DocumentsProvider { MatrixCursor result; File directory; + Context context; if (projection == null) projection = DEFAULT_DOCUMENT_PROJECTION; @@ -239,6 +281,12 @@ public class EmacsDocumentsProvider extends DocumentsProvider for (File child : directory.listFiles ()) queryDocument1 (result, child); + context = getContext (); + + /* Now allow interested applications to detect changes. */ + result.setNotificationUri (context.getContentResolver (), + getNotificationUri (directory)); + return result; } @@ -256,12 +304,9 @@ public class EmacsDocumentsProvider extends DocumentsProvider createDocument (String documentId, String mimeType, String displayName) throws FileNotFoundException { - File file; + File file, parentFile; boolean rc; - Uri updatedUri; - Context context; - context = getContext (); file = new File (documentId, displayName); try @@ -293,10 +338,10 @@ public class EmacsDocumentsProvider extends DocumentsProvider throw new FileNotFoundException (e.toString ()); } - updatedUri - = buildChildDocumentsUri ("org.gnu.emacs", documentId); - /* Tell the system about the change. */ - context.getContentResolver ().notifyChange (updatedUri, null); + parentFile = file.getParentFile (); + + if (parentFile != null) + notifyChange (parentFile); return file.getAbsolutePath (); } @@ -333,7 +378,6 @@ public class EmacsDocumentsProvider extends DocumentsProvider { File file, parent; File[] children; - Uri updatedUri; Context context; /* Java makes recursively deleting a file hard. File name @@ -347,14 +391,10 @@ public class EmacsDocumentsProvider extends DocumentsProvider throw new RuntimeException ("trying to delete file without" + " parent!"); - updatedUri - = buildChildDocumentsUri ("org.gnu.emacs", - parent.getAbsolutePath ()); - if (file.delete ()) { /* Tell the system about the change. */ - context.getContentResolver ().notifyChange (updatedUri, null); + notifyChange (parent); return; } @@ -368,7 +408,7 @@ public class EmacsDocumentsProvider extends DocumentsProvider if (file.delete ()) /* Tell the system about the change. */ - context.getContentResolver ().notifyChange (updatedUri, null); + notifyChange (parent); } @Override @@ -378,4 +418,42 @@ public class EmacsDocumentsProvider extends DocumentsProvider { deleteDocument (documentId); } + + @Override + public String + getDocumentType (String documentId) + { + return getMimeType (new File (documentId)); + } + + @Override + public String + renameDocument (String documentId, String displayName) + throws FileNotFoundException + { + File file, newName; + File parent; + + file = new File (documentId); + parent = file.getParentFile (); + newName = new File (parent, displayName); + + if (parent == null) + throw new FileNotFoundException ("parent is null"); + + file = new File (documentId); + + if (!file.renameTo (newName)) + return null; + + notifyChange (parent); + return newName.getAbsolutePath (); + } + + @Override + public boolean + isChildDocument (String parentDocumentId, String documentId) + { + return documentId.startsWith (parentDocumentId); + } } -- cgit v1.2.1 From dc120c7ad62d5f79fe50f72431d3b9bb2d7f1558 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 23:03:43 +0800 Subject: Improve appearance of the Android preferences screen * .gitignore: Add org/gnu/emacs/R.java. * cross/Makefile.in (top_builddir): Include verbose.mk. Rewrite rules to print nice looking statements. * doc/emacs/android.texi (Android, Android Startup) (Android Environment, Android Windowing, Android Fonts): * doc/emacs/emacs.texi (Top): Add an extra ``Android Troubleshooting'' node and move troubleshooting details there. * java/Makefile.in: Generate R.java; improve appearance by using verbose.mk. * java/org/gnu/emacs/EmacsPreferencesActivity.java: Reimplement in terms of PreferencesActivity. * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Avoid flicker. * java/res/xml/preferences.xml: New file. * src/verbose.mk.in (AM_V_AAPT, AM_V_SILENT): New variables. --- java/Makefile.in | 104 +++++++++++++------ java/org/gnu/emacs/EmacsPreferencesActivity.java | 123 ++++++++++++----------- java/org/gnu/emacs/EmacsView.java | 5 +- java/res/xml/preferences.xml | 28 ++++++ 4 files changed, 171 insertions(+), 89 deletions(-) create mode 100644 java/res/xml/preferences.xml (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 155754b59d2..fc0d23980e4 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -54,18 +54,33 @@ JARSIGNER_FLAGS = endif SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 $(JARSIGNER_FLAGS) -SIGN_EMACS_V2 = sign --v2-signing-enabled --ks emacs.keystore \ +SIGN_EMACS_V2 = sign --v2-signing-enabled --ks emacs.keystore \ --debuggable-apk-permitted --ks-pass pass:emacs1 -JAVA_FILES = $(wildcard $(srcdir)/org/gnu/emacs/*.java) -RESOURCE_FILES = $(wildcard $(srcdir)/res/drawable/*) -CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) +JAVA_FILES := $(wildcard $(srcdir)/org/gnu/emacs/*.java) +RESOURCE_FILES := $(foreach file,$(wildcard $(srcdir)/res/*), \ + $(wildcard $(file)/*)) + +# R.java is a file generated by the `aapt' utility containing +# constants that can then be used to locate ``resource identifiers''. +# It is not a regular file and should not be compiled as Java source +# code. Instead, it is automatically included by the Java compiler. +RESOURCE_FILE := $(srcdir)/org/gnu/emacs/R.java + +# CLASS_FILES is what should actually be built and included in the +# resulting Emacs executable. The Java compiler might generate more +# than one class file for each source file, so this only serves as a +# list of dependencies for Make. +CLASS_FILES := $(foreach file,$(JAVA_FILES),$(basename $(file)).class) + +# Remove RESOURCE_FILE from JAVA_FILES, if it is already present. +JAVA_FILES := $(filter-out $(RESOURCE_FILE),$(JAVA_FILES)) # Compute the name for the Emacs application package. This should be: # emacs---.apk -ANDROID_MIN_SDK = @ANDROID_MIN_SDK@ -APK_NAME = emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk +ANDROID_MIN_SDK := @ANDROID_MIN_SDK@ +APK_NAME := emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk # How this stuff works. @@ -113,19 +128,25 @@ include $(top_builddir)/cross/ndk-build/ndk-build.mk $(libsrc)/asset-directory-tool: $(MAKE) -C $(libsrc) $(notdir $@) -emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ - AndroidManifest.xml $(NDK_BUILD_SHARED) $(RESOURCE_FILES) +# install_tmp is a directory used to generate emacs.apk-in. +# That is then packaged into $(APK_NAME). + +.PHONY: install_temp install_temp/assets/directory-tree +install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(NDK_BUILD_SHARED) \ + $(RESOURCE_FILES) + $(AM_V_GEN) # Make the working directory for this stuff - rm -rf install_temp - mkdir -p install_temp/lib/$(ANDROID_ABI) - mkdir -p install_temp/assets/etc - mkdir -p install_temp/assets/lisp - mkdir -p install_temp/assets/info + $(AM_V_SILENT) rm -rf install_temp + $(AM_V_SILENT) mkdir -p install_temp/lib/$(ANDROID_ABI) + $(AM_V_SILENT) mkdir -p install_temp/assets/etc + $(AM_V_SILENT) mkdir -p install_temp/assets/lisp + $(AM_V_SILENT) mkdir -p install_temp/assets/info # Install architecture independents to assets/etc and assets/lisp - cp -r $(top_builddir)/lisp install_temp/assets - cp -r $(top_builddir)/etc install_temp/assets - cp -r $(top_builddir)/info install_temp/assets + $(AM_V_SILENT) cp -r $(top_builddir)/lisp install_temp/assets + $(AM_V_SILENT) cp -r $(top_builddir)/etc install_temp/assets + $(AM_V_SILENT) cp -r $(top_builddir)/info install_temp/assets # Remove undesirable files from those directories. + $(AM_V_SILENT) \ for subdir in `find install_temp -type d -print`; do \ chmod a+rx $${subdir} ; \ rm -rf $${subdir}/.gitignore ; \ @@ -139,34 +160,44 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ rm -rf $${subdir}/Makefile; \ done # Generate the directory tree for those directories. - $(libsrc)/asset-directory-tool install_temp/assets \ - install_temp/assets/directory-tree # Install architecture dependents to lib/$(ANDROID_ABI). This # perculiar naming scheme is required to make Android preserve these # binaries upon installation. + $(AM_V_SILENT) \ for file in $(CROSS_BINS); do \ if [ -x $$file ]; then \ filename=`basename $$file`; \ cp -f $$file install_temp/lib/$(ANDROID_ABI)/lib$${filename}.so; \ fi \ done + $(AM_V_SILENT) \ for file in $(CROSS_LIBS); do \ if [ -x $$file ]; then \ cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done ifneq ($(NDK_BUILD_SHARED),) - cp -f $(NDK_BUILD_SHARED) install_temp/lib/$(ANDROID_ABI) + $(AM_V_SILENT) cp -f $(NDK_BUILD_SHARED) \ + install_temp/lib/$(ANDROID_ABI) endif + +install_temp/assets/directory-tree: $(libsrc)/asset-directory-tool install_temp + $(AM_V_GEN) $(libsrc)/asset-directory-tool install_temp/assets \ + install_temp/assets/directory-tree + +emacs.apk-in: install_temp install_temp/assets/directory-tree \ + AndroidManifest.xml # Package everything. Specifying the assets on this command line is # necessary for AAssetManager_getNextFileName to work on old versions -# of Android. Make sure not to generate R.java, as it's not required -# by Emacs. - $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f \ - -M AndroidManifest.xml -A install_temp/assets \ +# 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 \ -S res -J install_temp - pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd - rm -rf install_temp + $(AM_V_SILENT) pushd install_temp &> /dev/null; \ + $(AAPT) add ../$@ `find lib -type f`; \ + popd &> /dev/null + $(AM_V_SILENT) rm -rf install_temp # Makefile itself. .PRECIOUS: ../config.status Makefile @@ -180,6 +211,14 @@ AndroidManifest.xml: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 \ AndroidManifest.xml.in pushd ..; ./config.status java/AndroidManifest.xml; popd +# R.java: +$(RESOURCE_FILE): $(RESOURCE_FILES) + $(AM_V_GEN) $(AAPT) p -I "$(ANDROID_JAR)" -f \ + -J $(dir $@) -M AndroidManifest.xml -S res + +# Make all class files depend on R.java being built. +$(CLASS_FILES): $(RESOURCE_FILE) + .SUFFIXES: .java .class .java.class &: $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< @@ -199,17 +238,18 @@ classes.dex: $(CLASS_FILES) .PHONY: clean maintainer-clean $(APK_NAME): classes.dex emacs.apk-in emacs.keystore - cp -f emacs.apk-in $@.unaligned - $(AAPT) add $@.unaligned classes.dex - $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore" - $(ZIPALIGN) -f 4 $@.unaligned $@ + $(AM_V_GEN) + $(AM_V_SILENT) cp -f emacs.apk-in $@.unaligned + $(AM_V_SILENT) $(AAPT) add $@.unaligned classes.dex + $(AM_V_SILENT) $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore" + $(AM_V_SILENT) $(ZIPALIGN) -f 4 $@.unaligned $@ # Signing must happen after alignment! - $(APKSIGNER) $(SIGN_EMACS_V2) $@ - rm -f $@.unaligned *.idsig + $(AM_V_SILENT) $(APKSIGNER) $(SIGN_EMACS_V2) $@ + $(AM_V_SILENT) rm -f $@.unaligned *.idsig clean: rm -f *.apk emacs.apk-in *.dex *.unaligned *.class *.idsig - rm -rf install-temp + rm -rf install-temp $(RESOURCE_FILE) find . -name '*.class' -delete maintainer-clean distclean bootstrap-clean: clean diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index 6cef7c37516..85639fe9236 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -22,27 +22,28 @@ package org.gnu.emacs; import java.io.File; 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; +import android.widget.Toast; + +import android.preference.*; /* 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. */ + command line on Android. -public class EmacsPreferencesActivity extends Activity -{ - /* The linear layout associated with the activity. */ - private LinearLayout layout; + Android provides a preferences activity, but it is deprecated. + Unfortunately, there is no alternative that looks the same way. */ +@SuppressWarnings ("deprecation") +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. */ @@ -59,72 +60,82 @@ public class EmacsPreferencesActivity extends Activity System.exit (0); } + /* Erase Emacs's dump file. */ + + private void + eraseDumpFile () + { + String wantedDumpFile; + File file; + Toast toast; + + wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () + + ".pdmp"); + file = new File (getFilesDir (), wantedDumpFile); + + if (file.exists ()) + file.delete (); + + /* Make sure to clear EmacsApplication.dumpFileName, or + starting Emacs without restarting this program will + make Emacs try to load a nonexistent dump file. */ + EmacsApplication.dumpFileName = null; + + /* Display a message stating that the dump file has been + erased. */ + toast = Toast.makeText (this, "Dump file removed", + Toast.LENGTH_SHORT); + toast.show (); + } + @Override public void onCreate (Bundle savedInstanceState) { - LinearLayout layout; - TextView textView; - LinearLayout.LayoutParams params; - int resid; + Preference tem; + Preference.OnPreferenceClickListener listener; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - setTheme (R.style.Theme_DeviceDefault_Settings); + setTheme (android.R.style.Theme_DeviceDefault_Settings); else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - setTheme (R.style.Theme_DeviceDefault); + setTheme (android.R.style.Theme_DeviceDefault); + + /* This must come before using any preference APIs. */ + super.onCreate (savedInstanceState); - layout = new LinearLayout (this); - layout.setOrientation (LinearLayout.VERTICAL); - setContentView (layout); + /* Add preferences from the XML file where they are defined. */ + addPreferencesFromResource (R.xml.preferences); - textView = new TextView (this); - textView.setPadding (8, 20, 20, 8); + /* Now, set up on click handlers for each of the preferences + items. */ - 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 () { + tem = findPreference ("start_quick"); + + listener = new Preference.OnPreferenceClickListener () { @Override - public void - onClick (View view) + public boolean + onPreferenceClick (Preference preference) { startEmacsQ (); + return true; } - }); - layout.addView (textView); + }; + + tem.setOnPreferenceClickListener (listener); - textView = new TextView (this); - textView.setPadding (8, 20, 20, 8); + tem = findPreference ("erase_dump"); - params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - textView.setLayoutParams (params); - textView.setText ("Erase dump file"); - textView.setOnClickListener (new View.OnClickListener () { + listener = new Preference.OnPreferenceClickListener () { @Override - public void - onClick (View view) + public boolean + onPreferenceClick (Preference preference) { - String wantedDumpFile; - File file; - - wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () - + ".pdmp"); - file = new File (getFilesDir (), wantedDumpFile); - - if (file.exists ()) - file.delete (); - - /* Make sure to clear EmacsApplication.dumpFileName, or - starting Emacs without restarting this program will - make Emacs try to load a nonexistent dump file. */ - EmacsApplication.dumpFileName = null; + eraseDumpFile (); + return true; } - }); - layout.addView (textView); + }; - super.onCreate (savedInstanceState); + tem.setOnPreferenceClickListener (listener); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 0416301101c..4fc8104e31f 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -181,7 +181,10 @@ public class EmacsView extends ViewGroup if (oldBitmap != null) { oldBitmap.recycle (); - surfaceView.setBitmap (null, null); + + /* Make sure to set the view's bitmap to the new bitmap, or + ugly flicker can result. */ + surfaceView.setBitmap (bitmap, null); } /* Some Android versions still don't free the bitmap until the diff --git a/java/res/xml/preferences.xml b/java/res/xml/preferences.xml new file mode 100644 index 00000000000..f0c3abb52e7 --- /dev/null +++ b/java/res/xml/preferences.xml @@ -0,0 +1,28 @@ + + + + + + + -- cgit v1.2.1 From ab48881a2fb72df016f2a00bc107e5a35a411a9d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 11 Feb 2023 10:26:34 +0800 Subject: Fix displaying popup menus from a menu entry on Android * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity, onDestroy) (onWindowFocusChanged): Keep track of the last focused activity. * java/org/gnu/emacs/EmacsDialog.java (display1): Use it if there is no current focus. --- java/org/gnu/emacs/EmacsActivity.java | 13 ++++++++++++- java/org/gnu/emacs/EmacsDialog.java | 26 +++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 30eaf85805a..3156a144a0f 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -47,6 +47,9 @@ public class EmacsActivity extends Activity /* List of activities with focus. */ public static List focusedActivities; + /* The last activity to have been focused. */ + public static EmacsActivity lastFocusedActivity; + /* The currently focused window. */ public static EmacsWindow focusedWindow; @@ -215,6 +218,11 @@ public class EmacsActivity extends Activity manager.removeWindowConsumer (this, isMultitask || isFinishing ()); focusedActivities.remove (this); invalidateFocus (); + + /* Remove this activity from the static field, lest it leak. */ + if (lastFocusedActivity == this) + lastFocusedActivity = null; + super.onDestroy (); } @@ -223,7 +231,10 @@ public class EmacsActivity extends Activity onWindowFocusChanged (boolean isFocused) { if (isFocused && !focusedActivities.contains (this)) - focusedActivities.add (this); + { + focusedActivities.add (this); + lastFocusedActivity = this; + } else focusedActivities.remove (this); diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index 7d88a23c58f..bd5e9ba8ee7 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -230,11 +230,31 @@ public class EmacsDialog implements DialogInterface.OnDismissListener AlertDialog dialog; if (EmacsActivity.focusedActivities.isEmpty ()) - return false; + { + /* If focusedActivities is empty then this dialog may have + been displayed immediately after a popup dialog is + dismissed. */ + + activity = EmacsActivity.lastFocusedActivity; + + if (activity == null) + return false; + } + else + activity = EmacsActivity.focusedActivities.get (0); - activity = EmacsActivity.focusedActivities.get (0); dialog = dismissDialog = toAlertDialog (activity); - dismissDialog.show (); + + try + { + dismissDialog.show (); + } + catch (Exception exception) + { + /* This can happen when the system decides Emacs is not in the + foreground any longer. */ + return false; + } /* If there are less than four buttons, then they must be individually enabled or disabled after the dialog is -- cgit v1.2.1 From 0198b8cffd82893412c738dae8e50c45a99286f1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 12 Feb 2023 20:32:25 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Environment): Document notifications permission. * java/org/gnu/emacs/EmacsEditable.java (EmacsEditable): * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): New files. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Load library dependencies in a less verbose fashion. * java/org/gnu/emacs/EmacsView.java (EmacsView): Make imManager public. (onCreateInputConnection): Set InputType to TYPE_NULL for now. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyDown) (onKeyUp, getEventUnicodeChar): Correctly handle key events with strings. * lisp/term/android-win.el (android-clear-preedit-text) (android-preedit-text): New special event handlers. * src/android.c (struct android_emacs_window): Add function lookup_string. (android_init_emacs_window): Adjust accordingly. (android_wc_lookup_string): New function. * src/androidgui.h (struct android_key_event): Improve commentary. (enum android_lookup_status): New enum. * src/androidterm.c (handle_one_android_event): Synchronize IM lookup code with X. * src/coding.c (from_unicode_buffer): Implement on Android. * src/coding.h: * src/sfnt.c: Fix commentary. --- java/org/gnu/emacs/EmacsEditable.java | 300 +++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsInputConnection.java | 175 ++++++++++++++++ java/org/gnu/emacs/EmacsNative.java | 166 +++------------ java/org/gnu/emacs/EmacsView.java | 7 +- java/org/gnu/emacs/EmacsWindow.java | 96 ++++++++- 5 files changed, 588 insertions(+), 156 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsEditable.java create mode 100644 java/org/gnu/emacs/EmacsInputConnection.java (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsEditable.java b/java/org/gnu/emacs/EmacsEditable.java new file mode 100644 index 00000000000..79af65a6ccd --- /dev/null +++ b/java/org/gnu/emacs/EmacsEditable.java @@ -0,0 +1,300 @@ +/* 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.text.InputFilter; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.SpanWatcher; +import android.text.Selection; + +import android.content.Context; + +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; + +import android.text.Spannable; + +import android.util.Log; + +import android.os.Build; + +/* Android input methods insist on having access to buffer contents. + Since Emacs is not designed like ``any other Android text editor'', + that is not possible. + + This file provides a fake editing buffer that is designed to weasel + as much information as possible out of an input method, without + actually providing buffer contents to Emacs. + + The basic idea is to have the fake editing buffer be initially + empty. + + When the input method inserts composed text, it sets a flag. + Updates to the buffer while the flag is set are sent to Emacs to be + displayed as ``preedit text''. + + Once some heuristics decide that composition has been completed, + the composed text is sent to Emacs, and the text that was inserted + in this editing buffer is erased. */ + +public class EmacsEditable extends SpannableStringBuilder + implements SpanWatcher +{ + private static final String TAG = "EmacsEditable"; + + /* Whether or not composition is currently in progress. */ + private boolean isComposing; + + /* The associated input connection. */ + private EmacsInputConnection connection; + + /* The associated IM manager. */ + private InputMethodManager imManager; + + /* Any extracted text an input method may be monitoring. */ + private ExtractedText extractedText; + + /* The corresponding text request. */ + private ExtractedTextRequest extractRequest; + + /* The number of nested batch edits. */ + private int batchEditCount; + + /* Whether or not invalidateInput should be called upon batch edits + ending. */ + private boolean pendingInvalidate; + + /* The ``composing span'' indicating the bounds of an ongoing + character composition. */ + private Object composingSpan; + + public + EmacsEditable (EmacsInputConnection connection) + { + /* Initialize the editable with one initial space, so backspace + always works. */ + super (); + + Object tem; + Context context; + + this.connection = connection; + + context = connection.view.getContext (); + tem = context.getSystemService (Context.INPUT_METHOD_SERVICE); + imManager = (InputMethodManager) tem; + + /* To watch for changes to text properties on Android, you + add... a text property. */ + setSpan (this, 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + public void + endBatchEdit () + { + if (batchEditCount < 1) + return; + + if (--batchEditCount == 0 && pendingInvalidate) + invalidateInput (); + } + + public void + beginBatchEdit () + { + ++batchEditCount; + } + + public void + setExtractedTextAndRequest (ExtractedText text, + ExtractedTextRequest request, + boolean monitor) + { + /* Extract the text. If monitor is set, also record it as the + text that is currently being extracted. */ + + text.startOffset = 0; + text.selectionStart = Selection.getSelectionStart (this); + text.selectionEnd = Selection.getSelectionStart (this); + text.text = this; + + if (monitor) + { + extractedText = text; + extractRequest = request; + } + } + + public void + compositionStart () + { + isComposing = true; + } + + public void + compositionEnd () + { + isComposing = false; + sendComposingText (null); + } + + private void + sendComposingText (String string) + { + EmacsWindow window; + long time, serial; + + window = connection.view.window; + + if (window.isDestroyed ()) + return; + + time = System.currentTimeMillis (); + + /* A composition event is simply a special key event with a + keycode of -1. */ + + synchronized (window.eventStrings) + { + serial + = EmacsNative.sendKeyPress (window.handle, time, 0, -1, -1); + + /* Save the string so that android_lookup_string can find + it. */ + if (string != null) + window.saveUnicodeString ((int) serial, string); + } + } + + private void + invalidateInput () + { + int start, end, composingSpanStart, composingSpanEnd; + + if (batchEditCount > 0) + { + Log.d (TAG, "invalidateInput: deferring for batch edit"); + pendingInvalidate = true; + return; + } + + pendingInvalidate = false; + + start = Selection.getSelectionStart (this); + end = Selection.getSelectionEnd (this); + + if (composingSpan != null) + { + composingSpanStart = getSpanStart (composingSpan); + composingSpanEnd = getSpanEnd (composingSpan); + } + else + { + composingSpanStart = -1; + composingSpanEnd = -1; + } + + Log.d (TAG, "invalidateInput: now " + start + ", " + end); + + /* Tell the input method that the cursor changed. */ + imManager.updateSelection (connection.view, start, end, + composingSpanStart, + composingSpanEnd); + + /* If there is any extracted text, tell the IME that it has + changed. */ + if (extractedText != null) + imManager.updateExtractedText (connection.view, + extractRequest.token, + extractedText); + } + + public SpannableStringBuilder + replace (int start, int end, CharSequence tb, int tbstart, + int tbend) + { + super.replace (start, end, tb, tbstart, tbend); + + /* If a change happens during composition, perform the change and + then send the text being composed. */ + + if (isComposing) + sendComposingText (toString ()); + + return this; + } + + private boolean + isSelectionSpan (Object span) + { + return ((Selection.SELECTION_START == span + || Selection.SELECTION_END == span) + && (getSpanFlags (span) + & Spanned.SPAN_INTERMEDIATE) == 0); + } + + @Override + public void + onSpanAdded (Spannable text, Object what, int start, int end) + { + Log.d (TAG, "onSpanAdded: " + text + " " + what + " " + + start + " " + end); + + /* Try to find the composing span. This isn't a public API. */ + + if (what.getClass ().getName ().contains ("ComposingText")) + composingSpan = what; + + if (isSelectionSpan (what)) + invalidateInput (); + } + + @Override + public void + onSpanChanged (Spannable text, Object what, int ostart, + int oend, int nstart, int nend) + { + Log.d (TAG, "onSpanChanged: " + text + " " + what + " " + + nstart + " " + nend); + + if (isSelectionSpan (what)) + invalidateInput (); + } + + @Override + public void + onSpanRemoved (Spannable text, Object what, + int start, int end) + { + Log.d (TAG, "onSpanRemoved: " + text + " " + what + " " + + start + " " + end); + + if (isSelectionSpan (what)) + invalidateInput (); + } + + public boolean + isInBatchEdit () + { + return batchEditCount > 0; + } +} diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java new file mode 100644 index 00000000000..897a393b984 --- /dev/null +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -0,0 +1,175 @@ +/* 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.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.SurroundingText; +import android.view.KeyEvent; + +import android.text.Editable; + +import android.util.Log; + +/* Android input methods, take number six. + + See EmacsEditable for more details. */ + +public class EmacsInputConnection extends BaseInputConnection +{ + private static final String TAG = "EmacsInputConnection"; + public EmacsView view; + private EmacsEditable editable; + + /* The length of the last string to be committed. */ + private int lastCommitLength; + + int currentLargeOffset; + + public + EmacsInputConnection (EmacsView view) + { + super (view, false); + this.view = view; + this.editable = new EmacsEditable (this); + } + + @Override + public Editable + getEditable () + { + return editable; + } + + @Override + public boolean + setComposingText (CharSequence text, int newCursorPosition) + { + editable.compositionStart (); + super.setComposingText (text, newCursorPosition); + return true; + } + + @Override + public boolean + setComposingRegion (int start, int end) + { + int i; + + if (lastCommitLength != 0) + { + Log.d (TAG, "Restarting composition for: " + lastCommitLength); + + for (i = 0; i < lastCommitLength; ++i) + sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DEL)); + + lastCommitLength = 0; + } + + editable.compositionStart (); + super.setComposingRegion (start, end); + return true; + } + + @Override + public boolean + finishComposingText () + { + editable.compositionEnd (); + return super.finishComposingText (); + } + + @Override + public boolean + beginBatchEdit () + { + editable.beginBatchEdit (); + return super.beginBatchEdit (); + } + + @Override + public boolean + endBatchEdit () + { + editable.endBatchEdit (); + return super.endBatchEdit (); + } + + @Override + public boolean + commitText (CharSequence text, int newCursorPosition) + { + editable.compositionEnd (); + super.commitText (text, newCursorPosition); + + /* An observation is that input methods rarely recompose trailing + spaces. Avoid re-setting the commit length in that case. */ + + if (text.toString ().equals (" ")) + lastCommitLength += 1; + else + /* At this point, the editable is now empty. + + The input method may try to cancel the edit upon a subsequent + backspace by calling setComposingRegion with a region that is + the length of TEXT. + + Record this length in order to be able to send backspace + events to ``delete'' the text in that case. */ + lastCommitLength = text.length (); + + Log.d (TAG, "commitText: \"" + text + "\""); + + return true; + } + + /* Return a large offset, cycling through 100000, 30000, 0. + The offset is typically used to force the input method to update + its notion of ``surrounding text'', bypassing any caching that + it might have in progress. + + There must be another way to do this, but I can't find it. */ + + public int + largeSelectionOffset () + { + switch (currentLargeOffset) + { + case 0: + currentLargeOffset = 100000; + return 100000; + + case 100000: + currentLargeOffset = 30000; + return 30000; + + case 30000: + currentLargeOffset = 0; + return 0; + } + + currentLargeOffset = 0; + return -1; + } +} diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 939348ba420..f0219843d35 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 { + /* List of native libraries that must be loaded during class + initialization. */ + private static final String[] libraryDeps; + /* Obtain the fingerprint of this build of Emacs. The fingerprint can be used to determine the dump file name. */ public static native String getFingerprint (); @@ -167,148 +171,26 @@ public class EmacsNative Every time you add a new shared library dependency to Emacs, please add it here as well. */ - try - { - System.loadLibrary ("png_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("selinux_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("crypto_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("pcre_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("packagelistparser_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("gnutls_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("gmp_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("nettle_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("p11-kit_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("tasn1_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("hogweed_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("jansson_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("jpeg_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("tiff_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("xml2_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("icuuc_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ + libraryDeps = new String[] { "png_emacs", "selinux_emacs", + "crypto_emacs", "pcre_emacs", + "packagelistparser_emacs", + "gnutls_emacs", "gmp_emacs", + "nettle_emacs", "p11-kit_emacs", + "tasn1_emacs", "hogweed_emacs", + "jansson_emacs", "jpeg_emacs", + "tiff_emacs", "xml2_emacs", + "icuuc_emacs", }; + + for (String dependency : libraryDeps) + { + try + { + System.loadLibrary (dependency); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } } System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 4fc8104e31f..bc3716f6da8 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -94,7 +94,7 @@ public class EmacsView extends ViewGroup private long lastClipSerial; /* The InputMethodManager for this view's context. */ - private InputMethodManager imManager; + public InputMethodManager imManager; /* Whether or not this view is attached to a window. */ public boolean isAttachedToWindow; @@ -558,8 +558,9 @@ public class EmacsView extends ViewGroup box that obscures Emacs. */ info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; - /* But don't return an InputConnection, in order to force the on - screen keyboard to work correctly. */ + /* Set a reasonable inputType. */ + info.inputType = InputType.TYPE_NULL; + return null; } diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 9e2f2f53270..0eca35cec61 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -23,6 +23,8 @@ import java.lang.IllegalStateException; import java.util.ArrayList; import java.util.List; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import android.content.Context; @@ -100,7 +102,7 @@ public class EmacsWindow extends EmacsHandleObject /* The button state and keyboard modifier mask at the time of the last button press or release event. */ - private int lastButtonState, lastModifiers; + public int lastButtonState, lastModifiers; /* Whether or not the window is mapped, and whether or not it is deiconified. */ @@ -122,6 +124,10 @@ public class EmacsWindow extends EmacsHandleObject to quit Emacs. */ private long lastVolumeButtonRelease; + /* Linked list of character strings which were recently sent as + events. */ + public LinkedHashMap eventStrings; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -155,6 +161,19 @@ public class EmacsWindow extends EmacsHandleObject } scratchGC = new EmacsGC ((short) 0); + + /* Create the map of input method-committed strings. Keep at most + ten strings in the map. */ + + eventStrings + = new LinkedHashMap () { + @Override + protected boolean + removeEldestEntry (Map.Entry entry) + { + return size () > 10; + } + }; } public void @@ -507,10 +526,40 @@ public class EmacsWindow extends EmacsHandleObject return view.getBitmap (); } + /* event.getCharacters is used because older input methods still + require it. */ + @SuppressWarnings ("deprecation") + public int + getEventUnicodeChar (KeyEvent event, int state) + { + String characters; + + if (event.getUnicodeChar (state) != 0) + return event.getUnicodeChar (state); + + characters = event.getCharacters (); + + if (characters != null && characters.length () == 1) + return characters.charAt (0); + + return characters == null ? 0 : -1; + } + + public void + saveUnicodeString (int serial, String string) + { + eventStrings.put (serial, string); + } + + /* event.getCharacters is used because older input methods still + require it. */ + @SuppressWarnings ("deprecation") public void onKeyDown (int keyCode, KeyEvent event) { int state, state_1; + long serial; + String characters; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -537,18 +586,28 @@ public class EmacsWindow extends EmacsHandleObject state_1 = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); - EmacsNative.sendKeyPress (this.handle, - event.getEventTime (), - state, keyCode, - event.getUnicodeChar (state_1)); - lastModifiers = state; + synchronized (eventStrings) + { + serial + = EmacsNative.sendKeyPress (this.handle, + event.getEventTime (), + state, keyCode, + getEventUnicodeChar (event, + state_1)); + lastModifiers = state; + + characters = event.getCharacters (); + + if (characters != null && characters.length () > 1) + saveUnicodeString ((int) serial, characters); + } } public void onKeyUp (int keyCode, KeyEvent event) { int state, state_1; - long time; + long time, serial; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -575,10 +634,12 @@ public class EmacsWindow extends EmacsHandleObject state_1 = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); - EmacsNative.sendKeyRelease (this.handle, - event.getEventTime (), - state, keyCode, - event.getUnicodeChar (state_1)); + serial + = EmacsNative.sendKeyRelease (this.handle, + event.getEventTime (), + state, keyCode, + getEventUnicodeChar (event, + state_1)); lastModifiers = state; if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) @@ -1105,4 +1166,17 @@ public class EmacsWindow extends EmacsHandleObject } }); } + + public String + lookupString (int eventSerial) + { + String any; + + synchronized (eventStrings) + { + any = eventStrings.remove (eventSerial); + } + + return any; + } }; -- cgit v1.2.1 From a158c1d5b964fda36f752998cef076d581dc4df4 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 15 Feb 2023 12:23:03 +0800 Subject: Update Android port * configure.ac (HAVE_TEXT_CONVERSION): Define on Android. * doc/emacs/input.texi (On-Screen Keyboards): Document ``text conversion'' slightly. * doc/lispref/commands.texi (Misc Events): Document new `text-conversion' event. * java/org/gnu/emacs/EmacsContextMenu.java (display): Use `syncRunnable'. * java/org/gnu/emacs/EmacsDialog.java (display): Likewise. * java/org/gnu/emacs/EmacsEditable.java: Delete file. * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): Reimplement from scratch. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Add new functions. * java/org/gnu/emacs/EmacsService.java (EmacsService, getEmacsView) (getLocationOnScreen, sync, getClipboardManager, restartEmacs): Use syncRunnable. (syncRunnable): New function. (updateIC, resetIC): New functions. * java/org/gnu/emacs/EmacsView.java (EmacsView): New field `inputConnection' and `icMode'. (onCreateInputConnection): Update accordingly. (setICMode, getICMode): New functions. * lisp/bindings.el (global-map): Ignore text conversion events. * src/alloc.c (mark_frame): Mark text conversion data. * src/android.c (struct android_emacs_service): New fields `update_ic' and `reset_ic'. (event_serial): Export. (android_query_sem): New function. (android_init_events): Initialize new semaphore. (android_write_event): Export. (android_select): Check for UI thread code. (setEmacsParams, android_init_emacs_service): Initialize new methods. (android_check_query, android_begin_query, android_end_query) (android_run_in_emacs_thread): (android_update_ic, android_reset_ic): New functions for managing synchronous queries from one thread to another. * src/android.h: Export new functions. * src/androidgui.h (enum android_event_type): Add input method events. (enum android_ime_operation, struct android_ime_event) (union android_event, enum android_ic_mode): New structs and enums. * src/androidterm.c (android_window_to_frame): Allow DPYINFO to be NULL. (android_decode_utf16, android_handle_ime_event) (handle_one_android_event, android_sync_edit) (android_copy_java_string, beginBatchEdit, endBatchEdit) (commitCompletion, deleteSurroundingText, finishComposingText) (getSelectedtext, getTextAfterCursor, getTextBeforeCursor) (setComposingText, setComposingRegion, setSelection, getSelection) (performEditorAction, getExtractedText): New functions. (struct android_conversion_query_context): (android_perform_conversion_query): (android_text_to_string): (struct android_get_selection_context): (android_get_selection): (struct android_get_extracted_text_context): (android_get_extracted_text): (struct android_extracted_text_request_class): (struct android_extracted_text_class): (android_update_selection): (android_reset_conversion): (android_set_point): (android_compose_region_changed): (android_notify_conversion): (text_conversion_interface): New functions and structures. (android_term_init): Initialize text conversion. * src/coding.c (syms_of_coding): Define Qutf_16le on Android. * src/frame.c (make_frame): Clear conversion data. (delete_frame): Reset conversion state. * src/frame.h (enum text_conversion_operation) (struct text_conversion_action, struct text_conversion_state) (GCALIGNED_STRUCT): Update structures. * src/keyboard.c (read_char, readable_events, kbd_buffer_get_event) (syms_of_keyboard): Handle text conversion events. * src/lisp.h: * src/process.c: Fix includes. * src/textconv.c (enum textconv_batch_edit_flags, textconv_query) (reset_frame_state, detect_conversion_events) (restore_selected_window, really_commit_text) (really_finish_composing_text, really_set_composing_text) (really_set_composing_region, really_delete_surrounding_text) (really_set_point, complete_edit) (handle_pending_conversion_events_1) (handle_pending_conversion_events, start_batch_edit) (end_batch_edit, commit_text, finish_composing_text) (set_composing_text, set_composing_region, textconv_set_point) (delete_surrounding_text, get_extracted_text) (report_selected_window_change, report_point_change) (register_texconv_interface): New functions. * src/textconv.h (struct textconv_interface) (TEXTCONV_SKIP_CONVERSION_REGION): Update prototype. * src/xdisp.c (mark_window_display_accurate_1): * src/xfns.c (xic_string_conversion_callback): * src/xterm.c (init_xterm): Adjust accordingly. --- java/org/gnu/emacs/EmacsContextMenu.java | 15 +- java/org/gnu/emacs/EmacsDialog.java | 15 +- java/org/gnu/emacs/EmacsEditable.java | 300 --------------------------- java/org/gnu/emacs/EmacsInputConnection.java | 187 +++++++++-------- java/org/gnu/emacs/EmacsNative.java | 46 ++++ java/org/gnu/emacs/EmacsService.java | 117 ++++++----- java/org/gnu/emacs/EmacsView.java | 55 ++++- 7 files changed, 267 insertions(+), 468 deletions(-) delete mode 100644 java/org/gnu/emacs/EmacsEditable.java (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 92429410d03..184c611bf9d 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -279,20 +279,7 @@ public class EmacsContextMenu } }; - synchronized (runnable) - { - EmacsService.SERVICE.runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + EmacsService.syncRunnable (runnable); return rc.thing; } diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index bd5e9ba8ee7..bc0333e99b9 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -317,20 +317,7 @@ public class EmacsDialog implements DialogInterface.OnDismissListener } }; - synchronized (runnable) - { - EmacsService.SERVICE.runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + EmacsService.syncRunnable (runnable); return rc.thing; } diff --git a/java/org/gnu/emacs/EmacsEditable.java b/java/org/gnu/emacs/EmacsEditable.java deleted file mode 100644 index 79af65a6ccd..00000000000 --- a/java/org/gnu/emacs/EmacsEditable.java +++ /dev/null @@ -1,300 +0,0 @@ -/* 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.text.InputFilter; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.SpanWatcher; -import android.text.Selection; - -import android.content.Context; - -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; - -import android.text.Spannable; - -import android.util.Log; - -import android.os.Build; - -/* Android input methods insist on having access to buffer contents. - Since Emacs is not designed like ``any other Android text editor'', - that is not possible. - - This file provides a fake editing buffer that is designed to weasel - as much information as possible out of an input method, without - actually providing buffer contents to Emacs. - - The basic idea is to have the fake editing buffer be initially - empty. - - When the input method inserts composed text, it sets a flag. - Updates to the buffer while the flag is set are sent to Emacs to be - displayed as ``preedit text''. - - Once some heuristics decide that composition has been completed, - the composed text is sent to Emacs, and the text that was inserted - in this editing buffer is erased. */ - -public class EmacsEditable extends SpannableStringBuilder - implements SpanWatcher -{ - private static final String TAG = "EmacsEditable"; - - /* Whether or not composition is currently in progress. */ - private boolean isComposing; - - /* The associated input connection. */ - private EmacsInputConnection connection; - - /* The associated IM manager. */ - private InputMethodManager imManager; - - /* Any extracted text an input method may be monitoring. */ - private ExtractedText extractedText; - - /* The corresponding text request. */ - private ExtractedTextRequest extractRequest; - - /* The number of nested batch edits. */ - private int batchEditCount; - - /* Whether or not invalidateInput should be called upon batch edits - ending. */ - private boolean pendingInvalidate; - - /* The ``composing span'' indicating the bounds of an ongoing - character composition. */ - private Object composingSpan; - - public - EmacsEditable (EmacsInputConnection connection) - { - /* Initialize the editable with one initial space, so backspace - always works. */ - super (); - - Object tem; - Context context; - - this.connection = connection; - - context = connection.view.getContext (); - tem = context.getSystemService (Context.INPUT_METHOD_SERVICE); - imManager = (InputMethodManager) tem; - - /* To watch for changes to text properties on Android, you - add... a text property. */ - setSpan (this, 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - public void - endBatchEdit () - { - if (batchEditCount < 1) - return; - - if (--batchEditCount == 0 && pendingInvalidate) - invalidateInput (); - } - - public void - beginBatchEdit () - { - ++batchEditCount; - } - - public void - setExtractedTextAndRequest (ExtractedText text, - ExtractedTextRequest request, - boolean monitor) - { - /* Extract the text. If monitor is set, also record it as the - text that is currently being extracted. */ - - text.startOffset = 0; - text.selectionStart = Selection.getSelectionStart (this); - text.selectionEnd = Selection.getSelectionStart (this); - text.text = this; - - if (monitor) - { - extractedText = text; - extractRequest = request; - } - } - - public void - compositionStart () - { - isComposing = true; - } - - public void - compositionEnd () - { - isComposing = false; - sendComposingText (null); - } - - private void - sendComposingText (String string) - { - EmacsWindow window; - long time, serial; - - window = connection.view.window; - - if (window.isDestroyed ()) - return; - - time = System.currentTimeMillis (); - - /* A composition event is simply a special key event with a - keycode of -1. */ - - synchronized (window.eventStrings) - { - serial - = EmacsNative.sendKeyPress (window.handle, time, 0, -1, -1); - - /* Save the string so that android_lookup_string can find - it. */ - if (string != null) - window.saveUnicodeString ((int) serial, string); - } - } - - private void - invalidateInput () - { - int start, end, composingSpanStart, composingSpanEnd; - - if (batchEditCount > 0) - { - Log.d (TAG, "invalidateInput: deferring for batch edit"); - pendingInvalidate = true; - return; - } - - pendingInvalidate = false; - - start = Selection.getSelectionStart (this); - end = Selection.getSelectionEnd (this); - - if (composingSpan != null) - { - composingSpanStart = getSpanStart (composingSpan); - composingSpanEnd = getSpanEnd (composingSpan); - } - else - { - composingSpanStart = -1; - composingSpanEnd = -1; - } - - Log.d (TAG, "invalidateInput: now " + start + ", " + end); - - /* Tell the input method that the cursor changed. */ - imManager.updateSelection (connection.view, start, end, - composingSpanStart, - composingSpanEnd); - - /* If there is any extracted text, tell the IME that it has - changed. */ - if (extractedText != null) - imManager.updateExtractedText (connection.view, - extractRequest.token, - extractedText); - } - - public SpannableStringBuilder - replace (int start, int end, CharSequence tb, int tbstart, - int tbend) - { - super.replace (start, end, tb, tbstart, tbend); - - /* If a change happens during composition, perform the change and - then send the text being composed. */ - - if (isComposing) - sendComposingText (toString ()); - - return this; - } - - private boolean - isSelectionSpan (Object span) - { - return ((Selection.SELECTION_START == span - || Selection.SELECTION_END == span) - && (getSpanFlags (span) - & Spanned.SPAN_INTERMEDIATE) == 0); - } - - @Override - public void - onSpanAdded (Spannable text, Object what, int start, int end) - { - Log.d (TAG, "onSpanAdded: " + text + " " + what + " " - + start + " " + end); - - /* Try to find the composing span. This isn't a public API. */ - - if (what.getClass ().getName ().contains ("ComposingText")) - composingSpan = what; - - if (isSelectionSpan (what)) - invalidateInput (); - } - - @Override - public void - onSpanChanged (Spannable text, Object what, int ostart, - int oend, int nstart, int nend) - { - Log.d (TAG, "onSpanChanged: " + text + " " + what + " " - + nstart + " " + nend); - - if (isSelectionSpan (what)) - invalidateInput (); - } - - @Override - public void - onSpanRemoved (Spannable text, Object what, - int start, int end) - { - Log.d (TAG, "onSpanRemoved: " + text + " " + what + " " - + start + " " + end); - - if (isSelectionSpan (what)) - invalidateInput (); - } - - public boolean - isInBatchEdit () - { - return batchEditCount > 0; - } -} diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 897a393b984..3cf4419838b 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -25,6 +25,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextSnapshot; import android.view.KeyEvent; import android.text.Editable; @@ -38,138 +39,156 @@ import android.util.Log; public class EmacsInputConnection extends BaseInputConnection { private static final String TAG = "EmacsInputConnection"; - public EmacsView view; - private EmacsEditable editable; - - /* The length of the last string to be committed. */ - private int lastCommitLength; - - int currentLargeOffset; + private EmacsView view; + private short windowHandle; public EmacsInputConnection (EmacsView view) { - super (view, false); + super (view, true); + this.view = view; - this.editable = new EmacsEditable (this); + this.windowHandle = view.window.handle; } @Override - public Editable - getEditable () + public boolean + beginBatchEdit () { - return editable; + Log.d (TAG, "beginBatchEdit"); + EmacsNative.beginBatchEdit (windowHandle); + return true; } @Override public boolean - setComposingText (CharSequence text, int newCursorPosition) + endBatchEdit () { - editable.compositionStart (); - super.setComposingText (text, newCursorPosition); + Log.d (TAG, "endBatchEdit"); + EmacsNative.endBatchEdit (windowHandle); return true; } @Override public boolean - setComposingRegion (int start, int end) + commitCompletion (CompletionInfo info) { - int i; - - if (lastCommitLength != 0) - { - Log.d (TAG, "Restarting composition for: " + lastCommitLength); - - for (i = 0; i < lastCommitLength; ++i) - sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_DEL)); + Log.d (TAG, "commitCompletion: " + info); + EmacsNative.commitCompletion (windowHandle, + info.getText ().toString (), + info.getPosition ()); + return true; + } - lastCommitLength = 0; - } + @Override + public boolean + commitText (CharSequence text, int newCursorPosition) + { + Log.d (TAG, "commitText: " + text + " " + newCursorPosition); + EmacsNative.commitText (windowHandle, text.toString (), + newCursorPosition); + return true; + } - editable.compositionStart (); - super.setComposingRegion (start, end); + @Override + public boolean + deleteSurroundingText (int leftLength, int rightLength) + { + Log.d (TAG, ("deleteSurroundingText: " + + leftLength + " " + rightLength)); + EmacsNative.deleteSurroundingText (windowHandle, leftLength, + rightLength); return true; } - + @Override public boolean finishComposingText () { - editable.compositionEnd (); - return super.finishComposingText (); + Log.d (TAG, "finishComposingText"); + + EmacsNative.finishComposingText (windowHandle); + return true; + } + + @Override + public String + getSelectedText (int flags) + { + Log.d (TAG, "getSelectedText: " + flags); + + return EmacsNative.getSelectedText (windowHandle, flags); + } + + @Override + public String + getTextAfterCursor (int length, int flags) + { + Log.d (TAG, "getTextAfterCursor: " + length + " " + flags); + + return EmacsNative.getTextAfterCursor (windowHandle, length, + flags); + } + + @Override + public String + getTextBeforeCursor (int length, int flags) + { + Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags); + + return EmacsNative.getTextBeforeCursor (windowHandle, length, + flags); } @Override public boolean - beginBatchEdit () + setComposingText (CharSequence text, int newCursorPosition) { - editable.beginBatchEdit (); - return super.beginBatchEdit (); + Log.d (TAG, "setComposingText: " + newCursorPosition); + + EmacsNative.setComposingText (windowHandle, text.toString (), + newCursorPosition); + return true; } @Override public boolean - endBatchEdit () + setComposingRegion (int start, int end) { - editable.endBatchEdit (); - return super.endBatchEdit (); + Log.d (TAG, "setComposingRegion: " + start + " " + end); + + EmacsNative.setComposingRegion (windowHandle, start, end); + return true; } - + @Override public boolean - commitText (CharSequence text, int newCursorPosition) + performEditorAction (int editorAction) { - editable.compositionEnd (); - super.commitText (text, newCursorPosition); - - /* An observation is that input methods rarely recompose trailing - spaces. Avoid re-setting the commit length in that case. */ - - if (text.toString ().equals (" ")) - lastCommitLength += 1; - else - /* At this point, the editable is now empty. - - The input method may try to cancel the edit upon a subsequent - backspace by calling setComposingRegion with a region that is - the length of TEXT. - - Record this length in order to be able to send backspace - events to ``delete'' the text in that case. */ - lastCommitLength = text.length (); - - Log.d (TAG, "commitText: \"" + text + "\""); + Log.d (TAG, "performEditorAction: " + editorAction); + EmacsNative.performEditorAction (windowHandle, editorAction); return true; } - /* Return a large offset, cycling through 100000, 30000, 0. - The offset is typically used to force the input method to update - its notion of ``surrounding text'', bypassing any caching that - it might have in progress. + @Override + public ExtractedText + getExtractedText (ExtractedTextRequest request, int flags) + { + Log.d (TAG, "getExtractedText: " + request + " " + flags); + + return EmacsNative.getExtractedText (windowHandle, request, + flags); + } - There must be another way to do this, but I can't find it. */ + + /* Override functions which are not implemented. */ - public int - largeSelectionOffset () + @Override + public TextSnapshot + takeSnapshot () { - switch (currentLargeOffset) - { - case 0: - currentLargeOffset = 100000; - return 100000; - - case 100000: - currentLargeOffset = 30000; - return 30000; - - case 30000: - currentLargeOffset = 0; - return 0; - } - - currentLargeOffset = 0; - return -1; + Log.d (TAG, "takeSnapshot"); + return null; } } diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index f0219843d35..aaac9446510 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -22,6 +22,8 @@ package org.gnu.emacs; import java.lang.System; import android.content.res.AssetManager; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; public class EmacsNative { @@ -161,6 +163,50 @@ public class EmacsNative descriptor, or NULL if there is none. */ public static native byte[] getProcName (int fd); + /* Notice that the Emacs thread will now start waiting for the main + thread's looper to respond. */ + public static native void beginSynchronous (); + + /* Notice that the Emacs thread will has finished waiting for the + main thread's looper to respond. */ + public static native void endSynchronous (); + + + + /* Input connection functions. These mostly correspond to their + counterparts in Android's InputConnection. */ + + public static native void beginBatchEdit (short window); + public static native void endBatchEdit (short window); + public static native void commitCompletion (short window, String text, + int position); + public static native void commitText (short window, String text, + int position); + public static native void deleteSurroundingText (short window, + int leftLength, + int rightLength); + public static native void finishComposingText (short window); + public static native String getSelectedText (short window, int flags); + public static native String getTextAfterCursor (short window, int length, + int flags); + public static native String getTextBeforeCursor (short window, int length, + int flags); + public static native void setComposingText (short window, String text, + int newCursorPosition); + public static native void setComposingRegion (short window, int start, + int end); + public static native void setSelection (short window, int start, int end); + public static native void performEditorAction (short window, + int editorAction); + public static native ExtractedText getExtractedText (short window, + ExtractedTextRequest req, + int flags); + + + /* Return the current value of the selection, or -1 upon + failure. */ + public static native int getSelection (short window); + static { /* Older versions of Android cannot link correctly with shared diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2ec2ddf9bda..855a738a30f 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -80,6 +80,11 @@ public class EmacsService extends Service private EmacsThread thread; private Handler handler; + /* Keep this in synch with androidgui.h. */ + public static final int IC_MODE_NULL = 0; + public static final int IC_MODE_ACTION = 1; + public static final int IC_MODE_TEXT = 2; + /* Display metrics used by font backends. */ public DisplayMetrics metrics; @@ -258,20 +263,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + syncRunnable (runnable); return view.thing; } @@ -292,19 +284,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } + syncRunnable (runnable); } public void @@ -502,19 +482,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } + syncRunnable (runnable); } @@ -594,20 +562,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + syncRunnable (runnable); return manager.thing; } @@ -622,4 +577,58 @@ public class EmacsService extends Service startActivity (intent); System.exit (0); } + + /* Wait synchronously for the specified RUNNABLE to complete in the + UI thread. Must be called from the Emacs thread. */ + + public static void + syncRunnable (Runnable runnable) + { + EmacsNative.beginSynchronous (); + + synchronized (runnable) + { + SERVICE.runOnUiThread (runnable); + + while (true) + { + try + { + runnable.wait (); + break; + } + catch (InterruptedException e) + { + continue; + } + } + } + + EmacsNative.endSynchronous (); + } + + public void + updateIC (EmacsWindow window, int newSelectionStart, + int newSelectionEnd, int composingRegionStart, + int composingRegionEnd) + { + Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart + + " " + newSelectionEnd + " " + + composingRegionStart + " " + + composingRegionEnd)); + window.view.imManager.updateSelection (window.view, + newSelectionStart, + newSelectionEnd, + composingRegionStart, + composingRegionEnd); + } + + public void + resetIC (EmacsWindow window, int icMode) + { + Log.d (TAG, "resetIC: " + window); + + window.view.setICMode (icMode); + window.view.imManager.restartInput (window.view); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index bc3716f6da8..52da6d41f7d 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -103,6 +103,13 @@ public class EmacsView extends ViewGroup displayed whenever possible. */ public boolean isCurrentlyTextEditor; + /* The associated input connection. */ + private EmacsInputConnection inputConnection; + + /* The current IC mode. See `android_reset_ic' for more + details. */ + private int icMode; + public EmacsView (EmacsWindow window) { @@ -554,14 +561,46 @@ public class EmacsView extends ViewGroup public InputConnection onCreateInputConnection (EditorInfo info) { + int selection, mode; + + /* Figure out what kind of IME behavior Emacs wants. */ + mode = getICMode (); + /* Make sure the input method never displays a full screen input box that obscures Emacs. */ info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; /* Set a reasonable inputType. */ - info.inputType = InputType.TYPE_NULL; + info.inputType = InputType.TYPE_CLASS_TEXT; + + /* Obtain the current position of point and set it as the + selection. */ + selection = EmacsNative.getSelection (window.handle); + + Log.d (TAG, "onCreateInputConnection: current selection is: " + selection); + + /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't + initialize the input connection. */ + if (selection == -1 || mode == EmacsService.IC_MODE_NULL) + { + info.inputType = InputType.TYPE_NULL; + return null; + } + + if (mode == EmacsService.IC_MODE_ACTION) + info.imeOptions |= EditorInfo.IME_ACTION_DONE; - return null; + /* Set the initial selection fields. */ + info.initialSelStart = selection; + info.initialSelEnd = selection; + + /* Create the input connection if necessary. */ + + if (inputConnection == null) + inputConnection = new EmacsInputConnection (this); + + /* Return the input connection. */ + return inputConnection; } @Override @@ -572,4 +611,16 @@ public class EmacsView extends ViewGroup keyboard. */ return isCurrentlyTextEditor; } + + public synchronized void + setICMode (int icMode) + { + this.icMode = icMode; + } + + public synchronized int + getICMode () + { + return icMode; + } }; -- cgit v1.2.1 From dd7066901f67233c09f3b0409a57db7686c7ea5b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 15 Feb 2023 13:39:47 +0800 Subject: Make debug.sh detect adb running as root * java/debug.sh: Run gdbserver directly if possible. --- java/debug.sh | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) (limited to 'java') diff --git a/java/debug.sh b/java/debug.sh index 2e95f9738c7..cbef7518984 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -263,6 +263,8 @@ if [ -n "$jdb_command" ]; then fi # See if gdbserver has to be uploaded +gdbserver_cmd= +is_root= if [ -z "$gdbserver" ]; then gdbserver_bin=/system/bin/gdbserver else @@ -272,9 +274,26 @@ else # Upload the specified gdbserver binary to the device. adb -s $device push "$gdbserver" "$gdbserver_bin" - # Copy it to the user directory. - adb -s $device shell "$gdbserver_cat" - adb -s $device shell "run-as $package chmod +x gdbserver" + + if (adb -s $device pull /system/bin/tee /dev/null &> /dev/null); then + # Copy it to the user directory. + adb -s $device shell "$gdbserver_cat" + adb -s $device shell "run-as $package chmod +x gdbserver" + gdbserver_cmd="./gdbserver" + else + # Hopefully this is an old version of Android which allows + # execution from /data/local/tmp. Its `chmod' doesn't support + # `+x' either. + adb -s $device shell "chmod 777 $gdbserver_bin" + gdbserver_cmd="$gdbserver_bin" + + # If the user is root, then there is no need to open any kind + # of TCP socket. + if (adb -s $device shell id | grep -G root); then + gdbserver= + is_root=yes + fi + fi fi # Now start gdbserver on the device asynchronously. @@ -284,13 +303,19 @@ exec 5<> /tmp/file-descriptor-stamp rm -f /tmp/file-descriptor-stamp if [ -z "$gdbserver" ]; then - adb -s $device shell run-as $package $gdbserver_bin --once \ - "+debug.$package.socket" --attach $pid >&5 & - gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" + if [ "$is_root" = "yes" ]; then + adb -s $device shell $gdbserver_bin --once \ + "+/data/local/tmp/debug.$package.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" + else + adb -s $device shell run-as $package $gdbserver_bin --once \ + "+debug.$package.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" + fi else # Normally the program cannot access $gdbserver_bin when it is # placed in /data/local/tmp. - adb -s $device shell run-as $package "./gdbserver" --once \ + adb -s $device shell run-as $package $gdbserver_cmd --once \ "0.0.0.0:7654" --attach $pid >&5 & gdb_socket="tcp:7654" fi -- cgit v1.2.1 From cf24b61985c26cbf2e5a24cb0b64a8528aa3a9cc Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 15 Feb 2023 22:51:44 +0800 Subject: Update Android port * doc/emacs/input.texi (On-Screen Keyboards): * doc/lispref/commands.texi (Misc Events): Improve documentation of text conversion stuff. * java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit) (endBatchEdit, commitCompletion, commitText, deleteSurroundingText) (finishComposingText, getSelectedText, getTextAfterCursor) (EmacsInputConnection, setComposingRegion, performEditorAction) (getExtractedText): Condition debug code on DEBUG_IC. * java/org/gnu/emacs/EmacsService.java (EmacsService, updateIC): Likewise. * lisp/bindings.el (global-map): * lisp/electric.el (global-map): Make `text-conversion' `analyze-text-conversion'. * lisp/progmodes/prog-mode.el (prog-mode): Enable text conversion in input methods. * lisp/simple.el (analyze-text-conversion): New function. * lisp/textmodes/text-mode.el (text-conversion-style) (text-mode): Likewise. * src/androidterm.c (android_handle_ime_event): Handle set_point_and_mark. (android_sync_edit): Give Emacs 100 ms instead. (android_perform_conversion_query): Skip the active region, not the conversion region. (getSelectedText): Implement properly. (android_update_selection): Expose mark to input methods. (android_reset_conversion): Handle `text-conversion-style'. * src/buffer.c (init_buffer_once, syms_of_buffer): Add buffer local variable `text-conversion-style'. * src/buffer.h (struct buffer, bset_text_conversion_style): New fields. * src/emacs.c (android_emacs_init): Call syms_of_textconv. * src/frame.h (enum text_conversion_operation): Rename TEXTCONV_SET_POINT. * src/lisp.h: Export syms_of_textconv. * src/marker.c (set_marker_internal): Force redisplay when the mark is set and the buffer is visible on builds that use text conversion. Explain why. * src/textconv.c (copy_buffer): Fix copying past gap. (get_mark): New function. (textconv_query): Implement new flag. (sync_overlay): New function. Display conversion text in an overlay. (record_buffer_change, really_commit_text) (really_set_composing_text, really_set_composing_region) (really_delete_surrounding_text, really_set_point) (handle_pending_conversion_events_1, decrement_inside) (handle_pending_conversion_events, textconv_set_point) (get_extracted_text, register_textconv_interface): Various fixes and improvements. * src/textconv.h (struct textconv_interface): Update documentation. * src/window.h (GCALIGNED_STRUCT): New field `prev_mark'. * src/xdisp.c (mark_window_display_accurate_1): Handle prev_mark. --- java/org/gnu/emacs/EmacsInputConnection.java | 68 ++++++++++++++++++++-------- java/org/gnu/emacs/EmacsService.java | 14 ++++-- 2 files changed, 59 insertions(+), 23 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 3cf4419838b..5eb56d5aa71 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -55,7 +55,9 @@ public class EmacsInputConnection extends BaseInputConnection public boolean beginBatchEdit () { - Log.d (TAG, "beginBatchEdit"); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "beginBatchEdit"); + EmacsNative.beginBatchEdit (windowHandle); return true; } @@ -64,7 +66,9 @@ public class EmacsInputConnection extends BaseInputConnection public boolean endBatchEdit () { - Log.d (TAG, "endBatchEdit"); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "endBatchEdit"); + EmacsNative.endBatchEdit (windowHandle); return true; } @@ -73,7 +77,9 @@ public class EmacsInputConnection extends BaseInputConnection public boolean commitCompletion (CompletionInfo info) { - Log.d (TAG, "commitCompletion: " + info); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "commitCompletion: " + info); + EmacsNative.commitCompletion (windowHandle, info.getText ().toString (), info.getPosition ()); @@ -84,7 +90,9 @@ public class EmacsInputConnection extends BaseInputConnection public boolean commitText (CharSequence text, int newCursorPosition) { - Log.d (TAG, "commitText: " + text + " " + newCursorPosition); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "commitText: " + text + " " + newCursorPosition); + EmacsNative.commitText (windowHandle, text.toString (), newCursorPosition); return true; @@ -94,8 +102,10 @@ public class EmacsInputConnection extends BaseInputConnection public boolean deleteSurroundingText (int leftLength, int rightLength) { - Log.d (TAG, ("deleteSurroundingText: " - + leftLength + " " + rightLength)); + if (EmacsService.DEBUG_IC) + Log.d (TAG, ("deleteSurroundingText: " + + leftLength + " " + rightLength)); + EmacsNative.deleteSurroundingText (windowHandle, leftLength, rightLength); return true; @@ -105,7 +115,8 @@ public class EmacsInputConnection extends BaseInputConnection public boolean finishComposingText () { - Log.d (TAG, "finishComposingText"); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "finishComposingText"); EmacsNative.finishComposingText (windowHandle); return true; @@ -115,7 +126,8 @@ public class EmacsInputConnection extends BaseInputConnection public String getSelectedText (int flags) { - Log.d (TAG, "getSelectedText: " + flags); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "getSelectedText: " + flags); return EmacsNative.getSelectedText (windowHandle, flags); } @@ -124,27 +136,44 @@ public class EmacsInputConnection extends BaseInputConnection public String getTextAfterCursor (int length, int flags) { - Log.d (TAG, "getTextAfterCursor: " + length + " " + flags); + String string; + + if (EmacsService.DEBUG_IC) + Log.d (TAG, "getTextAfterCursor: " + length + " " + flags); - return EmacsNative.getTextAfterCursor (windowHandle, length, - flags); + string = EmacsNative.getTextAfterCursor (windowHandle, length, + flags); + + if (EmacsService.DEBUG_IC) + Log.d (TAG, " --> " + string); + + return string; } @Override public String getTextBeforeCursor (int length, int flags) { - Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags); + String string; + + if (EmacsService.DEBUG_IC) + Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags); + + string = EmacsNative.getTextBeforeCursor (windowHandle, length, + flags); + + if (EmacsService.DEBUG_IC) + Log.d (TAG, " --> " + string); - return EmacsNative.getTextBeforeCursor (windowHandle, length, - flags); + return string; } @Override public boolean setComposingText (CharSequence text, int newCursorPosition) { - Log.d (TAG, "setComposingText: " + newCursorPosition); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "setComposingText: " + newCursorPosition); EmacsNative.setComposingText (windowHandle, text.toString (), newCursorPosition); @@ -155,7 +184,8 @@ public class EmacsInputConnection extends BaseInputConnection public boolean setComposingRegion (int start, int end) { - Log.d (TAG, "setComposingRegion: " + start + " " + end); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "setComposingRegion: " + start + " " + end); EmacsNative.setComposingRegion (windowHandle, start, end); return true; @@ -165,7 +195,8 @@ public class EmacsInputConnection extends BaseInputConnection public boolean performEditorAction (int editorAction) { - Log.d (TAG, "performEditorAction: " + editorAction); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "performEditorAction: " + editorAction); EmacsNative.performEditorAction (windowHandle, editorAction); return true; @@ -175,7 +206,8 @@ public class EmacsInputConnection extends BaseInputConnection public ExtractedText getExtractedText (ExtractedTextRequest request, int flags) { - Log.d (TAG, "getExtractedText: " + request + " " + flags); + if (EmacsService.DEBUG_IC) + Log.d (TAG, "getExtractedText: " + request + " " + flags); return EmacsNative.getExtractedText (windowHandle, request, flags); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 855a738a30f..2acb3ead086 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -88,6 +88,8 @@ public class EmacsService extends Service /* Display metrics used by font backends. */ public DisplayMetrics metrics; + public static final boolean DEBUG_IC = false; + @Override public int onStartCommand (Intent intent, int flags, int startId) @@ -612,10 +614,11 @@ public class EmacsService extends Service int newSelectionEnd, int composingRegionStart, int composingRegionEnd) { - Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart - + " " + newSelectionEnd + " " - + composingRegionStart + " " - + composingRegionEnd)); + if (DEBUG_IC) + Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart + + " " + newSelectionEnd + " " + + composingRegionStart + " " + + composingRegionEnd)); window.view.imManager.updateSelection (window.view, newSelectionStart, newSelectionEnd, @@ -626,7 +629,8 @@ public class EmacsService extends Service public void resetIC (EmacsWindow window, int icMode) { - Log.d (TAG, "resetIC: " + window); + if (DEBUG_IC) + Log.d (TAG, "resetIC: " + window); window.view.setICMode (icMode); window.view.imManager.restartInput (window.view); -- cgit v1.2.1 From 2dcce30290dc7782e9de3b4adf59f38b42408c98 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 16 Feb 2023 23:57:01 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Fonts): * doc/emacs/input.texi (On-Screen Keyboards): * doc/lispref/commands.texi (Misc Events): Update documentation. * java/org/gnu/emacs/EmacsInputConnection.java (setSelection): New function. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView) (reconfigureFrontBuffer): Make bitmap references weak references. * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Don't clear surfaceView bitmap. * lisp/comint.el (comint-mode): * lisp/international/fontset.el (script-representative-chars) (setup-default-fontset): Improve detection of CJK fonts. * lisp/isearch.el (set-text-conversion-style): New variable. (isearch-mode, isearch-done): Save and restore the text conversion style. * lisp/minibuffer.el (minibuffer-mode): Set an appropriate text conversion style. * lisp/simple.el (analyze-text-conversion): Run post-self-insert-hook properly. * lisp/subr.el (read-char-from-minibuffer): Disable text conversion when reading character. * src/androidterm.c (show_back_buffer): Don't check that F is not garbaged. (android_update_selection, android_reset_conversion): Use the ephemeral last point and handle text conversion being disabled. * src/buffer.c (syms_of_buffer): Convert old style DEFVAR. * src/keyboard.c (kbd_buffer_get_event): Handle text conversion first. * src/lisp.h: Update prototypes. * src/lread.c (read_filtered_event): Temporarily disable text conversion. * src/sfnt.c (sfnt_decompose_glyph_1, sfnt_decompose_glyph_2): New functions. (sfnt_decompose_glyph, sfnt_decompose_instructed_outline): Refactor contour decomposition to those two functions. (main): Update tests. * src/sfntfont-android.c (system_font_directories): Add empty field. (Fandroid_enumerate_fonts, init_sfntfont_android): Enumerate fonts in a user fonts directory. * src/sfntfont.c (struct sfnt_font_desc): New field `num_glyphs'. (sfnt_enum_font_1): Set num_glyphs and avoid duplicate fonts. (sfntfont_glyph_valid): New function. (sfntfont_lookup_char, sfntfont_list_1): Make sure glyphs found are valid. * src/textconv.c (sync_overlay, really_commit_text) (really_set_composing_text, really_set_composing_region) (really_delete_surrounding_text, really_set_point_and_mark) (handle_pending_conversion_events_1) (handle_pending_conversion_events, conversion_disabled_p) (disable_text_conversion, resume_text_conversion) (Fset_text_conversion_style, syms_of_textconv): Update to respect new options. * src/textconv.h: * src/window.h (GCALIGNED_STRUCT): New field `ephemeral_last_point'. * src/xdisp.c (mark_window_display_accurate_1): Set it. --- java/org/gnu/emacs/EmacsInputConnection.java | 14 +++++++++++++- java/org/gnu/emacs/EmacsSurfaceView.java | 17 ++++++++++------- java/org/gnu/emacs/EmacsView.java | 8 +------- 3 files changed, 24 insertions(+), 15 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 5eb56d5aa71..e2a15894695 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -173,7 +173,8 @@ public class EmacsInputConnection extends BaseInputConnection setComposingText (CharSequence text, int newCursorPosition) { if (EmacsService.DEBUG_IC) - Log.d (TAG, "setComposingText: " + newCursorPosition); + Log.d (TAG, ("setComposingText: " + + text + " ## " + newCursorPosition)); EmacsNative.setComposingText (windowHandle, text.toString (), newCursorPosition); @@ -213,6 +214,17 @@ public class EmacsInputConnection extends BaseInputConnection flags); } + @Override + public boolean + setSelection (int start, int end) + { + if (EmacsService.DEBUG_IC) + Log.d (TAG, "setSelection: " + start + " " + end); + + EmacsNative.setSelection (windowHandle, start, end); + return true; + } + /* Override functions which are not implemented. */ diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 2d80be0881a..62e927094e4 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -28,6 +28,8 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Paint; +import java.lang.ref.WeakReference; + /* This originally extended SurfaceView. However, doing so proved to be too slow, and Android's surface view keeps up to three of its own back buffers, which use too much memory (up to 96 MB for a @@ -39,7 +41,7 @@ public class EmacsSurfaceView extends View private EmacsView view; private Bitmap frontBuffer; private Canvas bitmapCanvas; - private Bitmap bitmap; + private WeakReference bitmap; private Paint bitmapPaint; public @@ -49,10 +51,11 @@ public class EmacsSurfaceView extends View this.view = view; this.bitmapPaint = new Paint (); + this.bitmap = new WeakReference (null); } private void - copyToFrontBuffer (Rect damageRect) + copyToFrontBuffer (Bitmap bitmap, Rect damageRect) { if (damageRect != null) bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect, @@ -73,7 +76,7 @@ public class EmacsSurfaceView extends View bitmapCanvas = null; } - this.bitmap = bitmap; + this.bitmap = new WeakReference (bitmap); /* Next, create the new front buffer if necessary. */ @@ -92,20 +95,20 @@ public class EmacsSurfaceView extends View bitmapCanvas = new Canvas (frontBuffer); /* And copy over the bitmap contents. */ - copyToFrontBuffer (null); + copyToFrontBuffer (bitmap, null); } else if (bitmap != null) /* Just copy over the bitmap contents. */ - copyToFrontBuffer (null); + copyToFrontBuffer (bitmap, null); } public synchronized void setBitmap (Bitmap bitmap, Rect damageRect) { - if (bitmap != this.bitmap) + if (bitmap != this.bitmap.get ()) reconfigureFrontBuffer (bitmap); else if (bitmap != null) - copyToFrontBuffer (damageRect); + copyToFrontBuffer (bitmap, damageRect); if (bitmap != null) { diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 52da6d41f7d..5ea8b0dcc0e 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -186,13 +186,7 @@ public class EmacsView extends ViewGroup /* Explicitly free the old bitmap's memory. */ if (oldBitmap != null) - { - oldBitmap.recycle (); - - /* Make sure to set the view's bitmap to the new bitmap, or - ugly flicker can result. */ - surfaceView.setBitmap (bitmap, null); - } + oldBitmap.recycle (); /* Some Android versions still don't free the bitmap until the next GC. */ -- cgit v1.2.1 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. --- 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 ++++++++- 5 files changed, 44 insertions(+), 13 deletions(-) (limited to 'java') 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); -- cgit v1.2.1 From 60ed861b10fdc34bb9c888a59c6e6f75a27b77b4 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 17 Feb 2023 19:02:23 +0800 Subject: Fix crash on old versions of Android * java/org/gnu/emacs/EmacsService.java (nameKeysym): Implement stub on Android 3.0 and earlier. --- java/org/gnu/emacs/EmacsService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 4d373937ab0..ba6ec485d62 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -470,7 +470,10 @@ public class EmacsService extends Service public String nameKeysym (int keysym) { - return KeyEvent.keyCodeToString (keysym); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) + return KeyEvent.keyCodeToString (keysym); + + return String.valueOf (keysym); } public void -- cgit v1.2.1 From d70bb47aeb586bfa5feb29d6f3759604eb93829a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 17 Feb 2023 21:09:00 +0800 Subject: Update emacsbug and version.el for the Android port * java/Makefile.in (install_temp/assets/version): New generated file. * lisp/loadup.el: Set emacs versions appropriately prior to dumping on Android. * lisp/mail/emacsbug.el (emacs-build-description): Insert Android build fingerprint. * lisp/version.el (emacs-repository-version-android) (emacs-repository-get-version, emacs-repository-get-branch): Implement for Android. * src/androidterm.c (android_set_build_fingerprint): New function. (syms_of_androidterm): New variable `android-build-fingerprint'. --- java/Makefile.in | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 92c03469c69..f732c9211ee 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -192,12 +192,18 @@ ifneq ($(NDK_BUILD_SHARED),) install_temp/lib/$(ANDROID_ABI) endif -install_temp/assets/directory-tree: $(libsrc)/asset-directory-tool install_temp +install_temp/assets/directory-tree: $(libsrc)/asset-directory-tool \ + install_temp install_temp/assets/version $(AM_V_GEN) $(libsrc)/asset-directory-tool install_temp/assets \ install_temp/assets/directory-tree +install_temp/assets/version: install_temp + $(AM_V_GEN) { (git rev-parse HEAD || echo "Unknown") \ + && (git rev-parse --abbrev-ref HEAD \ + || echo "Unknown") } 2> /dev/null > $@ + emacs.apk-in: install_temp install_temp/assets/directory-tree \ - AndroidManifest.xml + install_temp/assets/version AndroidManifest.xml # Package everything. Specifying the assets on this command line is # necessary for AAssetManager_getNextFileName to work on old versions # of Android. Make sure not to generate R.java, as it's already been -- cgit v1.2.1 From a61f9cb77ced0607e4607bc5d82ae06165e6138d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 18 Feb 2023 23:09:30 +0800 Subject: Update Android port * doc/emacs/input.texi (On-Screen Keyboards): Document `touch-screen-always-display'. * doc/lispref/commands.texi (Misc Events): Improve documentation of text conversion events. * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog, display1): Reorder buttons to make more sense. * lisp/elec-pair.el (electric-pair-analyze-conversion): New function. * lisp/simple.el (analyze-text-conversion): Improve integration with electric pair modes. * lisp/term.el (term-mode): Always display the onscreen keyboard. * lisp/touch-screen.el (touch-screen-display-keyboard) (touch-screen-handle-point-up): Respect new options. * src/textconv.c (really_set_composing_text): Stop widenining unnecessarily. (really_delete_surrounding_text): Really delete surrounding text. Give text conversion analyzers the buffer text. (syms_of_textconv): Update doc string. --- java/org/gnu/emacs/EmacsDialog.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index bc0333e99b9..9f9124ce99c 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -166,14 +166,14 @@ public class EmacsDialog implements DialogInterface.OnDismissListener if (size >= 2) { button = buttons.get (1); - dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + dialog.setButton (DialogInterface.BUTTON_NEGATIVE, button.name, button); } if (size >= 3) { button = buttons.get (2); - dialog.setButton (DialogInterface.BUTTON_NEGATIVE, + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, button.name, button); } } @@ -274,10 +274,8 @@ public class EmacsDialog implements DialogInterface.OnDismissListener if (size >= 2) { button = buttons.get (1); - dialog.setButton (DialogInterface.BUTTON_NEUTRAL, - button.name, button); buttonView - = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + = dialog.getButton (DialogInterface.BUTTON_NEGATIVE); buttonView.setEnabled (button.enabled); } @@ -285,7 +283,7 @@ public class EmacsDialog implements DialogInterface.OnDismissListener { button = buttons.get (2); buttonView - = dialog.getButton (DialogInterface.BUTTON_NEGATIVE); + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); buttonView.setEnabled (button.enabled); } } -- cgit v1.2.1 From c8f49c9276d34741bfbe7752dd38391c0b8d782b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 13:17:43 +0800 Subject: Implement `fullscreen' on Android 4.0 and later * doc/emacs/android.texi (Android Windowing): Document what new frame parameters are now supported. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `isFullscreen'. (detachWindow, attachWindow): Sync fullscreen state. (onWindowFocusChanged): Add more logging. (onResume): Restore previous fullscreen state. (syncFullscreen): New function. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow) (setFullscreen): New function. * src/android.c (struct android_emacs_window): Add new method. (android_init_emacs_window): Look up new method. (android_set_fullscreen): New function. * src/androidgui.h: * src/androidterm.c (android_fullscreen_hook): Implement accordingly. --- java/org/gnu/emacs/EmacsActivity.java | 107 +++++++++++++++++++++++++++++++++- java/org/gnu/emacs/EmacsWindow.java | 26 +++++++++ 2 files changed, 130 insertions(+), 3 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 3156a144a0f..7e09e608984 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -26,12 +26,16 @@ import java.util.ArrayList; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import android.os.Build; +import android.os.Bundle; import android.util.Log; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; import android.view.Menu; +import android.view.View; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.widget.FrameLayout.LayoutParams; +import android.widget.FrameLayout; public class EmacsActivity extends Activity implements EmacsWindowAttachmentManager.WindowConsumer @@ -56,6 +60,9 @@ public class EmacsActivity extends Activity /* Whether or not this activity is paused. */ private boolean isPaused; + /* Whether or not this activity is fullscreen. */ + private boolean isFullscreen; + static { focusedActivities = new ArrayList (); @@ -104,6 +111,8 @@ public class EmacsActivity extends Activity public void detachWindow () { + syncFullscreenWith (null); + if (window == null) Log.w (TAG, "detachWindow called, but there is no window"); else @@ -131,6 +140,8 @@ public class EmacsActivity extends Activity throw new IllegalStateException ("trying to attach window when one" + " already exists"); + syncFullscreenWith (child); + /* Record and attach the view. */ window = child; @@ -230,6 +241,9 @@ public class EmacsActivity extends Activity public void onWindowFocusChanged (boolean isFocused) { + Log.d (TAG, ("onWindowFocusChanged: " + + (isFocused ? "YES" : "NO"))); + if (isFocused && !focusedActivities.contains (this)) { focusedActivities.add (this); @@ -257,6 +271,9 @@ public class EmacsActivity extends Activity { isPaused = false; + /* Update the window insets. */ + syncFullscreenWith (window); + EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); super.onResume (); } @@ -279,4 +296,88 @@ public class EmacsActivity extends Activity super.onContextMenuClosed (menu); } + + @SuppressWarnings ("deprecation") + public void + syncFullscreenWith (EmacsWindow emacsWindow) + { + WindowInsetsController controller; + Window window; + int behavior, flags; + View view; + + if (emacsWindow != null) + isFullscreen = emacsWindow.fullscreen; + else + isFullscreen = false; + + /* On Android 11 or later, use the window insets controller to + control whether or not the view is fullscreen. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + { + window = getWindow (); + + /* If there is no attached window, return immediately. */ + if (window == null) + return; + + behavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + controller = window.getInsetsController (); + controller.setSystemBarsBehavior (behavior); + + if (isFullscreen) + controller.hide (WindowInsets.Type.statusBars () + | WindowInsets.Type.navigationBars ()); + else + controller.show (WindowInsets.Type.statusBars () + | WindowInsets.Type.navigationBars ()); + + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + /* On Android 4.1 or later, use `setSystemUiVisibility'. */ + + window = getWindow (); + + if (window == null) + return; + + view = window.getDecorView (); + + if (isFullscreen) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + /* This flag means that Emacs will be full screen, but + the system will cancel the full screen state upon + switching to another program. */ + view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_FULLSCREEN); + else + { + /* These flags means that Emacs will be full screen as + long as the state flag is set. */ + flags = 0; + flags |= View.SYSTEM_UI_FLAG_FULLSCREEN; + flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + view.setSystemUiVisibility (flags); + } + } + else + view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + @Override + public void + onAttachedToWindow () + { + super.onAttachedToWindow (); + + /* Update the window insets. */ + syncFullscreenWith (window); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 0eca35cec61..90fc4c44198 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -128,6 +128,9 @@ public class EmacsWindow extends EmacsHandleObject events. */ public LinkedHashMap eventStrings; + /* Whether or not this window is fullscreen. */ + public boolean fullscreen; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -1179,4 +1182,27 @@ public class EmacsWindow extends EmacsHandleObject return any; } + + public void + setFullscreen (final boolean isFullscreen) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsActivity activity; + Object tem; + + fullscreen = isFullscreen; + tem = getAttachedConsumer (); + + if (tem != null) + { + activity = (EmacsActivity) getAttachedConsumer (); + activity.syncFullscreenWith (EmacsWindow.this); + } + } + }); + } }; -- cgit v1.2.1 From efc46330aa1e3c433246b8a008ffc7f2675369c2 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 15:29:46 +0800 Subject: Allow opening more files in emacsclient on Android * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity) (checkReadableOrCopy): New function. (onCreate): If the file specified is not readable from C, read it into a temporary file and ask Emacs to open that. --- java/org/gnu/emacs/EmacsOpenActivity.java | 64 ++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index baf31039ecd..c8501d91025 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -58,8 +58,10 @@ import android.os.Bundle; import android.os.ParcelFileDescriptor; import java.io.File; -import java.io.FileReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -176,6 +178,50 @@ public class EmacsOpenActivity extends Activity dialog.show (); } + /* Check that the specified FILE is readable. If it is not, then + copy the file in FD to a location in the system cache + directory and return the name of that file. */ + + private String + checkReadableOrCopy (String file, ParcelFileDescriptor fd) + throws IOException, FileNotFoundException + { + File inFile; + FileOutputStream outStream; + InputStream stream; + byte buffer[]; + int read; + + inFile = new File (file); + + if (inFile.setReadable (true)) + return file; + + /* inFile is now the file being written to. */ + inFile = new File (getCacheDir (), inFile.getName ()); + buffer = new byte[4098]; + outStream = new FileOutputStream (inFile); + stream = new FileInputStream (fd.getFileDescriptor ()); + + try + { + while ((read = stream.read (buffer)) >= 0) + outStream.write (buffer, 0, read); + } + finally + { + /* Note that this does not close FD. + + Keep in mind that execution is transferred to ``finally'' + even if an exception happens inside the while loop + above. */ + stream.close (); + outStream.close (); + } + + return inFile.getCanonicalPath (); + } + /* Finish this activity in response to emacsclient having successfully opened a file. @@ -340,17 +386,19 @@ public class EmacsOpenActivity extends Activity opening the file and doing readlink on its file descriptor in /proc/self/fd. */ resolver = getContentResolver (); + fd = null; try { fd = resolver.openFileDescriptor (uri, "r"); names = EmacsNative.getProcName (fd.getFd ()); - fd.close (); /* What is the right encoding here? */ if (names != null) fileName = new String (names, "UTF-8"); + + fileName = checkReadableOrCopy (fileName, fd); } catch (FileNotFoundException exception) { @@ -360,6 +408,18 @@ public class EmacsOpenActivity extends Activity { /* Do nothing. */ } + + if (fd != null) + { + try + { + fd.close (); + } + catch (IOException exception) + { + /* Do nothing. */ + } + } } if (fileName == null) -- cgit v1.2.1 From 0998ab3ade7caf2b163cd99a44b8a3f17be763d5 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 19:56:51 +0800 Subject: Report both sides of the region to the input method upon setup * java/org/gnu/emacs/EmacsNative.java (getSelection): Return array of ints. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Adjust accordingly. * src/androidterm.c (struct android_get_selection_context): New field `mark'. (android_get_selection): Set the mark field as appropriate. (getSelection): Adjust accordingly. --- java/org/gnu/emacs/EmacsNative.java | 2 +- java/org/gnu/emacs/EmacsView.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index aaac9446510..ef1a0e75a5c 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -205,7 +205,7 @@ public class EmacsNative /* Return the current value of the selection, or -1 upon failure. */ - public static native int getSelection (short window); + public static native int[] getSelection (short window); static { diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 5ea8b0dcc0e..89f526853b2 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -555,7 +555,8 @@ public class EmacsView extends ViewGroup public InputConnection onCreateInputConnection (EditorInfo info) { - int selection, mode; + int mode; + int[] selection; /* Figure out what kind of IME behavior Emacs wants. */ mode = getICMode (); @@ -575,7 +576,7 @@ public class EmacsView extends ViewGroup /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't initialize the input connection. */ - if (selection == -1 || mode == EmacsService.IC_MODE_NULL) + if (mode == EmacsService.IC_MODE_NULL || selection == null) { info.inputType = InputType.TYPE_NULL; return null; @@ -585,8 +586,8 @@ public class EmacsView extends ViewGroup info.imeOptions |= EditorInfo.IME_ACTION_DONE; /* Set the initial selection fields. */ - info.initialSelStart = selection; - info.initialSelEnd = selection; + info.initialSelStart = selection[0]; + info.initialSelEnd = selection[1]; /* Create the input connection if necessary. */ -- cgit v1.2.1 From 18f723faa85b78dff7c8f59257640feededc44d1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 20:45:32 +0800 Subject: Fix parallel compilation of Android port * cross/Makefile.in ($(top_builddir)/lib/libgnu.a): * java/Makefile.in (CROSS_LIBS): Explicitly depend on gnulib to prevent it from being built at the same time from different jobs. --- java/Makefile.in | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index f732c9211ee..91feb53e96b 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -121,17 +121,25 @@ CROSS_BINS = ../cross/src/android-emacs ../cross/lib-src/ctags \ # Libraries to cross-compile. CROSS_LIBS = ../cross/src/libemacs.so +# Make sure gnulib is built first! +# If not, then the recursive invocations of make below will try to +# build gnulib at the same time. +CROSS_ARCHIVES = ../cross/lib/libgnu.a + # Third party libraries to compile. include $(top_builddir)/cross/ndk-build/ndk-build.mk -.PHONY: $(CROSS_BINS) $(CROSS_LIBS) +.PHONY: $(CROSS_BINS) $(CROSS_LIBS) $(CROSS_ARCHIVES) -../cross/src/android-emacs ../cross/src/libemacs.so: - make -C ../cross src/$(notdir $@) +../cross/src/android-emacs ../cross/src/libemacs.so: $(CROSS_ARCHIVES) + $(MAKE) -C ../cross src/$(notdir $@) ../cross/lib-src/hexl ../cross/lib-src/movemail \ -../cross/lib-src/ctags ../cross/lib-src/ebrowse &: - make -C ../cross lib-src/$(notdir $@) +../cross/lib-src/ctags ../cross/lib-src/ebrowse &: $(CROSS_ARCHIVES) + $(MAKE) -C ../cross lib-src/$(notdir $@) + +../cross/lib/libgnu.a: + $(MAKE) -C ../cross lib/libgnu.a # This is needed to generate the ``.directory-tree'' file used by the # Android emulations of readdir and faccessat. -- cgit v1.2.1 From f3196052070999ef9f9c822a676443b14df6a0e6 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 21:25:37 +0800 Subject: More parallel build fixes * cross/Makefile.in: (.PHONY): * java/Makefile.in: (.PHONY): * src/Makefile.in: (libemacs.so): Avoid calling ndk-build from two places at once. Build android-emacs separately from libemacs.so. --- java/Makefile.in | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 91feb53e96b..52e0a7a75f1 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -149,10 +149,11 @@ $(libsrc)/asset-directory-tool: # install_tmp is a directory used to generate emacs.apk-in. # That is then packaged into $(APK_NAME). +# There is no need to depend on NDK_BUILD_SHARED as libemacs.so +# does already. .PHONY: install_temp install_temp/assets/directory-tree -install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(NDK_BUILD_SHARED) \ - $(RESOURCE_FILES) +install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(RESOURCE_FILES) $(AM_V_GEN) # Make the working directory for this stuff $(AM_V_SILENT) rm -rf install_temp -- cgit v1.2.1 From 585ee91b21fb6f1889226b5861333410275dc017 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 21:52:21 +0800 Subject: More fixes to parallel Make * cross/ndk-build/ndk-build.mk.in (NDK_BUILD_MODULES) (NDK_BUILD_SHARED, NDK_BUILD_STATIC): Define group rule to build all files so that they are built within one make process. * java/Makefile.in: Reorganize cross compilation and make sure there is only one make subprocess for each subdirectory of cross. --- java/Makefile.in | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 52e0a7a75f1..482419f07fa 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -113,33 +113,43 @@ APK_NAME := emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk all: $(APK_NAME) # Binaries to cross-compile. -CROSS_BINS = ../cross/src/android-emacs ../cross/lib-src/ctags \ - ../cross/lib-src/hexl ../cross/lib-src/movemail \ - ../cross/lib-src/ebrowse ../cross/lib-src/emacsclient \ - ../cross/lib-src/etags +CROSS_SRC_BINS = $(top_builddir)/cross/src/android-emacs +CROSS_LIBSRC_BINS = $(top_builddir)/cross/lib-src/ctags \ + $(top_builddir)/cross/lib-src/hexl \ + $(top_builddir)/cross/lib-src/movemail \ + $(top_builddir)/cross/lib-src/ebrowse \ + $(top_builddir)/cross/lib-src/emacsclient \ + $(top_builddir)/cross/lib-src/etags +CROSS_BINS = $(CROSS_SRC_BINS) $(CROSS_LIBSRC_BINS) # Libraries to cross-compile. -CROSS_LIBS = ../cross/src/libemacs.so +CROSS_LIBS = $(top_builddir)/cross/src/libemacs.so # Make sure gnulib is built first! # If not, then the recursive invocations of make below will try to # build gnulib at the same time. -CROSS_ARCHIVES = ../cross/lib/libgnu.a +CROSS_ARCHIVES = $(top_builddir)/cross/lib/libgnu.a # Third party libraries to compile. include $(top_builddir)/cross/ndk-build/ndk-build.mk .PHONY: $(CROSS_BINS) $(CROSS_LIBS) $(CROSS_ARCHIVES) -../cross/src/android-emacs ../cross/src/libemacs.so: $(CROSS_ARCHIVES) - $(MAKE) -C ../cross src/$(notdir $@) - -../cross/lib-src/hexl ../cross/lib-src/movemail \ -../cross/lib-src/ctags ../cross/lib-src/ebrowse &: $(CROSS_ARCHIVES) - $(MAKE) -C ../cross lib-src/$(notdir $@) - -../cross/lib/libgnu.a: - $(MAKE) -C ../cross lib/libgnu.a +# There should only be a single invocation of $(MAKE) -C +# $(top_srcdir)/cross for each directory under $(top_srcdir)/cross. +$(CROSS_SRC_BINS) $(CROSS_LIBS) &: $(CROSS_ARCHIVES) + $(MAKE) -C $(top_builddir)/cross $(foreach file, \ + $(CROSS_SRC_BINS) \ + $(CROSS_LIBS), \ + src/$(notdir $(file))) + +$(CROSS_LIBSRC_BINS) &: $(CROSS_ARCHIVES) + $(MAKE) -C $(top_builddir)/cross $(foreach file, \ + $(CROSS_LIBSRC_BINS), \ + lib-src/$(notdir $(file))) + +$(CROSS_ARCHIVES): + $(MAKE) -C $(top_builddir)/cross lib/libgnu.a # This is needed to generate the ``.directory-tree'' file used by the # Android emulations of readdir and faccessat. -- cgit v1.2.1 From b91396bace7e09ba1dc0430ce5e2b5b3000fe77a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 20 Feb 2023 10:50:04 +0800 Subject: Improve reliability of Java code rebuilds * java/Makefile.in ($(CLASS_FILES)): Depend on the Java compiler's internal dependency tracking. --- java/Makefile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 482419f07fa..af44d551701 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -257,8 +257,8 @@ $(RESOURCE_FILE): $(RESOURCE_FILES) $(CLASS_FILES): $(RESOURCE_FILE) .SUFFIXES: .java .class -.java.class &: - $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< +$(CLASS_FILES) &: $(JAVA_FILES) + $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $(JAVA_FILES) # N.B. that find must be called all over again in case javac generated # nested classes. -- cgit v1.2.1 From 57c19f477fddb542fa40747aeb7060fa9756756f Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 20 Feb 2023 22:14:29 +0800 Subject: Update Android port * INSTALL.android: Explain where to get tree-sitter. * configure.ac: Add support for dynamic modules and tree-sitter. * doc/emacs/android.texi (Android Windowing): * java/org/gnu/emacs/EmacsSdk11Clipboard.java (EmacsSdk11Clipboard, ownsClipboard): Improve clipboard handling and documentation. --- java/org/gnu/emacs/EmacsSdk11Clipboard.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java index 2df2015c9c1..ea35a463299 100644 --- a/java/org/gnu/emacs/EmacsSdk11Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -25,6 +25,8 @@ import android.content.ClipData; import android.util.Log; +import android.os.Build; + import java.io.UnsupportedEncodingException; /* This class implements EmacsClipboard for Android 3.0 and later @@ -43,7 +45,12 @@ public class EmacsSdk11Clipboard extends EmacsClipboard EmacsSdk11Clipboard () { manager = EmacsService.SERVICE.getClipboardManager (); - manager.addPrimaryClipChangedListener (this); + + /* The system forbids Emacs from reading clipboard data in the + background under Android 10 or later. */ + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) + manager.addPrimaryClipChangedListener (this); } @Override @@ -105,6 +112,9 @@ public class EmacsSdk11Clipboard extends EmacsClipboard public synchronized int ownsClipboard () { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + return -1; + return ownsClipboard ? 1 : 0; } -- cgit v1.2.1 From 7aa4ffddd842e495d1ae388afff12075317ecb07 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 21 Feb 2023 15:28:06 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Startup): Document `content' special directory. * java/debug.sh (is_root): Improve /bin/tee detection. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function `dup'. * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity) (checkReadableOrCopy, onCreate): Create content directory names when the file is not readable. * java/org/gnu/emacs/EmacsService.java (EmacsService) (openContentUri, checkContentUri): New functions. * src/android.c (struct android_emacs_service): New methods. (android_content_name_p, android_get_content_name) (android_check_content_access): New function. (android_fstatat, android_open): Implement opening content URIs. (dup): Export to Java. (android_init_emacs_service): Initialize new methods. (android_faccessat): Implement content file names. --- java/debug.sh | 2 +- java/org/gnu/emacs/EmacsNative.java | 4 + java/org/gnu/emacs/EmacsOpenActivity.java | 42 +++++++++-- java/org/gnu/emacs/EmacsService.java | 119 ++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 7 deletions(-) (limited to 'java') diff --git a/java/debug.sh b/java/debug.sh index 30e5a94eee5..f07bb98ed7d 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -281,7 +281,7 @@ else # Upload the specified gdbserver binary to the device. adb -s $device push "$gdbserver" "$gdbserver_bin" - if (adb -s $device pull /system/bin/tee /dev/null &> /dev/null); then + if adb -s $device shell ls /system/bin/tee; then # Copy it to the user directory. adb -s $device shell "$gdbserver_cat" adb -s $device shell "run-as $package chmod +x gdbserver" diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index ef1a0e75a5c..f0917a68120 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -31,6 +31,10 @@ public class EmacsNative initialization. */ private static final String[] libraryDeps; + + /* Like `dup' in C. */ + public static native int dup (int fd); + /* Obtain the fingerprint of this build of Emacs. The fingerprint can be used to determine the dump file name. */ public static native String getFingerprint (); diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index c8501d91025..87ce454a816 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -57,6 +57,8 @@ import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.util.Log; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -69,6 +71,8 @@ import java.io.UnsupportedEncodingException; public class EmacsOpenActivity extends Activity implements DialogInterface.OnClickListener { + private static final String TAG = "EmacsOpenActivity"; + private class EmacsClientThread extends Thread { private ProcessBuilder builder; @@ -178,12 +182,16 @@ public class EmacsOpenActivity extends Activity dialog.show (); } - /* Check that the specified FILE is readable. If it is not, then - copy the file in FD to a location in the system cache - directory and return the name of that file. */ + /* Check that the specified FILE is readable. If Android 4.4 or + later is being used, return URI formatted into a `/content/' file + name. + + If it is not, then copy the file in FD to a location in the + system cache directory and return the name of that file. */ private String - checkReadableOrCopy (String file, ParcelFileDescriptor fd) + checkReadableOrCopy (String file, ParcelFileDescriptor fd, + Uri uri) throws IOException, FileNotFoundException { File inFile; @@ -191,12 +199,34 @@ public class EmacsOpenActivity extends Activity InputStream stream; byte buffer[]; int read; + String content; + + Log.d (TAG, "checkReadableOrCopy: " + file); inFile = new File (file); - if (inFile.setReadable (true)) + if (inFile.canRead ()) return file; + Log.d (TAG, "checkReadableOrCopy: NO"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + { + content = "/content/" + uri.getEncodedAuthority (); + + for (String segment : uri.getPathSegments ()) + content += "/" + Uri.encode (segment); + + /* Append the URI query. */ + + if (uri.getEncodedQuery () != null) + content += "?" + uri.getEncodedQuery (); + + Log.d (TAG, "checkReadableOrCopy: " + content); + + return content; + } + /* inFile is now the file being written to. */ inFile = new File (getCacheDir (), inFile.getName ()); buffer = new byte[4098]; @@ -398,7 +428,7 @@ public class EmacsOpenActivity extends Activity if (names != null) fileName = new String (names, "UTF-8"); - fileName = checkReadableOrCopy (fileName, fd); + fileName = checkReadableOrCopy (fileName, fd, uri); } catch (FileNotFoundException exception) { diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index ba6ec485d62..c9701ff2990 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -19,7 +19,10 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UnsupportedEncodingException; + import java.util.List; import java.util.ArrayList; @@ -41,22 +44,31 @@ import android.app.Service; import android.content.ClipboardManager; import android.content.Context; +import android.content.ContentResolver; 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.database.Cursor; +import android.database.MatrixCursor; + + import android.net.Uri; import android.os.Build; import android.os.Looper; import android.os.IBinder; import android.os.Handler; +import android.os.ParcelFileDescriptor; import android.os.Vibrator; import android.os.VibratorManager; import android.os.VibrationEffect; +import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Document; + import android.util.Log; import android.util.DisplayMetrics; @@ -79,6 +91,7 @@ public class EmacsService extends Service private EmacsThread thread; private Handler handler; + private ContentResolver resolver; /* Keep this in synch with androidgui.h. */ public static final int IC_MODE_NULL = 0; @@ -193,6 +206,7 @@ public class EmacsService extends Service metrics = getResources ().getDisplayMetrics (); pixelDensityX = metrics.xdpi; pixelDensityY = metrics.ydpi; + resolver = getContentResolver (); try { @@ -643,4 +657,109 @@ public class EmacsService extends Service window.view.setICMode (icMode); window.view.imManager.restartInput (window.view); } + + /* Open a content URI described by the bytes BYTES, a non-terminated + string; make it writable if WRITABLE, and readable if READABLE. + Truncate the file if TRUNCATE. + + Value is the resulting file descriptor or -1 upon failure. */ + + public int + openContentUri (byte[] bytes, boolean writable, boolean readable, + boolean truncate) + { + String name, mode; + ParcelFileDescriptor fd; + int i; + + /* Figure out the file access mode. */ + + mode = ""; + + if (readable) + mode += "r"; + + if (writable) + mode += "w"; + + if (truncate) + mode += "t"; + + /* Try to open an associated ParcelFileDescriptor. */ + + try + { + /* The usual file name encoding question rears its ugly head + again. */ + name = new String (bytes, "UTF-8"); + Log.d (TAG, "openContentUri: " + Uri.parse (name)); + + fd = resolver.openFileDescriptor (Uri.parse (name), mode); + + /* Use detachFd on newer versions of Android or plain old + dup. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) + { + i = fd.detachFd (); + fd.close (); + + return i; + } + else + { + i = EmacsNative.dup (fd.getFd ()); + fd.close (); + + return i; + } + } + catch (Exception exception) + { + return -1; + } + } + + public boolean + checkContentUri (byte[] string, boolean readable, boolean writable) + { + String mode, name; + ParcelFileDescriptor fd; + + /* Decode this into a URI. */ + + try + { + /* The usual file name encoding question rears its ugly head + again. */ + name = new String (string, "UTF-8"); + Log.d (TAG, "checkContentUri: " + Uri.parse (name)); + } + catch (UnsupportedEncodingException exception) + { + name = null; + throw new RuntimeException (exception); + } + + mode = "r"; + + if (writable) + mode += "w"; + + try + { + fd = resolver.openFileDescriptor (Uri.parse (name), mode); + fd.close (); + + Log.d (TAG, "checkContentUri: YES"); + + return true; + } + catch (Exception exception) + { + Log.d (TAG, "checkContentUri: NO"); + Log.d (TAG, exception.toString ()); + return false; + } + } }; -- cgit v1.2.1 From 77feba74564c4d58b472b82fec0137091bb5c7e1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 21 Feb 2023 21:07:57 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu) (addSubmenu, inflateMenuItems): Handle tooltips correctly. * src/android.c (android_scan_directory_tree): Fix limit generation for root directory. * src/androidmenu.c (android_init_emacs_context_menu) (android_menu_show): Implement menu item help text on Android 8.0 and later. --- java/org/gnu/emacs/EmacsContextMenu.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 184c611bf9d..6b3ae0c6de9 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Build; import android.view.Menu; import android.view.MenuItem; @@ -54,7 +55,7 @@ public class EmacsContextMenu private class Item implements MenuItem.OnMenuItemClickListener { public int itemID; - public String itemName; + public String itemName, tooltip; public EmacsContextMenu subMenu; public boolean isEnabled, isCheckable, isChecked; @@ -147,7 +148,7 @@ public class EmacsContextMenu item name. */ public EmacsContextMenu - addSubmenu (String itemName, String title) + addSubmenu (String itemName, String title, String tooltip) { EmacsContextMenu submenu; Item item; @@ -155,6 +156,7 @@ public class EmacsContextMenu item = new Item (); item.itemID = 0; item.itemName = itemName; + item.tooltip = tooltip; item.subMenu = createContextMenu (title); item.subMenu.parent = this; @@ -214,6 +216,13 @@ public class EmacsContextMenu if (item.isChecked) menuItem.setChecked (true); + + /* If the tooltip text is set and the system is new enough + to support menu item tooltips, set it on the item. */ + + if (item.tooltip != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + menuItem.setTooltipText (item.tooltip); } } } -- cgit v1.2.1 From 1e6f957c0dbb7e4a5e04c20fcb797be1d98df3d8 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 22 Feb 2023 14:59:27 +0800 Subject: Update Android port * doc/emacs/input.texi (On-Screen Keyboards): Document changes to text conversion. * java/org/gnu/emacs/EmacsInputConnection.java (getExtractedText) (EmacsInputConnection): * src/keyboard.c (read_key_sequence): Disable text conversion after reading prefix key. * src/textconv.c (get_extracted_text): Fix returned value when request length is zero. --- java/org/gnu/emacs/EmacsInputConnection.java | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index e2a15894695..834c2226c82 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -207,11 +207,19 @@ public class EmacsInputConnection extends BaseInputConnection public ExtractedText getExtractedText (ExtractedTextRequest request, int flags) { + ExtractedText text; + if (EmacsService.DEBUG_IC) Log.d (TAG, "getExtractedText: " + request + " " + flags); - return EmacsNative.getExtractedText (windowHandle, request, + text = EmacsNative.getExtractedText (windowHandle, request, flags); + + if (EmacsService.DEBUG_IC) + Log.d (TAG, "getExtractedText: " + text.text + " @" + + text.startOffset + ":" + text.selectionStart); + + return text; } @Override @@ -225,6 +233,16 @@ public class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + sendKeyEvent (KeyEvent key) + { + if (EmacsService.DEBUG_IC) + Log.d (TAG, "sendKeyEvent: " + key); + + return super.sendKeyEvent (key); + } + /* Override functions which are not implemented. */ -- cgit v1.2.1 From ea74f3c0678d0729a8d6307e35c2d228f665afa3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 24 Feb 2023 22:25:48 +0800 Subject: Improve Android configury * configure.ac (JAVA_PUSH_LINT): New macro. (JAVAFLAGS): New variable. Check for various lint flags and macros and enable them. * java/Makefile.in (ANDROID_ABI): * java/org/gnu/emacs/EmacsSdk7FontDriver.java: Remove compiler warning. --- java/Makefile.in | 5 +---- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index af44d551701..7ba05f6c9a3 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -40,10 +40,7 @@ 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 \ - $(WARN_JAVAFLAGS) +JAVAFLAGS = @JAVAFLAGS@ # Android 4.3 and earlier require Emacs to be signed with a different # digital signature algorithm. diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index a964cadb74c..ba92d4cef49 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -325,7 +325,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver list.add (new Sdk7FontEntity (typefaceList[i])); } - return (FontEntity[]) list.toArray (new FontEntity[0]); + return list.toArray (new FontEntity[0]); } @Override -- cgit v1.2.1 From 8e4c5db193dc7baee5846520fe8b63d8bea99148 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 25 Feb 2023 19:11:07 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Startup, Android File System) (Android Environment, Android Windowing, Android Troubleshooting): Improve documentation; fix typos. * doc/lispref/commands.texi (Misc Events): Likewise. * java/org/gnu/emacs/EmacsService.java (queryBattery): New function. * lisp/battery.el (battery-status-function): Set appropriately for Android. (battery-android): New function. * src/android.c (struct android_emacs_service): New method `query_battery'. (android_check_content_access): Improve exception checking. (android_init_emacs_service): Look up new method. (android_destroy_handle, android_create_window) (android_init_android_rect_class, android_init_emacs_gc_class) (android_set_clip_rectangles) (android_create_pixmap_from_bitmap_data, android_fill_polygon) (android_get_image, android_put_image, android_bell) (android_set_input_focus, android_raise_window) (android_lower_window, android_query_tree, android_get_geometry) (android_translate_coordinates, android_wc_lookup_string) (android_damage_window, android_build_string) (android_build_jstring, android_exception_check_1) (android_exception_check_2): New functions. (android_browse_url): Improve exception handling. Always use android_exception_check and don't leak local refs. (android_query_battery): New function. * src/android.h (struct android_battery_state): New struct. * src/androidfns.c (Fandroid_query_battery, syms_of_androidfns): New function. * src/androidfont.c (androidfont_from_lisp, DO_SYMBOL_FIELD) (DO_CARDINAL_FIELD, androidfont_list, androidfont_match) (androidfont_draw, androidfont_open_font) (androidfont_close_font): * src/androidselect.c (Fandroid_set_clipboard) (Fandroid_get_clipboard): * src/sfnt.c (sfnt_map_glyf_table): * src/sfntfont.c (sfntfont_free_outline_cache) (sfntfont_free_raster_cache, sfntfont_close): Allow font close functions to be called twice. --- java/org/gnu/emacs/EmacsService.java | 57 +++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index c9701ff2990..48c7c743014 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -54,9 +54,9 @@ import android.content.res.AssetManager; import android.database.Cursor; import android.database.MatrixCursor; - import android.net.Uri; +import android.os.BatteryManager; import android.os.Build; import android.os.Looper; import android.os.IBinder; @@ -762,4 +762,59 @@ public class EmacsService extends Service return false; } } + + /* Return the status of the battery. See struct + android_battery_status for the order of the elements + returned. + + Value may be null upon failure. */ + + public long[] + queryBattery () + { + Object tem; + BatteryManager manager; + long capacity, chargeCounter, currentAvg, currentNow; + long status, remaining; + int prop; + + /* Android 4.4 or earlier require applications to listen to + changes to the battery instead of querying for its status. */ + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + return null; + + tem = getSystemService (Context.BATTERY_SERVICE); + manager = (BatteryManager) tem; + remaining = -1; + + prop = BatteryManager.BATTERY_PROPERTY_CAPACITY; + capacity = manager.getLongProperty (prop); + prop = BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER; + chargeCounter = manager.getLongProperty (prop); + prop = BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE; + currentAvg = manager.getLongProperty (prop); + prop = BatteryManager.BATTERY_PROPERTY_CURRENT_NOW; + currentNow = manager.getLongProperty (prop); + + /* Return the battery status. N.B. that Android 7.1 and earlier + only return ``charging'' or ``discharging''. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + status = manager.getIntProperty (BatteryManager.BATTERY_PROPERTY_STATUS); + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + status = (manager.isCharging () + ? BatteryManager.BATTERY_STATUS_CHARGING + : BatteryManager.BATTERY_STATUS_DISCHARGING); + else + status = (currentNow > 0 + ? BatteryManager.BATTERY_STATUS_CHARGING + : BatteryManager.BATTERY_STATUS_DISCHARGING); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + remaining = manager.computeChargeTimeRemaining (); + + return new long[] { capacity, chargeCounter, currentAvg, + currentNow, remaining, status, }; + } }; -- cgit v1.2.1 From df29bb71fc47b5e24fa971b668e4fa09dd6d244d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 25 Feb 2023 20:11:48 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsNoninteractive.java (main): Port to Android 2.2. * src/android-asset.h (AAsset_openFileDescriptor): Delete stub function. * src/android.c (android_check_compressed_file): Delete function. (android_open): Stop trying to find compressed files or to use the system provided file descriptor. Explain why. --- java/org/gnu/emacs/EmacsNoninteractive.java | 118 ++++++++++++++++++---------- 1 file changed, 76 insertions(+), 42 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index 4da82f2f894..30901edb75f 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -87,56 +87,23 @@ public class EmacsNoninteractive /* Create and attach the activity thread. */ activityThread = method.invoke (null); + context = 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_MR1) + try { - method - = activityThreadClass.getMethod ("getPackageInfo", - String.class, - int.class); - loadedApk = method.invoke (activityThread, "org.gnu.emacs", - 0); + loadedApkClass = Class.forName ("android.app.LoadedApk"); } - else + catch (ClassNotFoundException exception) { - 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"); + /* Android 2.2 has no LoadedApk class, but fortunately it + does not need to be used, since contexts can be + directly created. */ - /* Now, get a context. */ - contextImplClass = Class.forName ("android.app.ContextImpl"); + loadedApkClass = null; + contextImplClass = Class.forName ("android.app.ContextImpl"); - try - { - method = contextImplClass.getDeclaredMethod ("createAppContext", - activityThreadClass, - loadedApkClass); - method.setAccessible (true); - context = (Context) method.invoke (null, activityThread, loadedApk); - } - catch (NoSuchMethodException exception) - { - /* Older Android versions don't have createAppContext, but - instead require creating a ContextImpl, and then - calling createPackageContext. */ method = activityThreadClass.getDeclaredMethod ("getSystemContext"); context = (Context) method.invoke (activityThread); method = contextImplClass.getDeclaredMethod ("createPackageContext", @@ -147,6 +114,73 @@ public class EmacsNoninteractive 0); } + /* If the context has not already been created, then do what + is appropriate for newer versions of Android. */ + + if (context == null) + { + /* 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_MR1) + { + 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"); + + try + { + method + = contextImplClass.getDeclaredMethod ("createAppContext", + activityThreadClass, + loadedApkClass); + method.setAccessible (true); + context = (Context) method.invoke (null, activityThread, + loadedApk); + } + catch (NoSuchMethodException exception) + { + /* Older Android versions don't have createAppContext, but + instead require creating a ContextImpl, and then + calling createPackageContext. */ + method + = activityThreadClass.getDeclaredMethod ("getSystemContext"); + context = (Context) method.invoke (activityThread); + method + = contextImplClass.getDeclaredMethod ("createPackageContext", + String.class, + int.class); + method.setAccessible (true); + context = (Context) method.invoke (context, "org.gnu.emacs", + 0); + } + } + /* Don't actually start the looper or anything. Instead, obtain an AssetManager. */ assets = context.getAssets (); -- cgit v1.2.1 From 8fa86cc7cd708fae8657b4e977711132999054bc Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 25 Feb 2023 21:52:11 +0800 Subject: Update Android port * java/debug.sh (is_root): Fix tee detection again for old systems which don't return exit codes from adb shell. * src/android.c (android_run_select_thread, NATIVE_NAME, JNICALL): * src/android.h (NATIVE_NAME): * src/androidterm.c (JNICALL, NATIVE_NAME): Apply stack alignment to all JNICALL functions. --- java/debug.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java') diff --git a/java/debug.sh b/java/debug.sh index f07bb98ed7d..83690e0b536 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -281,7 +281,7 @@ else # Upload the specified gdbserver binary to the device. adb -s $device push "$gdbserver" "$gdbserver_bin" - if adb -s $device shell ls /system/bin/tee; then + if (adb -s $device shell ls /system/bin | grep -G tee); then # Copy it to the user directory. adb -s $device shell "$gdbserver_cat" adb -s $device shell "run-as $package chmod +x gdbserver" -- cgit v1.2.1 From 39ddf1855bbebc5d31b544e6308a4ae49f4925b3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 26 Feb 2023 10:33:41 +0800 Subject: Update Android port * doc/lispref/commands.texi (Misc Events): Update documentation. * java/org/gnu/emacs/EmacsService.java (EmacsService) (onStartCommand): Improve notification message. * src/android.c (android_hack_asset_fd): Detect if ashmem is available dynamically. (android_detect_ashmem): New function. * src/textconv.c (record_buffer_change): Use markers to represent BEG and END instead. (syms_of_textconv): Update doc string. --- java/org/gnu/emacs/EmacsService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 48c7c743014..7f4f75b5147 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -101,6 +101,8 @@ public class EmacsService extends Service /* Display metrics used by font backends. */ public DisplayMetrics metrics; + /* Flag that says whether or not to print verbose debugging + information. */ public static final boolean DEBUG_IC = false; @Override @@ -117,8 +119,10 @@ public class EmacsService extends Service { tem = getSystemService (Context.NOTIFICATION_SERVICE); manager = (NotificationManager) tem; - infoBlurb = ("See (emacs)Android Environment for more" - + " details about this notification."); + infoBlurb = ("This notification is displayed to keep Emacs" + + " running while it is in the background. You" + + " may disable if you want;" + + " see (emacs)Android Environment."); channel = new NotificationChannel ("emacs", "Emacs persistent notification", NotificationManager.IMPORTANCE_DEFAULT); -- cgit v1.2.1 From 15bcb446be2f2f5b85a1b9585ec3abaabcbf04d9 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 1 Mar 2023 12:00:46 +0800 Subject: Update Android port * java/Makefile.in (ETAGS, clean): New rules to generate tags. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication): * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea): * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog)::(dialog. Then): * java/org/gnu/emacs/EmacsDocumentsProvider.java (EmacsDocumentsProvider): * 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/EmacsFillPolygon.java (EmacsFillPolygon): * java/org/gnu/emacs/EmacsFillRectangle.java (EmacsFillRectangle): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsNoninteractive.java (EmacsNoninteractive): * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): * java/org/gnu/emacs/EmacsPreferencesActivity.java (EmacsPreferencesActivity): * java/org/gnu/emacs/EmacsSdk11Clipboard.java (EmacsSdk11Clipboard): * java/org/gnu/emacs/EmacsSdk23FontDriver.java (EmacsSdk23FontDriver): * java/org/gnu/emacs/EmacsSdk8Clipboard.java (EmacsSdk8Clipboard): * java/org/gnu/emacs/EmacsService.java (EmacsService): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView) (buffers): * java/org/gnu/emacs/EmacsView.java (EmacsView, ViewGroup): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, drawables): * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager): Make classes final where appropriate. --- java/Makefile.in | 16 +++++++++++++++- java/org/gnu/emacs/EmacsActivity.java | 22 +++++++++++----------- java/org/gnu/emacs/EmacsApplication.java | 2 +- java/org/gnu/emacs/EmacsContextMenu.java | 2 +- java/org/gnu/emacs/EmacsCopyArea.java | 2 +- java/org/gnu/emacs/EmacsDialog.java | 2 +- java/org/gnu/emacs/EmacsDocumentsProvider.java | 2 +- java/org/gnu/emacs/EmacsDrawLine.java | 2 +- java/org/gnu/emacs/EmacsDrawPoint.java | 2 +- java/org/gnu/emacs/EmacsDrawRectangle.java | 2 +- java/org/gnu/emacs/EmacsFillPolygon.java | 2 +- java/org/gnu/emacs/EmacsFillRectangle.java | 2 +- java/org/gnu/emacs/EmacsGC.java | 2 +- java/org/gnu/emacs/EmacsInputConnection.java | 11 ++++++++++- java/org/gnu/emacs/EmacsNative.java | 2 +- java/org/gnu/emacs/EmacsNoninteractive.java | 2 +- java/org/gnu/emacs/EmacsOpenActivity.java | 2 +- java/org/gnu/emacs/EmacsPixmap.java | 2 +- java/org/gnu/emacs/EmacsPreferencesActivity.java | 2 +- java/org/gnu/emacs/EmacsSdk11Clipboard.java | 2 +- java/org/gnu/emacs/EmacsSdk23FontDriver.java | 2 +- java/org/gnu/emacs/EmacsSdk8Clipboard.java | 2 +- java/org/gnu/emacs/EmacsService.java | 2 +- java/org/gnu/emacs/EmacsSurfaceView.java | 2 +- java/org/gnu/emacs/EmacsView.java | 2 +- java/org/gnu/emacs/EmacsWindow.java | 2 +- .../gnu/emacs/EmacsWindowAttachmentManager.java | 2 +- 27 files changed, 60 insertions(+), 37 deletions(-) (limited to 'java') diff --git a/java/Makefile.in b/java/Makefile.in index 7ba05f6c9a3..bff021752c7 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -281,9 +281,23 @@ $(APK_NAME): classes.dex emacs.apk-in emacs.keystore $(AM_V_SILENT) $(APKSIGNER) $(SIGN_EMACS_V2) $@ $(AM_V_SILENT) rm -f $@.unaligned *.idsig +# TAGS generation. + +ETAGS = $(top_builddir)/lib-src/etags + +$(ETAGS): FORCE + $(MAKE) -C ../lib-src $(notdir $@) + +tagsfiles = $(JAVA_FILES) $(RESOURCE_FILE) + +.PHONY: tags FORCE +tags: TAGS +TAGS: $(ETAGS) $(tagsfiles) + $(AM_V_GEN) $(ETAGS) $(tagsfiles) + clean: rm -f *.apk emacs.apk-in *.dex *.unaligned *.class *.idsig - rm -rf install-temp $(RESOURCE_FILE) + rm -rf install-temp $(RESOURCE_FILE) TAGS find . -name '*.class' -delete maintainer-clean distclean bootstrap-clean: clean diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 7e09e608984..0ee8c239899 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -108,7 +108,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void detachWindow () { syncFullscreenWith (null); @@ -131,7 +131,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void attachWindow (EmacsWindow child) { Log.d (TAG, "attachWindow: " + child); @@ -161,21 +161,21 @@ public class EmacsActivity extends Activity } @Override - public void + public final void destroy () { finish (); } @Override - public EmacsWindow + public final EmacsWindow getAttachedWindow () { return window; } @Override - public void + public final void onCreate (Bundle savedInstanceState) { FrameLayout.LayoutParams params; @@ -238,7 +238,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onWindowFocusChanged (boolean isFocused) { Log.d (TAG, ("onWindowFocusChanged: " @@ -256,7 +256,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onPause () { isPaused = true; @@ -266,7 +266,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onResume () { isPaused = false; @@ -279,7 +279,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onContextMenuClosed (Menu menu) { Log.d (TAG, "onContextMenuClosed: " + menu); @@ -298,7 +298,7 @@ public class EmacsActivity extends Activity } @SuppressWarnings ("deprecation") - public void + public final void syncFullscreenWith (EmacsWindow emacsWindow) { WindowInsetsController controller; @@ -372,7 +372,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onAttachedToWindow () { super.onAttachedToWindow (); diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java index 6a065165eb1..10099721744 100644 --- a/java/org/gnu/emacs/EmacsApplication.java +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -27,7 +27,7 @@ import android.content.Context; import android.app.Application; import android.util.Log; -public class EmacsApplication extends Application +public final class EmacsApplication extends Application { private static final String TAG = "EmacsApplication"; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 6b3ae0c6de9..0de292af21a 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -42,7 +42,7 @@ import android.widget.PopupMenu; Android menu, which can be turned into a popup (or other kind of) menu. */ -public class EmacsContextMenu +public final class EmacsContextMenu { private static final String TAG = "EmacsContextMenu"; diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 1daa2190542..f69b0cde866 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -27,7 +27,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsCopyArea +public final class EmacsCopyArea { private static Xfermode overAlu; diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index 9f9124ce99c..80a5e5f7369 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -38,7 +38,7 @@ import android.view.ViewGroup; describes a single alert dialog. Then, `inflate' turns it into AlertDialog. */ -public class EmacsDialog implements DialogInterface.OnDismissListener +public final class EmacsDialog implements DialogInterface.OnDismissListener { private static final String TAG = "EmacsDialog"; diff --git a/java/org/gnu/emacs/EmacsDocumentsProvider.java b/java/org/gnu/emacs/EmacsDocumentsProvider.java index 3c3c7ead3c5..901c3b909e0 100644 --- a/java/org/gnu/emacs/EmacsDocumentsProvider.java +++ b/java/org/gnu/emacs/EmacsDocumentsProvider.java @@ -48,7 +48,7 @@ import java.io.IOException; This functionality is only available on Android 19 and later. */ -public class EmacsDocumentsProvider extends DocumentsProvider +public final class EmacsDocumentsProvider extends DocumentsProvider { /* Home directory. This is the directory whose contents are initially returned to requesting applications. */ diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index c6e5123bfca..0b23138a36c 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -29,7 +29,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsDrawLine +public final class EmacsDrawLine { public static void perform (EmacsDrawable drawable, EmacsGC gc, diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java b/java/org/gnu/emacs/EmacsDrawPoint.java index 3bc7be17961..de8ddf09cc4 100644 --- a/java/org/gnu/emacs/EmacsDrawPoint.java +++ b/java/org/gnu/emacs/EmacsDrawPoint.java @@ -19,7 +19,7 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -public class EmacsDrawPoint +public final class EmacsDrawPoint { public static void perform (EmacsDrawable drawable, diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 695a8c6ea44..ce5e94e4a76 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -27,7 +27,7 @@ import android.graphics.RectF; import android.util.Log; -public class EmacsDrawRectangle +public final class EmacsDrawRectangle { public static void perform (EmacsDrawable drawable, EmacsGC gc, diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index 22e2dd0d8a9..d55a0b3aca8 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -29,7 +29,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; -public class EmacsFillPolygon +public final class EmacsFillPolygon { public static void perform (EmacsDrawable drawable, EmacsGC gc, Point points[]) diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index aed0a540c8f..4a0478b446f 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -26,7 +26,7 @@ import android.graphics.Rect; import android.util.Log; -public class EmacsFillRectangle +public final class EmacsFillRectangle { public static void perform (EmacsDrawable drawable, EmacsGC gc, diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index bdc27a1ca5b..a7467cb9bd0 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -29,7 +29,7 @@ import android.graphics.Xfermode; /* X like graphics context structures. Keep the enums in synch with androidgui.h! */ -public class EmacsGC extends EmacsHandleObject +public final class EmacsGC extends EmacsHandleObject { public static final int GC_COPY = 0; public static final int GC_XOR = 1; diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 834c2226c82..ed64c368857 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -36,7 +36,7 @@ import android.util.Log; See EmacsEditable for more details. */ -public class EmacsInputConnection extends BaseInputConnection +public final class EmacsInputConnection extends BaseInputConnection { private static final String TAG = "EmacsInputConnection"; private EmacsView view; @@ -243,6 +243,15 @@ public class EmacsInputConnection extends BaseInputConnection return super.sendKeyEvent (key); } + @Override + public boolean + deleteSurroundingTextInCodePoints (int beforeLength, int afterLength) + { + /* This can be implemented the same way as + deleteSurroundingText. */ + return this.deleteSurroundingText (beforeLength, afterLength); + } + /* Override functions which are not implemented. */ diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index f0917a68120..b1205353090 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -25,7 +25,7 @@ import android.content.res.AssetManager; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; -public class EmacsNative +public final class EmacsNative { /* List of native libraries that must be loaded during class initialization. */ diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index 30901edb75f..f365037b311 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -44,7 +44,7 @@ import java.lang.reflect.Method; initializes Emacs. */ @SuppressWarnings ("unchecked") -public class EmacsNoninteractive +public final class EmacsNoninteractive { private static String getLibraryDirectory (Context context) diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index 87ce454a816..fddd5331d2f 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -68,7 +68,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -public class EmacsOpenActivity extends Activity +public final class EmacsOpenActivity extends Activity implements DialogInterface.OnClickListener { private static final String TAG = "EmacsOpenActivity"; diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index a83d8f25542..eb011bc5e65 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -29,7 +29,7 @@ import android.os.Build; /* Drawable backed by bitmap. */ -public class EmacsPixmap extends EmacsHandleObject +public final class EmacsPixmap extends EmacsHandleObject implements EmacsDrawable { /* The depth of the bitmap. This is not actually used, just defined diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index 85639fe9236..70934fa4bd4 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -42,7 +42,7 @@ import android.preference.*; Unfortunately, there is no alternative that looks the same way. */ @SuppressWarnings ("deprecation") -public class EmacsPreferencesActivity extends PreferenceActivity +public final 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. */ diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java index ea35a463299..a05184513cd 100644 --- a/java/org/gnu/emacs/EmacsSdk11Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -32,7 +32,7 @@ import java.io.UnsupportedEncodingException; /* This class implements EmacsClipboard for Android 3.0 and later systems. */ -public class EmacsSdk11Clipboard extends EmacsClipboard +public final class EmacsSdk11Clipboard extends EmacsClipboard implements ClipboardManager.OnPrimaryClipChangedListener { private static final String TAG = "EmacsSdk11Clipboard"; diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java b/java/org/gnu/emacs/EmacsSdk23FontDriver.java index 11e128d5769..aaba8dbd166 100644 --- a/java/org/gnu/emacs/EmacsSdk23FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java @@ -22,7 +22,7 @@ package org.gnu.emacs; import android.graphics.Paint; import android.graphics.Rect; -public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver +public final class EmacsSdk23FontDriver extends EmacsSdk7FontDriver { private void textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics, diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java b/java/org/gnu/emacs/EmacsSdk8Clipboard.java index 34e66912562..818a722a908 100644 --- a/java/org/gnu/emacs/EmacsSdk8Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java @@ -31,7 +31,7 @@ import java.io.UnsupportedEncodingException; similarly old systems. */ @SuppressWarnings ("deprecation") -public class EmacsSdk8Clipboard extends EmacsClipboard +public final class EmacsSdk8Clipboard extends EmacsClipboard { private static final String TAG = "EmacsSdk8Clipboard"; private ClipboardManager manager; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 7f4f75b5147..e61d9487375 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -82,7 +82,7 @@ class Holder /* EmacsService is the service that starts the thread running Emacs and handles requests by that Emacs instance. */ -public class EmacsService extends Service +public final class EmacsService extends Service { public static final String TAG = "EmacsService"; public static final int MAX_PENDING_REQUESTS = 256; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 62e927094e4..e0411f7f8b3 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -35,7 +35,7 @@ import java.lang.ref.WeakReference; own back buffers, which use too much memory (up to 96 MB for a single frame.) */ -public class EmacsSurfaceView extends View +public final class EmacsSurfaceView extends View { private static final String TAG = "EmacsSurfaceView"; private EmacsView view; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 89f526853b2..d2330494bc7 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -51,7 +51,7 @@ import android.util.Log; It is also a ViewGroup, as it also lays out children. */ -public class EmacsView extends ViewGroup +public final class EmacsView extends ViewGroup { public static final String TAG = "EmacsView"; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 90fc4c44198..007a2a86e68 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -59,7 +59,7 @@ import android.os.Build; Views are also drawables, meaning they can accept drawing requests. */ -public class EmacsWindow extends EmacsHandleObject +public final class EmacsWindow extends EmacsHandleObject implements EmacsDrawable { private static final String TAG = "EmacsWindow"; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 510300571b8..1548bf28087 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -50,7 +50,7 @@ import android.util.Log; Finally, every time a window is removed, the consumer is destroyed. */ -public class EmacsWindowAttachmentManager +public final class EmacsWindowAttachmentManager { public static EmacsWindowAttachmentManager MANAGER; private final static String TAG = "EmacsWindowAttachmentManager"; -- cgit v1.2.1 From 194b3f948cba9f6da0e5d3b36ada8ab9ca74d482 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 1 Mar 2023 14:31:57 +0800 Subject: Update Android port * java/AndroidManifest.xml.in: Specify @style/EmacsStyle. * java/org/gnu/emacs/EmacsActivity.java (onCreate): Stop setting the theme here. * java/res/values-v11/style.xml: * java/res/values-v14/style.xml: * java/res/values-v29/style.xml: * java/res/values/style.xml: Extract style resources into res/values. --- java/AndroidManifest.xml.in | 2 +- java/org/gnu/emacs/EmacsActivity.java | 7 ------- java/res/values-v11/style.xml | 23 +++++++++++++++++++++++ java/res/values-v14/style.xml | 24 ++++++++++++++++++++++++ java/res/values-v29/style.xml | 30 ++++++++++++++++++++++++++++++ java/res/values/style.xml | 25 +++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 java/res/values-v11/style.xml create mode 100644 java/res/values-v14/style.xml create mode 100644 java/res/values-v29/style.xml create mode 100644 java/res/values/style.xml (limited to 'java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 1da3646e2f5..4ebfe470c0a 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -67,7 +67,7 @@ along with GNU Emacs. If not, see . --> android:icon="@drawable/emacs" android:hardwareAccelerated="true" android:supportsRtl="true" - android:theme="@android:style/Theme" + android:theme="@style/EmacsStyle" android:debuggable="@ANDROID_DEBUGGABLE@" android:extractNativeLibs="true"> diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 0ee8c239899..1c5d7605caa 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -187,13 +187,6 @@ public class EmacsActivity extends Activity = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q", false); - /* 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); - params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); diff --git a/java/res/values-v11/style.xml b/java/res/values-v11/style.xml new file mode 100644 index 00000000000..50cf96e8bc5 --- /dev/null +++ b/java/res/values-v11/style.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/java/res/values/style.xml b/java/res/values/style.xml new file mode 100644 index 00000000000..396b7d8da39 --- /dev/null +++ b/java/res/values/style.xml @@ -0,0 +1,25 @@ + + + + + +