summaryrefslogtreecommitdiff
path: root/java/org/gnu/emacs/EmacsService.java
diff options
context:
space:
mode:
authorPo Lu <luangruo@yahoo.com>2023-08-07 08:51:11 +0800
committerPo Lu <luangruo@yahoo.com>2023-08-07 08:51:11 +0800
commitc71a520d1da636a722cf87b46534ca3b5aafbc7b (patch)
tree95a3099c065800f60602e0a403b551a6d6dba139 /java/org/gnu/emacs/EmacsService.java
parent18e7bc87521e3c48b819cfe4a113f532ba905561 (diff)
parent9a9f73041d09d2da7ed562c7ffae0d9519562fba (diff)
Introduce an Android window system port for GNU Emacs
* src/xterm.h: New fields `quit_keysym' and `quit_keysym_time'. * src/xterm.c (handle_one_xevent): Check for the quit keysym, and set Vquit_flag upon witnessing two clicks in rapid succession. (x_term_init): Set `quit_keysym'. (init_xterm): Fix typo in name of `register_textconv_interface'. (syms_of_xterm) <Vx_toolkit_scroll_bars>: Describe its default value on android. * src/xfns.c (xic_string_conversion_callback): Pass `0' as the last argument to textconv_query. (Fx_server_vendor, Fx_server_version): Document return values on Android. * src/xfaces.c (Fx_family_fonts, set_lface_from_font): Use FRAME_RES instead of FRAME_RES_Y, respecting user preferences on window systems that have distinct display and font scaling factors. (Fx_load_color_file): Call `emacs_fclose', not fclose. * src/xdisp.c (tab_bar_item_info): Allow `close_p' to be NULL. (get_tab_bar_item): Update commentary to reflect that change. (get_tab_bar_item_kbd): New function, resembling get_tab_bar_item. (build_desired_tool_bar_string): Clear `f->tool_bar_wraps_p'; insert new line characters if a QCwrap item is encountered, and set f->tool_bar_wrap_p. Replace characters beyond the end of the tool bar with spaces. (display_tool_bar_line): Move iterator to the next line if in contact with an explicit line-wrap item. (redisplay_internal): If there are newline characters in the tool bar, refrain from coercing each row into being identically tall. Don't call `set_tty_color_mode' on Android. (mark_window_display_accurate_1): Report changes to the point and mark to input methods. (display_menu_bar): Adjust ifdefs to allow non-X window systems to use the built-in menu bar. (draw_row_with_mouse_face): Don't call TTY functions on Android. (note_mouse_highlight): Call `popup_activated' on Android. (expose_frame): Correctly work on the menu bar window. (gui_union_rectangles): New function. * src/window.h (struct window): New fields for recording the last window and point positions, along with an ephemeral position used during IM text conversion. (WINDOW_MENU_BAR_P): Correct definition for non-X window systems without external menu bars. * src/window.c (replace_buffer_in_windows): Call Qreplace_buffer_in_windows only when bound. * src/w32proc.c (sys_spawnve): Pass extra argument to openp. * src/w32font.c (fill_in_logfont): Use font scaling factor, not the display scaling factor. * src/w32.c (check_windows_init_file): Pass extra argument to openp. * src/verbose.mk.in (AM_V_JAVAC, AM_V_DX, AM_V_AAPT) (AM_V_ZIPALIGN, AM_V_SILENT): New variables. * src/textconv.h (struct textconv_interface): New `point_changed', `compose_region_changed' and `notify_conversion'. Add declarations for new functions. * src/textconv.c (TEXTCONV_DEBUG): New macro. (suppress_conversion_count): New variable. (enum textconv_batch_edit_flags): New flag. (copy_buffer): Don't overwrite text before the gap with the text after. (get_mark, select_window): New functions. (textconv_query): New argument FLAGS. Contingent upon its value, use the previous point or mark or skip the conversion region. (sync_overlay, record_buffer_change, reset_frame_state) (detect_conversion_events, restore_selected_window) (really_commit_text, really_finish_composing_text) (really_set_composing_region, really_delete_composing_text) (really_request_point_update, really_set_point_and_mark) (complete_edit): New functions. (struct complete_edit_check_context): New structure; store in it the result of editing operations. (complete_edit_check, handle_pending_conversion_events_1) (decrement_inside, handle_pending_conversion_events) (start_batch_edit, end_batch_edit, commit_text) (set_composing_text, textconv_set_point_and_mark) (request_point_update, textconv_barrier, get_extracted_text) (get_surrounding_text, conversion_disabled_p) (report_selected_window_change, report_point_change) (disable_text_conversion, resume_text_conversion) (register_textconv_interface, check_postponed_buffers) (postponed_buffers, Fset_text_conversion_style) (syms_of_textconv) <Qaction, Qtext_conversion, Qpush_mark, Qunderline, Qoverriding_text_conversion_style, Vtext_conversion_edits, Voverriding_text_conversion_style, Vtext_conversion_face>: New functions, symbols and variables. * src/terminal.c (Fterminal_live_p): Return Qandroid if type is output_android. * src/termhooks.h (enum output_method): Add `output_android'. (struct terminal) <display_info>: Add union constituent field for `android'. <query_colors>: Define on Android as well. (TERMINAL_FONT_CACHE) [HAVE_ANDROID]: Return the inappropriately named font cache field on Android. * src/term.c (string_cost, string_cost_one_line, per_line_cost) (calculate_costs, produce_glyphs, produce_glyphs, tty_capable_p) (tty_capable_p, device, init_tty, maybe_fatal) (delete_tty) [HAVE_ANDROID]: Exclude or turn these functions into vestiges. (Fsuspend_tty, Fresume_tty): Call `emacs_fclose' and always signal on Android. (Fresume_tty): Call `emacs_fdopen'. (Ftty__set_output_buffer_size) [HAVE_ANDROID]: Remove this function. (encode_terminal_code): Replace with a stub. (init_tty, delete_tty, maybe_fatal): Call `emacs_fclose'. (syms_of_term): Remove most unnecessary code on Android. <system_uses_terminfo>: Always set this option on Android. * src/sysdep.c (init_standard_fds): Call emacs_fopen. (reset_sigio, widen_foreground_group): Define out on Android. (reset_sys_modes): Don't call either function on Android. (init_sigbus, handle_sigbus): New functions. (init_signals): Don't add user signals on Android. Register signal handlers for SIGBUS, and refrain from handling SIGSEGV. (emacs_fstatat): Wrap android_fstatat on Android. (sys_fstat, sys_faccessat): New function. (emacs_openat): Exclude this function when building libemacs.so. (emacs_open, emacs_open_noquit, emacs_fopen, emacs_close): Wrap functions defined in the Android filesystem emulation code. (emacs_fdopen, emacs_fclose, emacs_unlink, emacs_symlink) (emacs_rmdir, emacs_mkdir, emacs_renameat_noreplace, emacs_rename) (emacs_fchmodat): New wrappers for more of those functions. (close_output_streams): Placate the file descriptor sanitizer that's included with android. * src/sound.c (Fplay_sound_internal): Pass extra argument to openp. * src/sfntfont.h: * src/sfntfont.c: * src/sfntfont-android.c: * src/sfnt.h: * src/sfnt.c: New files. * src/scroll.c: Exclude the entire file on Android. * src/process.c: (allocate_pty): Call sys_faccessat, not faccessat. (Fmake_process): Call openp with an extra argument. (wait_reading_process_output): Call android_select. (Fprocess_send_eof): Don't call tcdrain if not present. (handle_child_signal): Write a comment describing a small, seldom encountered issue. * src/print.c (print_vectorlike): Don't print FONT_EXTRA_INDEX for font entities. * src/pdumper.c (Fdump_emacs_portable): Allow dumping in interactive Emacs's on Android, as this is performed within loadup.el. (dump_discard_mem): Use madvise if posix_advise is not present. (pdumper_load): Call sys_fstat, not fstat. (syms_of_pdumper) <Vpdumper_fingerprint>: Calculate the fingerprint for this Emacs executable and store it there. * src/menu.c (have_boxes): Android has boxes. (push_submenu_start, push_submenu_end): Define on Android. (single_menu_item): Produce submenus on Android as well. (x_popup_menu_1): Call EVENT_START, in contrast to duplicating its old functionality with calls to Fcar and XCDR. (Fx_popup_menu): Update documentation to reflect that touch screen events are now accepted as POSITION. * src/marker.c (set_marker_internal): Redisplay buffers when their mark changes, enabling changes to be reported to the IME. * src/lread.c (lread_fd, lread_fd_cmp, lread_fd_p, lread_close) (lread_fstat, lread_read_quit, lread_lseek, file_stream) (file_seek, file_stream_valid_p, file_stream_close) (file_stream_invalid, getc): New macros. Define to an implementation with file descriptors and file streams on systems other than Android 2.3+, and one using Android file descriptors on those systems. (USE_ANDROID_ASSETS): Define on Android 2.3+; (file_get_char): New function. (infile, skip_dyn_bytes, skip_dyn_eof, readbyte_from_stdio) (read_filtered_event, safe_to_load_version, close_infile_unwind): Implement in terms of those macros. (close_file_unwind_android_fd): New function. (Fload): Pass extra argument to `openp' and use Android file descriptors where possible. (Flocate_file_internal): Pass extra argument to `openp'. (maybe_swap_for_eln1): Call sys_fstat, not fstat. (openp): New arg PLATFORM; if supplied and opening a platform-specific file descriptor replacement is possible, place one there. (build_load_history): Fix typos in comments. (skip_lazy_string): Implement in terms of the aformentioned macros. * src/lisp.h: Add declarations for new functions. * src/keyboard.h (reading_key_sequence): Declare here. (EVENT_START): Treat touch screen events specially by returning the posn of their touch point. * src/keyboard.c (reading_key_sequence, menu_bar_touch_id): New variables. (command_loop_1): (read_menu_command): Pass false to read_key_sequence. (read_char): Update commentary. (readable_events): If text conversion events (edits from an input method) are queued, return 1. (kbd_buffer_get_event): If text conversion events exist, carry out the edits contained within. Then, generate a Qtext_conversion event. (lispy_function_keys, FUNCTION_KEY_OFFSET): Define function key array on Android. (coords_in_tab_bar_window): New function. (make_lispy_event) <TOUCHSCREEN_BEGIN_EVENT>: Keep track of touches that fall into the confines of the tab bar, and include the tab bar item in their position lists. Moreover, retain and track the touch in C code if it's taking place within the menu bar. <TOUCHSCREEN_END_EVENT>: Likewise for the tab bar; generate menu bar events if the touch ends on a menu item and was previously singled out for tracking. <TOUCHSCREEN_UPDATE_EVENT>: Don't deliver this event if the frame is dead, or if it was identified for tracking since the only touch sequence that changed begun inside the menu bar. (handle_async_input): Call android_check_query_urgent. (handle_input_available_signal): Add memory fence. (parse_tool_bar_item): Handle `wrap' properties within tool bar items moving subsequent items onto a new row. (access_keymap_keyremap): New arguments START, END, KEYBUF. Set Qcurrent_key_remap_sequence around calls to the remap function. (keyremap_step): Pass the necessary information to access_keymap_keyremap. (restore_reading_key_sequence): New function. (read_key_sequence): Set `reading_key_sequence'. New arg DISABLE_TEXT_CONVERSION_P, which causes text conversion to be disabled as long as the key sequence is being read. Disable text conversion as well if a menu or function key prefix is read, insert imaginary prefix keys before touchscreen events within special areas of a frame. Don't insert prefix keys if input is being mocked, which transpires if the input is in actuality originating from a key translation map. (read_key_sequence_vs): New argument DISABLE_TEXT_CONVERSION. (Fread_key_sequence): New argument DISABLE_TEXT_CONVERSION. (Fopen_dribble_file): Use emacs_fclose. (head_table): Make touchscreen-begin and touchscreen-end events touchscreen events. (syms_of_keyboard) <QCwrap, Qtouchscreen, Qtext_conversion>: New symbols. <disable_inhibit_text_conversion, Vcurrent_key_remap_sequence>: New variables. * src/inotify.c (Finotify_add_watch): Detect and avoid watching special files that don't exist from the POV of inotify. * src/image.c (image_create_bitmap_from_data) (image_create_bitmap_from_file, free_bitmap_record) (prepare_image_for_display, image_clear_image_1) (image_clear_image_1, image_size_in_bytes, image_set_transform): (Create_Pixmap_From_Bitmap_Data, lookup_rgb_color) (image_to_emacs_colors, image_from_emacs_colors) (image_pixmap_draw_cross, image_disable_image): Implement on Android, reusing much of the X11 code. (matrix_identity, matrix_rotate, matrix_mirror_horizontal) (matrix_translate): New functions. (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_get_x_image, image_unget_x_image): Implement on Android. (image_find_image_fd): Return an Android file descriptor if possible. (close_android_fd): New function. (slurp_file): Accept `image_fds', defined to Android file descriptors. (xpm_load): Enable built-in XPM support on Android. (xbm_load, pbm_load, png_load_body, jpeg_load_body, gif_load) (webp_load, imagemagick_load_image, svg_load): Use image file descriptors on Android; these file descriptors may in fact represent compressed asset streams, and obviate the necessity of creating a new file descriptor for each asset image opened. (Fimage_transforms_p): Report rotate90 on Android. (image_types, syms_of_image): Enable built-in XPM support on Android. * src/fringe.c (init_fringe_bitmap): Bit swap bitmaps on Android, as on X. * src/frame.h (enum text_conversion_operation): New enumerator. (struct text_conversion_action, struct text_conversion_state): New variable. (struct frame): New fields `tool_bar_wraps_p' and `conversion'. Increase the width of `output_method'. <output_data>: Add `android' field. <wait_event_type>: Define on Android as well. (fset_menu_bar_window): Define correctly, so that it's declared on non-X builds without external menu bars. (FRAME_ANDROID_P): Define macro. (FRAME_WINDOW_P) [HAVE_ANDROID]: Define to FRAME_ANDROID_P. (FRAME_RES): New macro. (MOUSE_HL_INFO): Define without referencing tty output data on Android, which doesn't have them. * src/frame.c (Fframep): Return `android' on Android systems. (Fwindow_system): Likewise. (make_frame): Clear text conversion state and `tool_bar_wraps_p'. (Fmake_terminal_frame): Signal that Android doesn't support text terminals. (delete_frame): Reset text conversion state prior to deleting the frame. (gui_display_get_resource): Don't call the resource hook on Android. (Fx_parse_geometry): Pacify compiler warning. (make_monitor_attribute_list): Don't always use SOURCE if nil. (syms_of_frame) <Qandroid>: New symbol. <Vdefault_frame_scroll_bars>: Don't default scroll bars to an enabled state on Android. * src/fontset.c (fontset_find_font): Tackle an unusual problem. * src/font.h (struct font_entity): New field `is_android'. (PT_PER_INCH): Define to 160.00 on Android. * src/font.c (font_make_entity): New function. (font_make_entity_android): New variant that sets `is_android' to true. (font_pixel_size, font_find_for_lface, font_open_for_lface) (Ffont_face_attributes, Fopen_font): Respect the distinction between frame text and display scales. * src/fns.c (Flocale_info): Silence compiler warning. * src/filelock.c (BOOT_TIME): Undefine BOOT_TIME when building libemacs.so (get_boot_time, rename_lock_file, create_lock_file) (current_lock_owner, make_lock_file_name, unlock_file): Employ wrappers for Android filesystem operations. * src/fileio.c (emacs_fd, emacs_fd_open, emacs_fd_close) (emacs_fd_read, emacs_fd_lseek, emacs_fd_fstat, emacs_fd_valid_p): New type and macros; define them to suitable values, akin to those in lread.c (check_vfs_filename): New function. (file_access_p): Call `sys_faccessat'. (close_file_unwind_emacs_fd): New function. (fclose_unwind): Call `emacs_fclose', not fclose. (file_name_directory): Export this function. (user_homedir): If PW->pw_dir is not set and its uid is the current user, call `android_get_home_directory'. (get_homedir): Call `android_get_home_directory' if PW->pw_dir is not set. (Fcopy_file, Fmake_directory_internal, Fdelete_directory_internal) (Fdelete_file, Frename_file, Fmake_symbolic_link, Faccess_file) (file_directory_p, file_accessible_directory_p, Fset_file_modes) (Fset_file_times, Ffile_newer_than_file_p, read_non_regular) (Finsert_file_contents, write_region) (Fverify_visited_file_modtime, Fset_visited_file_modtime) (do_auto_save_unwind): Make use of Android filesystem wrappers and file descriptors where possible. (Fadd_name_to_file): Prohibit creating links to and from files residing on Android special directories. (Ffile_system_info): Avoid compilation failure on Android, where Gnulib can't find out how to implement statfs. * src/epaths.in [HAVE_ANDROID && !ANDROID_STUBIFY]: Deface this file, so Makefile cannot change the hard-coded values within. * src/emacs.c (using_utf8): Correctly initialize mbstate_t on Android. (init_cmdargs): Pass extra argument to openp. (load_pdump): When building libemacs.so, use solely the file provided on the command line or as an argument to `android_emacs_init'. (load_seccomp): Call sys_fstat, not fstat. (main, android_emacs_init): Name `main' `android_emacs_init' when building libemacs.so, and accept an argument designating the dump file. (main): Initialize text conversion and Android. Don't presume that argv is NULL terminated. (Fkill_emacs, shut_down_emacs): Properly implement RESTART on Android. (syms_of_emacs) <Vsystem_type>: Describe the possible value `android'. * src/emacs-module.c (MODULE_HANDLE_NONLOCAL_EXIT): Cease relying on GCC clean-up attribute extension. (MODULE_INTERNAL_CLEANUP): New macro. (module_make_global_ref, module_free_global_ref) (module_make_function, module_get_function_finalizer) (module_make_interactive, module_funcall, module_extract_integer) (module_extract_float, module_copy_string_contents) (module_get_user_ptr, module_set_user_ptr) (module_get_user_finalizer, module_set_user_finalizer) (module_vec_set, module_vec_size, module_process_input) (module_extract_big_integer, module_make_big_integer): Carry out necessary clean-up tasks using MODULE_HANDLE_NONLOCAL_EXIT. * src/editfns.c (Fuser_full_name): Call `android_user_full_name', as USER_FULL_NAME doesn't always work. * src/doc.c (doc_fd, doc_fd_p, doc_open, doc_read_quit) (doc_lseek): New types and macros, resembling those in lread.c. (get_doc_string, Fsnarf_documentation): Implement in terms of those macros, so as to use Android asset streams directly. * src/dispnew.c (clear_current_matrices, clear_desired_matrices) (allocate_matrices_for_window_redisplay, free_glyphs) (redraw_frame, update_frame, scrolling, update_frame_line): Disable support for text terminals when building for Android. (Fopen_termscript): Use emacs_fclose. (init_display_interactive): Set Vinitial_window_system to Qandroid, and lose if Emacs needs to create a text terminal. * src/dispextern.h (No_Cursor, Emacs_Rectangle, struct gui_box): New definitions. (struct glyph_string) <gc>: Define to the Android GC type. (HAVE_NATIVE_TRANSPHORMS): Define on Android. (struct image): New fields `ximg', `mask_img', as on X. (enum tool_bar_item_idx): New tool bar item property TOOL_BAR_ITEM_WRAP. * src/dired.c (emacs_dir, emacs_closedir, emacs_readdir): New typedef and definitions. (open_directory): Return emacs_dir; use android_opendir on Android, instead of at-funcs. (directory_files_internal_unwind): Call emacs_closedir. (read_dirent): Call emacs_readdir. (directory_files_internal, file_name_completion) (file_name_completion_dirp): Use Android wrappers for directories and files. (file_attributes): Abstain from openat on Android. * src/conf_post.h (MB_CUR_MAX): Define to REPLACEMENT_MB_CUR_MAX if necessary to counteract inept LLVM headers. * src/coding.h (from_unicode_buffer): Define if HAVE_ANDROID as well. * src/coding.c (from_unicode_buffer): Define on Android, creating a variant that understands UCS-16 extended into wchar_t. (syms_of_coding) <Qutf_16le>: Define on Android. * src/charset.c (load_charset_map_from_file): Supply extra argument to openp, and call Emacs wrappers for fdopen and fclose. * src/callproc.c (get_current_directory): Return the home directory if ENCODED is a special directory. (delete_temp_file): Call emacs_unlink in lieu of unlink. (call_process): Use openp. (emacs_spawn): Use Android executable loader binary if needed and enabled. (init_callproc): Set Vshell_file_name to /system/bin/sh if libemacs.so. (syms_of_callproc) <Vctags_program_name, Vetags_program_name, Vhexl_program_name, Vemacsclient_program_name, Vmovemail_program_name>: New variables. Define to the names of the programs they respectively stand for. * src/callint.c (Fcall_interactively): Supply new argument in calls to Fread_key_sequence and Fread_key_sequence_vector. * src/buffer.h (struct buffer) <text_conversion_style_>: New bvar. (bset_text_conversion_style): New bvar setter. * src/buffer.c (init_buffer_once): Set the text conversion style. (syms_of_buffer) <BVAR (current_buffer, text_conversion_style)>: Define new BLV. * src/androidvfs.c: * src/androidterm.h: * src/androidterm.c: * src/androidselect.c: * src/androidmenu.c: * src/androidgui.h: * src/androidfont.c: * src/androidfns.c: * src/android.h: * src/android.c: * src/android-emacs.c: * src/android-asset.h: New function. * src/alloc.c (cleanup_vector): Finalize Android font entities. (find_string_data_in_pure) [__i386__ && !__clang__]: On Android, compensate for a bug in the latest NDK GCC. (mark_pinned_symbols, android_make_lisp_symbol): Elude another bug in debuginfo generation with an almost nonsensical fix. (garbage_collect): Mark androidterm and sfntfont. (mark_frame): Mark text conversion actions and info. * src/Makefile.in (XCONFIGURE): New variable. If set, add srcdir to vpath. (hostlib): New variable, always defined to libgnu.a on the build machine. (GIF_CFLAGS, JPEG_CFLAGS, TIFF_CFLAGS, SQLITE3_CFLAGS) (LIBSELINUX_CFLAGS, ANDROID_OBJ, ANDROID_LIBS, ANDROID_LDFLAGS) (ANDROID_BUILD_CFLAGS, LIBGMP_CFLAGS): New variables. (CM_OBJ): Update commentary. (EMACS_CFLAGS): Add new compiler flags variables. (base_obj): Add ANDROID_OBJ. (SOME_MACHINE_OBJECTS): Add Android-related objects. (lisp.mk): Generate from its absolute file name. ($(lispsource)/international/charprop.el): Don't generate when building libemacs.so. ($(libsrc)/make-docfile$(EXEEXT) $(libsrc)/make-fingerprint$(EXEEXT)): Depend on libgnu.a on the build machine. (mostlyclean): Remove libemacs.so. (build-counter.c, libemacs.so, android-emacs): New targets. These targets are made from this Makefile copied to a subdirectory of `cross', and provide the Emacs library and an ancillary binary used by the Android port. * nt/mingw-cfg.site: * nt/gnulib-cfg.mk: Impede building Gnulib's vasnprintf* code. * msdos/sedlibmk.inp: * msdos/sedlibcf.inp: * msdos/sed3v2.inp: * msdos/sed1v2.inp: Fix the DJGPP build. * make-dist (possibly_non_vc_files): Add exec/configure and exec/config.h.in. * m4/ndk-build.m4: New file. * m4/getline.m4: * m4/getdelim.m4: * m4/asm-underscore.m4: Update from Gnulib. * lisp/wid-edit.el (widget-event-point): Treat touch screen events correctly. (widget-keymap): Map touchscreen-begin to widget-button-click. (widget-event-start): New function. (widget-button--check-and-call-button): (widget-button-click): Behave correctly when confronted by touch screen events. * lisp/version.el (android-read-build-system) (android_read_build_time): New functions. (emacs-build-system, emacs-repository-version-android) (emacs-repository-get-version): (emacs-repository-get-branch): Implement properly on Android, by reading a file generated during the packaging process. * lisp/touch-screen.el: New file, supplying support for translating raw touch screen events into gestures. * lisp/tool-bar.el (secondary-tool-bar-map): New defvar. (tool-bar--cache-key, tool-bar--secondary-cache-key): Make defsubsts. (tool-bar--flush-key): Flush caches for the secondary tool bar as well. (tool-bar-make-keymap, tool-bar-make-keymap-1): Append the secondary tool bar map below the primary tool bar map. (modifier-bar-modifier-list): New variable. (tool-bar-apply-modifiers, modifier-bar-button) (tool-bar-event-apply-alt-modifier) (tool-bar-event-apply-super-modifier) (tool-bar-event-apply-hyper-modifier) (tool-bar-event-apply-shift-modifier) (tool-bar-event-apply-control-modifier) (tool-bar-event-apply-meta-modifier, modifier-bar-available-p) (modifier-bar-mode): New functions. * lisp/textmodes/text-mode.el (text-mode): Set text-conversion-style to t. * lisp/textmodes/reftex-global.el (reftex-create-tags-file): Use etags-program-name to provide the name of the etags program. * lisp/textmodes/conf-mode.el (conf-mode-initialize): Enable text conversion. * lisp/textmodes/artist.el (artist-figlet-get-font-list): Use /system/bin/sh on Android. * lisp/term/android-win.el: New file. * lisp/term.el (term-mode): Always display the on screen keyboard. (term-exec-1): Use /system/bin/sh on Android. * lisp/tab-line.el (tab-line-tab-map) (tab-line-new-tab) (tab-line-select-tab) (tab-line-close-tab) (tab-line-track-tap) (tab-line-event-start): Improve support for touch screen events. * lisp/tab-bar.el (tab-bar-mouse-context-menu): (tab-bar-map): Likewise. (tab-bar-handle-timeout, tab-bar-touchscreen-begin): New functions. * lisp/subr.el (event-start): Don't return nonsense if EVENT is a touchscreen event. (event-end): Likewise. (read-key): Disable text conversion within read-key-sequence-vector. (read-char-choice-with-read-key): Display the on screen keyboard. (read-char-from-minibuffer): Disable text conversion. (use-dialog-box-p): Prefer dialog boxes on Android. (y-or-n-p): Disable text conversion properly under all three modes of operation. * lisp/startup.el (android-fonts-enumerated): New variable. (normal-top-level): Load system fonts on Android. * lisp/speedbar.el (speedbar-fetch-etags-command): Use etags-program-name instead of hard-coding `etags'. * lisp/simple.el (normal-erase-is-backspace-setup-frame): Return true on Android. (event-apply-modifier): Correctly apply Shift and Control modifiers to keys with other modifiers. (undo-auto-amalgamate): Mention analyze-text-conversion wrt being an amalgamating command. * lisp/shell.el (shell--command-completion-data): Don't lose if PATH contains an inaccessible directory. * lisp/progmodes/prog-mode.el (prog-mode): Enable text conversion. * lisp/progmodes/cperl-mode.el (cperl-etags): Don't hard-code etags, employ etags-program-name instead. * lisp/progmodes/cc-mode.el (c-initialize-cc-mode): Initialize text conversion hook. * lisp/progmodes/cc-cmds.el (c-post-text-conversion): New function. Do electric characters. * lisp/play/gamegrid.el (gamegrid-setup-default-font): Don't crash if the display resolution is too high. * lisp/play/dunnet.el (text-conversion-style): * lisp/play/doctor.el (doctor-mode): Enable text conversion. * lisp/pixel-scroll.el (pixel-scroll-precision-scroll-down-page) (pixel-scroll-precision-scroll-Up-page): Make autoloads. * lisp/org/org-ctags.el (org-ctags-path-to-ctags): Use ctags-program-name, not ctags. * lisp/obsolete/terminal.el (terminal-emulator): Start /system/bin/sh, not /bin/sh. * lisp/net/tramp.el (tramp-encoding-shell): Use /system/bin/sh on Android. * lisp/net/eww.el (eww-form-submit, eww-form-file) (eww-form-checkbox, eww-form-select): Define these faces on Android as well. * lisp/net/browse-url.el (browse-url-default-browser) (browse-url--browser-defcustom-type): Specify on Android. (browse-url-android-share, browse-url-default-android-browser): New option and function. * lisp/mwheel.el (mouse-wheel-down-event, mouse-wheel-up-event) (mouse-wheel-left-event, mouse-wheel-right-event): Define suitably on Android. * lisp/mouse.el (minor-mode-menu-from-indicator): New argument EVENT. Use it for positioning the menu. (mouse-minor-mode-menu): Pass EVENT to that function. * lisp/minibuffer.el (clear-minibuffer-message): Don't clear the message if `touch-screen-preview-select' may be underway. (minibuffer-mode): Enable text conversion. (minibuffer-setup-on-screen-keyboard) (minibuffer-exit-on-screen-keyboard): New functions. * lisp/menu-bar.el (menu-bar-close-window): New option. (menu-bar-edit-menu): Bind execute-extended-command to a menu item. (kill-this-buffer, kill-this-buffer-enabled-p): Respect menu-bar-close-window. * lisp/mail/rmail.el (rmail-autodetect, rmail-insert-inbox-text): Don't hard-code the name of movemail; rather, use movemail-program-name. * lisp/mail/emacsbug.el (emacs-build-description): Insert the Android version and manufacturer. * lisp/ls-lisp.el (ls-lisp-use-insert-directory-program): Default to off on Android. * lisp/loadup.el: Set load-list to empty load list after startup; dump the first time Emacs starts, and load Android related miscellanea. * lisp/isearch.el (isearch-text-conversion-style): New variable. (isearch-mode, isearch-done): Display the OSK, then temporarily disable and restore the on screen keyboard. * lisp/international/mule-cmds.el (set-coding-system-map): Update menu definition for Android. * lisp/international/fontset.el (script-representative-chars) (setup-default-fontset): Improve detection of CJK fonts. * lisp/image/wallpaper.el: Fix compiler warning. * lisp/ielm.el (inferior-emacs-lisp-mode): Don't hard-code name of hexl, replacing that with hexl-program-name. * lisp/htmlfontify.el (hfy-etags-bin): Replace hard-coded Emacs with etags-program-name. * lisp/hexl.el (hexl-program): Replace hard-coded hexl. * lisp/help-macro.el (make-help-screen): Display the on screen keyboard and disable text conversion prior to reading options. * lisp/gnus/mail-source.el (mail-source-movemail-program): Replace hard-coded movemail with movemail-program-name. * lisp/gnus/gnus-score.el (gnus-read-char): New function. (gnus-summary-increase-score): Use a dialog box to display these options on Android. * 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-popup-menus-p, display-graphic-p, display-symbol-keys-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, display-monitor-attributes-list): Implement window system specific functions on Android. * lisp/files.el (basic-save-buffer): Allow files to exist without a parent directory. * lisp/faces.el (tool-bar): Use default definition on Android. * lisp/emacs-lisp/eldoc.el (eldoc-add-command-completions): Add touch-screen-handle-touch and analyze-text-conversion. * lisp/elec-pair.el (electric-pair-analyze-conversion): New function. * lisp/doc-view.el (doc-view-menu): Improve menu. (doc-view-tool-bar-map): Add a new tool bar for Doc View. (doc-view-new-search): New command. (doc-view-mode): Enable that new tool bar. * lisp/dired-aux.el (dired-do-chxxx, dired-do-chmod) (dired-do-print, dired-do-shell-command, dired-do-compress-to) (dired-do-create-files, dired-do-rename, dired-do-isearch) (dired-do-isearch-regexp, dired-do-search) (dired-do-query-replace-regexp, dired-do-find-regexp) (dired-vc-next-action): Disable ``click to select'' after running this command. * lisp/dired.el (dired-insert-set-properties): Attach click-to-select keymap to file names if necessary. (dired-mode-map): Bind `touchscreen-hold' to click to select mode. (dired-post-do-command): New function. (dired-do-delete): Call it. (dired-mark-for-click, dired-enable-click-to-select-mode): New functions. (dired-click-to-select-mode): New minor mode. * lisp/cus-edit.el (custom-button-mouse, custom-button-pressed) (custom-display): Define faces to their default values on Android. * lisp/comint.el (comint-mode): Enable text conversion. * lisp/cedet/semantic/db-ebrowse.el (semanticdb-create-ebrowse-database): Replace fixed ebrowse with ebrowse-program-name. * lisp/calc/calc.el (calc-mode): Display the on screen keyboard. (calc): Insist on displaying the on screen keyboard. * lisp/button.el (button-map): Bind touch screen events to push-button. (push-button): Deal with touch screen events. * lisp/bindings.el (cut, paste, cut, text-conversion): New bindings. * lisp/battery.el (battery-status-function): Use `battery-android'. (battery-android): New function. * lib/gnulib.mk.in: * lib/getline.c: * lib/getdelim.c: * lib/Makefile.in: Update from Gnulib. * lib-src/emacsclient.c (decode_options): Set `alt_display' to `android'. * lib-src/asset-directory-tool.c: New file. * lib-src/Makefile.in: Adapt for cross-compilation. * java/res/xml/preferences.xml: * java/res/values/style.xml: * java/res/values/strings.xml: * java/res/values/bool.xml: * java/res/values-v29/style.xml: * java/res/values-v24/bool.xml: * java/res/values-v19/bool.xml: * java/res/values-v14/style.xml: * java/res/values-v11/style.xml: * java/org/gnu/emacs/EmacsWindowAttachmentManager.java: * java/org/gnu/emacs/EmacsWindow.java: * java/org/gnu/emacs/EmacsView.java: * java/org/gnu/emacs/EmacsThread.java: * java/org/gnu/emacs/EmacsSurfaceView.java: * java/org/gnu/emacs/EmacsService.java: * java/org/gnu/emacs/EmacsSdk8Clipboard.java: * java/org/gnu/emacs/EmacsSdk7FontDriver.java: * java/org/gnu/emacs/EmacsSdk23FontDriver.java: * java/org/gnu/emacs/EmacsSdk11Clipboard.java: * java/org/gnu/emacs/EmacsSafThread.java: * java/org/gnu/emacs/EmacsPreferencesActivity.java: * java/org/gnu/emacs/EmacsPixmap.java: * java/org/gnu/emacs/EmacsOpenActivity.java: * java/org/gnu/emacs/EmacsNoninteractive.java: * java/org/gnu/emacs/EmacsNative.java: * java/org/gnu/emacs/EmacsMultitaskActivity.java: * java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java: * java/org/gnu/emacs/EmacsInputConnection.java: * java/org/gnu/emacs/EmacsHolder.java: * java/org/gnu/emacs/EmacsHandleObject.java: * java/org/gnu/emacs/EmacsGC.java: * java/org/gnu/emacs/EmacsFontDriver.java: * java/org/gnu/emacs/EmacsFillRectangle.java: * java/org/gnu/emacs/EmacsFillPolygon.java: * java/org/gnu/emacs/EmacsDrawable.java: * java/org/gnu/emacs/EmacsDrawRectangle.java: * java/org/gnu/emacs/EmacsDrawPoint.java: * java/org/gnu/emacs/EmacsDrawLine.java: * java/org/gnu/emacs/EmacsDocumentsProvider.java: * java/org/gnu/emacs/EmacsDirectoryEntry.java: * java/org/gnu/emacs/EmacsDialogButtonLayout.java: * java/org/gnu/emacs/EmacsDialog.java: * java/org/gnu/emacs/EmacsCursor.java: * java/org/gnu/emacs/EmacsContextMenu.java: * java/org/gnu/emacs/EmacsClipboard.java: * java/org/gnu/emacs/EmacsApplication.java: * java/org/gnu/emacs/EmacsActivity.java: * java/debug.sh: * java/README: * java/Makefile.in: * java/INSTALL: * java/AndroidManifest.xml.in: * exec/trace.c: * exec/test.c: * exec/mipsfpu.h: * exec/mipsfpu.c: * exec/mipsel-user.h: * exec/loader-x86_64.s: * exec/loader-x86.s: * exec/loader-mipsel.s: * exec/loader-mips64el.s: * exec/loader-armeabi.s: * exec/loader-aarch64.s: * exec/install-sh: * exec/exec1.c: * exec/exec.h: * exec/exec.c: * exec/deps.mk: * exec/configure.ac: * exec/config.sub: * exec/config.h.in: * exec/config.guess: * exec/config-mips.m4.in: * exec/README: * exec/Makefile.in: * etc/images/last-page.xpm: New files. * etc/PROBLEMS: Expound upon problems with font instructing on Android. * etc/NEWS: Announce changes. * etc/MACHINES: Describe support for Android. * etc/DEBUG: Illustrate the steps to debug Emacs on Android. * doc/lispref/processes.texi (Subprocess Creation): * doc/lispref/os.texi (System Environment): * doc/lispref/keymaps.texi (Translation Keymaps): (Extended Menu Items): (Tool Bar): * doc/lispref/frames.texi (Frames): (Frame Layout): (Font and Color Parameters): (Pop-Up Menus): (Window System Selections): * doc/lispref/elisp.texi (Top): * doc/lispref/display.texi (Defining Faces): (Window Systems): * doc/lispref/commands.texi (Touchscreen Events): (Touchscreen Events): (Misc Events): (Key Sequence Input): * doc/emacs/windows.texi (Tab Line): * doc/emacs/input.texi: * doc/emacs/frames.texi (Tool Bars): (Tab Bars): * doc/emacs/emacs.texi (Top): * doc/emacs/dired.texi (Marks vs Flags): * doc/emacs/android.texi: * doc/emacs/Makefile.in (EMACSSOURCES): Update the documentation to properly describe changes effected. * cross/verbose.mk.android: * cross/ndk-build/ndk-resolve.mk: * cross/ndk-build/ndk-prebuilt-static-library.mk: * cross/ndk-build/ndk-prebuilt-shared-library.mk: * cross/ndk-build/ndk-clear-vars.mk: * cross/ndk-build/ndk-build.mk.in: * cross/ndk-build/ndk-build-static-library.mk: * cross/ndk-build/ndk-build-shared-library.mk: * cross/ndk-build/ndk-build-executable.mk: * cross/ndk-build/README: * cross/ndk-build/Makefile.in: * cross/langinfo.h: * cross/README: * cross/Makefile.in: New files. * configure.ac: Configure Emacs for cross-compilation on Android. * build-aux/ndk-module-extract.awk: * build-aux/ndk-build-helper.mk: * build-aux/ndk-build-helper-4.mk: * build-aux/ndk-build-helper-3.mk: * build-aux/ndk-build-helper-2.mk: * build-aux/ndk-build-helper-1.mk: * build-aux/makecounter.sh: New file. * autogen.sh: Autogen in exec as well. * admin/merge-gnulib (GNULIB_MODULES): Add getline, stpncpy and strnlen. Clean lib. * README: * Makefile.in: * INSTALL: Update for Android. * .dir-locals.el (c-mode): Add a few new types.
Diffstat (limited to 'java/org/gnu/emacs/EmacsService.java')
-rw-r--r--java/org/gnu/emacs/EmacsService.java1820
1 files changed, 1820 insertions, 0 deletions
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
new file mode 100644
index 00000000000..d91d8f66009
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -0,0 +1,1820 @@
+/* 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 <https://www.gnu.org/licenses/>. */
+
+package org.gnu.emacs;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.database.Cursor;
+
+import android.graphics.Matrix;
+import android.graphics.Point;
+
+import android.webkit.MimeTypeMap;
+
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.ExtractedText;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.app.Service;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.UriPermission;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager;
+
+import android.content.res.AssetManager;
+
+import android.hardware.input.InputManager;
+
+import android.net.Uri;
+
+import android.os.BatteryManager;
+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;
+
+import android.widget.Toast;
+
+/* EmacsService is the service that starts the thread running Emacs
+ and handles requests by that Emacs instance. */
+
+public final class EmacsService extends Service
+{
+ public static final String TAG = "EmacsService";
+
+ /* The started Emacs service object. */
+ public static EmacsService SERVICE;
+
+ /* If non-NULL, an extra argument to pass to
+ `android_emacs_init'. */
+ public static String extraStartupArgument;
+
+ /* The thread running Emacs C code. */
+ private EmacsThread thread;
+
+ /* Handler used to run tasks on the main thread. */
+ private Handler handler;
+
+ /* Content resolver used to access URIs. */
+ private ContentResolver resolver;
+
+ /* 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;
+
+ /* Flag that says whether or not to print verbose debugging
+ information when responding to an input method. */
+ public static final boolean DEBUG_IC = false;
+
+ /* Flag that says whether or not to stringently check that only the
+ Emacs thread is performing drawing calls. */
+ private static final boolean DEBUG_THREADS = false;
+
+ /* Atomic integer used for synchronization between
+ icBeginSynchronous/icEndSynchronous and viewGetSelection.
+
+ Value is 0 if no query is in progress, 1 if viewGetSelection is
+ being called, and 2 if icBeginSynchronous was called. */
+ public static final AtomicInteger servicingQuery;
+
+ /* Thread used to query document providers, or null if it hasn't
+ been created yet. */
+ private EmacsSafThread storageThread;
+
+ static
+ {
+ servicingQuery = new AtomicInteger ();
+ };
+
+ /* Return the directory leading to the directory in which native
+ library files are stored on behalf of CONTEXT. */
+
+ public static String
+ getLibraryDirectory (Context context)
+ {
+ int apiLevel;
+
+ apiLevel = Build.VERSION.SDK_INT;
+
+ if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
+ return context.getApplicationInfo ().nativeLibraryDir;
+
+ return context.getApplicationInfo ().dataDir + "/lib";
+ }
+
+ @Override
+ 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 = ("This notification is displayed to keep Emacs"
+ + " running while it is in the background. You"
+ + " may disable it if you want;"
+ + " see (emacs)Android Environment.");
+ 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;
+ }
+
+ @Override
+ public IBinder
+ onBind (Intent intent)
+ {
+ 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 "";
+ }
+ }
+
+ @Override
+ public void
+ onCreate ()
+ {
+ final AssetManager manager;
+ Context app_context;
+ final String filesDir, libDir, cacheDir, classPath;
+ final double pixelDensityX;
+ final double pixelDensityY;
+ final double scaledDensity;
+ double tempScaledDensity;
+
+ SERVICE = this;
+ handler = new Handler (Looper.getMainLooper ());
+ manager = getAssets ();
+ app_context = getApplicationContext ();
+ metrics = getResources ().getDisplayMetrics ();
+ pixelDensityX = metrics.xdpi;
+ pixelDensityY = metrics.ydpi;
+ tempScaledDensity = ((metrics.scaledDensity
+ / metrics.density)
+ * pixelDensityX);
+ resolver = getContentResolver ();
+
+ /* If the density used to compute the text size is lesser than
+ 160, there's likely a bug with display density computation.
+ Reset it to 160 in that case.
+
+ Note that Android uses 160 ``dpi'' as the density where 1 point
+ corresponds to 1 pixel, not 72 or 96 as used elsewhere. This
+ difference is codified in PT_PER_INCH defined in font.h. */
+
+ if (tempScaledDensity < 160)
+ tempScaledDensity = 160;
+
+ /* scaledDensity is const as required to refer to it from within
+ the nested function below. */
+ scaledDensity = tempScaledDensity;
+
+ try
+ {
+ /* Configure Emacs with the asset manager and other necessary
+ parameters. */
+ filesDir = app_context.getFilesDir ().getCanonicalPath ();
+ libDir = getLibraryDirectory (this);
+ 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
+ + ", libDir = " + libDir + ", and classPath = " + classPath
+ + "; fileToOpen = " + EmacsOpenActivity.fileToOpen
+ + "; display density: " + pixelDensityX + " by "
+ + pixelDensityY + " scaled to " + scaledDensity);
+
+ /* Start the thread that runs Emacs. */
+ thread = new EmacsThread (this, new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ EmacsNative.setEmacsParams (manager, filesDir, libDir,
+ cacheDir, (float) pixelDensityX,
+ (float) pixelDensityY,
+ (float) scaledDensity,
+ classPath, EmacsService.this,
+ Build.VERSION.SDK_INT);
+ }
+ }, extraStartupArgument,
+ /* If any file needs to be opened, open it now. */
+ EmacsOpenActivity.fileToOpen);
+ thread.start ();
+ }
+ catch (IOException exception)
+ {
+ EmacsNative.emacsAbort ();
+ return;
+ }
+ }
+
+
+
+ /* Functions from here on must only be called from the Emacs
+ thread. */
+
+ public void
+ runOnUiThread (Runnable runnable)
+ {
+ handler.post (runnable);
+ }
+
+ public EmacsView
+ getEmacsView (final EmacsWindow window, final int visibility,
+ final boolean isFocusedByDefault)
+ {
+ Runnable runnable;
+ final EmacsHolder<EmacsView> view;
+
+ view = new EmacsHolder<EmacsView> ();
+
+ runnable = new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ synchronized (this)
+ {
+ view.thing = new EmacsView (window);
+ view.thing.setVisibility (visibility);
+
+ /* 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 ();
+ }
+ }
+ };
+
+ syncRunnable (runnable);
+ 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 ();
+ }
+ }
+ };
+
+ syncRunnable (runnable);
+ }
+
+
+
+ public static void
+ checkEmacsThread ()
+ {
+ if (DEBUG_THREADS)
+ {
+ if (Thread.currentThread () instanceof EmacsThread)
+ return;
+
+ throw new RuntimeException ("Emacs thread function"
+ + " called from other thread!");
+ }
+ }
+
+ /* These drawing functions must only be called from the Emacs
+ thread. */
+
+ public void
+ fillRectangle (EmacsDrawable drawable, EmacsGC gc,
+ int x, int y, int width, int height)
+ {
+ checkEmacsThread ();
+ EmacsFillRectangle.perform (drawable, gc, x, y,
+ width, height);
+ }
+
+ public void
+ fillPolygon (EmacsDrawable drawable, EmacsGC gc,
+ Point points[])
+ {
+ checkEmacsThread ();
+ EmacsFillPolygon.perform (drawable, gc, points);
+ }
+
+ public void
+ drawRectangle (EmacsDrawable drawable, EmacsGC gc,
+ int x, int y, int width, int height)
+ {
+ checkEmacsThread ();
+ EmacsDrawRectangle.perform (drawable, gc, x, y,
+ width, height);
+ }
+
+ public void
+ drawLine (EmacsDrawable drawable, EmacsGC gc,
+ int x, int y, int x2, int y2)
+ {
+ checkEmacsThread ();
+ EmacsDrawLine.perform (drawable, gc, x, y,
+ x2, y2);
+ }
+
+ public void
+ drawPoint (EmacsDrawable drawable, EmacsGC gc,
+ int x, int y)
+ {
+ checkEmacsThread ();
+ EmacsDrawPoint.perform (drawable, gc, x, y);
+ }
+
+ public void
+ clearWindow (EmacsWindow window)
+ {
+ checkEmacsThread ();
+ window.clearWindow ();
+ }
+
+ public void
+ clearArea (EmacsWindow window, int x, int y, int width,
+ int height)
+ {
+ checkEmacsThread ();
+ 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<EmacsWindow> 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 == null
+ ? 0 : (window.parent != null
+ ? window.parent.handle : 0));
+
+ 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
+ /* Android 4.0 and earlier don't support mouse input events at
+ all. */
+ < 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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ {
+ if (device.supportsSource (InputDevice.SOURCE_MOUSE))
+ return true;
+ }
+ else
+ {
+ /* `supportsSource' is only present on API level 21 and
+ later, but earlier versions provide a bit mask
+ containing each supported source. */
+
+ if ((device.getSources () & InputDevice.SOURCE_MOUSE) != 0)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public String
+ nameKeysym (int keysym)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
+ return KeyEvent.keyCodeToString (keysym);
+
+ return String.valueOf (keysym);
+ }
+
+
+
+ /* 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)
+ {
+ 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));
+ }
+ }
+
+ /* Ask the system to open the specified URL in an application that
+ understands how to open it.
+
+ If SEND, tell the system to also open applications that can
+ ``send'' the URL (through mail, for example), instead of only
+ those that can view the URL.
+
+ Value is NULL upon success, or a string describing the error
+ upon failure. */
+
+ public String
+ browseUrl (String url, boolean send)
+ {
+ Intent intent;
+ Uri uri;
+
+ try
+ {
+ /* Parse the URI. */
+ if (!send)
+ {
+ uri = Uri.parse (url);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ {
+ /* On Android 4.4 and later, check if URI is actually
+ a file name. If so, rewrite it into a content
+ provider URI, so that it can be accessed by other
+ programs. */
+
+ if (uri.getScheme ().equals ("file")
+ && uri.getPath () != null)
+ uri
+ = DocumentsContract.buildDocumentUri ("org.gnu.emacs",
+ uri.getPath ());
+ }
+
+ Log.d (TAG, ("browseUri: browsing " + url
+ + " --> " + uri.getPath ()
+ + " --> " + uri));
+
+ intent = new Intent (Intent.ACTION_VIEW, uri);
+ intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ else
+ {
+ intent = new Intent (Intent.ACTION_SEND);
+ intent.setType ("text/plain");
+ intent.putExtra (Intent.EXTRA_SUBJECT, "Sharing link");
+ intent.putExtra (Intent.EXTRA_TEXT, url);
+
+ /* Display a list of programs able to send this URL. */
+ intent = Intent.createChooser (intent, "Send");
+
+ /* Apparently flags need to be set after a choser is
+ created. */
+ intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ startActivity (intent);
+ }
+ catch (Exception e)
+ {
+ return e.toString ();
+ }
+
+ return null;
+ }
+
+ /* Get a SDK 11 ClipboardManager.
+
+ Android 4.0.x requires that this be called from the main
+ thread. */
+
+ public ClipboardManager
+ getClipboardManager ()
+ {
+ final EmacsHolder<ClipboardManager> manager;
+ Runnable runnable;
+
+ manager = new EmacsHolder<ClipboardManager> ();
+
+ runnable = new Runnable () {
+ public void
+ run ()
+ {
+ Object tem;
+
+ synchronized (this)
+ {
+ tem = getSystemService (Context.CLIPBOARD_SERVICE);
+ manager.thing = (ClipboardManager) tem;
+ notify ();
+ }
+ }
+ };
+
+ syncRunnable (runnable);
+ 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);
+ }
+
+ /* 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 ();
+ }
+
+
+
+ /* IMM functions such as `updateSelection' holds an internal lock
+ that is also taken before `onCreateInputConnection' (in
+ EmacsView.java) is called; when that then asks the UI thread for
+ the current selection, a dead lock results. To remedy this,
+ reply to any synchronous queries now -- and prohibit more queries
+ for the duration of `updateSelection' -- if EmacsView may have
+ been asking for the value of the region. */
+
+ public static void
+ icBeginSynchronous ()
+ {
+ /* Set servicingQuery to 2, so viewGetSelection knows it shouldn't
+ proceed. */
+
+ if (servicingQuery.getAndSet (2) == 1)
+ /* But if viewGetSelection is already in progress, answer it
+ first. */
+ EmacsNative.answerQuerySpin ();
+ }
+
+ public static void
+ icEndSynchronous ()
+ {
+ if (servicingQuery.getAndSet (0) != 2)
+ throw new RuntimeException ("incorrect value of `servicingQuery': "
+ + "likely 1");
+ }
+
+ public static int[]
+ viewGetSelection (short window)
+ {
+ int[] selection;
+
+ /* See if a query is already in progress from the other
+ direction. */
+ if (!servicingQuery.compareAndSet (0, 1))
+ return null;
+
+ /* Now call the regular getSelection. Note that this can't race
+ with answerQuerySpin, as `android_servicing_query' can never be
+ 2 when icBeginSynchronous is called, so a query will always be
+ started. */
+ selection = EmacsNative.getSelection (window);
+
+ /* Finally, clear servicingQuery if its value is still 1. If a
+ query has started from the other side, it ought to be 2. */
+
+ servicingQuery.compareAndSet (1, 0);
+ return selection;
+ }
+
+
+
+ public void
+ updateIC (EmacsWindow window, int newSelectionStart,
+ int newSelectionEnd, int composingRegionStart,
+ int composingRegionEnd)
+ {
+ if (DEBUG_IC)
+ Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
+ + " " + newSelectionEnd + " "
+ + composingRegionStart + " "
+ + composingRegionEnd));
+
+ icBeginSynchronous ();
+ window.view.imManager.updateSelection (window.view,
+ newSelectionStart,
+ newSelectionEnd,
+ composingRegionStart,
+ composingRegionEnd);
+ icEndSynchronous ();
+ }
+
+ public void
+ resetIC (EmacsWindow window, int icMode)
+ {
+ int oldMode;
+
+ if (DEBUG_IC)
+ Log.d (TAG, "resetIC: " + window + ", " + icMode);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ && (oldMode = window.view.getICMode ()) == icMode
+ /* Don't do this if there is currently no input
+ connection. */
+ && oldMode != IC_MODE_NULL)
+ {
+ if (DEBUG_IC)
+ Log.d (TAG, "resetIC: calling invalidateInput");
+
+ /* Android 33 and later allow the IM reset to be optimized out
+ and replaced by a call to `invalidateInput', which is much
+ faster, as it does not involve resetting the input
+ connection. */
+
+ icBeginSynchronous ();
+ window.view.imManager.invalidateInput (window.view);
+ icEndSynchronous ();
+
+ return;
+ }
+
+ window.view.setICMode (icMode);
+
+ icBeginSynchronous ();
+ window.view.icGeneration++;
+ window.view.imManager.restartInput (window.view);
+ icEndSynchronous ();
+ }
+
+ public void
+ updateCursorAnchorInfo (EmacsWindow window, float x,
+ float y, float yBaseline,
+ float yBottom)
+ {
+ CursorAnchorInfo info;
+ CursorAnchorInfo.Builder builder;
+ Matrix matrix;
+ int[] offsets;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ return;
+
+ offsets = new int[2];
+ builder = new CursorAnchorInfo.Builder ();
+ matrix = new Matrix (window.view.getMatrix ());
+ window.view.getLocationOnScreen (offsets);
+ matrix.postTranslate (offsets[0], offsets[1]);
+ builder.setMatrix (matrix);
+ builder.setInsertionMarkerLocation (x, y, yBaseline, yBottom,
+ 0);
+ info = builder.build ();
+
+
+
+ if (DEBUG_IC)
+ Log.d (TAG, ("updateCursorAnchorInfo: " + x + " " + y
+ + " " + yBaseline + "-" + yBottom));
+
+ icBeginSynchronous ();
+ window.view.imManager.updateCursorAnchorInfo (window.view, info);
+ icEndSynchronous ();
+ }
+
+
+
+ /* Content provider functions. */
+
+ /* 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");
+ 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");
+ }
+ 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 ();
+
+ return true;
+ }
+ catch (Exception exception)
+ {
+ /* Fall through. */
+ }
+
+ return false;
+ }
+
+ /* Build a content file name for URI.
+
+ Return a file name within the /contents/by-authority
+ pseudo-directory that `android_get_content_name' can then
+ transform back into an encoded URI.
+
+ A content name consists of any number of unencoded path segments
+ separated by `/' characters, possibly followed by a question mark
+ and an encoded query string. */
+
+ public static String
+ buildContentName (Uri uri)
+ {
+ StringBuilder builder;
+
+ builder = new StringBuilder ("/content/by-authority/");
+ builder.append (uri.getAuthority ());
+
+ /* First, append each path segment. */
+
+ for (String segment : uri.getPathSegments ())
+ {
+ /* FIXME: what if segment contains a slash character? */
+ builder.append ('/');
+ builder.append (uri.encode (segment));
+ }
+
+ /* Now, append the query string if necessary. */
+
+ if (uri.getEncodedQuery () != null)
+ builder.append ('?').append (uri.getEncodedQuery ());
+
+ return builder.toString ();
+ }
+
+
+
+ private long[]
+ queryBattery19 ()
+ {
+ IntentFilter filter;
+ Intent battery;
+ long capacity, chargeCounter, currentAvg, currentNow;
+ long status, remaining, plugged, temp;
+
+ filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
+ battery = registerReceiver (null, filter);
+
+ if (battery == null)
+ return null;
+
+ capacity = battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 0);
+ chargeCounter
+ = (battery.getIntExtra (BatteryManager.EXTRA_SCALE, 0)
+ / battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 100) * 100);
+ currentAvg = 0;
+ currentNow = 0;
+ status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
+ remaining = -1;
+ plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
+ temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
+
+ return new long[] { capacity, chargeCounter, currentAvg,
+ currentNow, remaining, status, plugged,
+ temp, };
+ }
+
+ /* 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, plugged, temp;
+ int prop;
+ IntentFilter filter;
+ Intent battery;
+
+ /* Android 4.4 or earlier require applications to use a different
+ API to query the battery status. */
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ return queryBattery19 ();
+
+ 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 ();
+
+ plugged = -1;
+ temp = -1;
+
+ /* Now obtain additional information from the battery manager. */
+
+ filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
+ battery = registerReceiver (null, filter);
+
+ if (battery != null)
+ {
+ plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
+ temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
+
+ /* Make status more reliable. */
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
+ status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
+ }
+
+ return new long[] { capacity, chargeCounter, currentAvg,
+ currentNow, remaining, status, plugged,
+ temp, };
+ }
+
+ public void
+ updateExtractedText (EmacsWindow window, ExtractedText text,
+ int token)
+ {
+ if (DEBUG_IC)
+ Log.d (TAG, "updateExtractedText: @" + token + ", " + text);
+
+ window.view.imManager.updateExtractedText (window.view,
+ token, text);
+ }
+
+
+
+ /* Document tree management functions. These functions shouldn't be
+ called before Android 5.0. */
+
+ /* Return an array of each document authority providing at least one
+ tree URI that Emacs holds the rights to persistently access. */
+
+ public String[]
+ getDocumentAuthorities ()
+ {
+ List<UriPermission> permissions;
+ HashSet<String> allProviders;
+ Uri uri;
+
+ permissions = resolver.getPersistedUriPermissions ();
+ allProviders = new HashSet<String> ();
+
+ for (UriPermission permission : permissions)
+ {
+ uri = permission.getUri ();
+
+ if (DocumentsContract.isTreeUri (uri)
+ && permission.isReadPermission ())
+ allProviders.add (uri.getAuthority ());
+ }
+
+ return allProviders.toArray (new String[0]);
+ }
+
+ /* Start a file chooser activity to request access to a directory
+ tree.
+
+ Value is 1 if the activity couldn't be started for some reason,
+ and 0 in any other case. */
+
+ public int
+ requestDirectoryAccess ()
+ {
+ Runnable runnable;
+ final EmacsHolder<Integer> rc;
+
+ /* Return 1 if Android is too old to support this feature. */
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ return 1;
+
+ rc = new EmacsHolder<Integer> ();
+ rc.thing = Integer.valueOf (1);
+
+ runnable = new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ EmacsActivity activity;
+ Intent intent;
+ int id;
+
+ synchronized (this)
+ {
+ /* Try to obtain an activity that will receive the
+ response from the file chooser dialog. */
+
+ if (EmacsActivity.focusedActivities.isEmpty ())
+ {
+ /* If focusedActivities is empty then this dialog
+ may have been displayed immediately after another
+ popup dialog was dismissed. Try the
+ EmacsActivity to be focused. */
+
+ activity = EmacsActivity.lastFocusedActivity;
+
+ if (activity == null)
+ {
+ /* Still no luck. Return failure. */
+ notify ();
+ return;
+ }
+ }
+ else
+ activity = EmacsActivity.focusedActivities.get (0);
+
+ /* Now create the intent. */
+ intent = new Intent (Intent.ACTION_OPEN_DOCUMENT_TREE);
+
+ try
+ {
+ id = EmacsActivity.ACCEPT_DOCUMENT_TREE;
+ activity.startActivityForResult (intent, id, null);
+ rc.thing = Integer.valueOf (0);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace ();
+ }
+
+ notify ();
+ }
+ }
+ };
+
+ syncRunnable (runnable);
+ return rc.thing;
+ }
+
+ /* Return an array of each tree provided by the document PROVIDER
+ that Emacs has permission to access.
+
+ Value is an array if the provider really does exist, NULL
+ otherwise. */
+
+ public String[]
+ getDocumentTrees (byte provider[])
+ {
+ String providerName;
+ List<String> treeList;
+ List<UriPermission> permissions;
+ Uri uri;
+
+ try
+ {
+ providerName = new String (provider, "US-ASCII");
+ }
+ catch (UnsupportedEncodingException exception)
+ {
+ return null;
+ }
+
+ permissions = resolver.getPersistedUriPermissions ();
+ treeList = new ArrayList<String> ();
+
+ for (UriPermission permission : permissions)
+ {
+ uri = permission.getUri ();
+
+ if (DocumentsContract.isTreeUri (uri)
+ && uri.getAuthority ().equals (providerName)
+ && permission.isReadPermission ())
+ /* Make sure the tree document ID is encoded. Refrain from
+ encoding characters such as +:&?#, since they don't
+ conflict with file name separators or other special
+ characters. */
+ treeList.add (Uri.encode (DocumentsContract.getTreeDocumentId (uri),
+ " +:&?#"));
+ }
+
+ return treeList.toArray (new String[0]);
+ }
+
+ /* Find the document ID of the file within TREE_URI designated by
+ NAME.
+
+ NAME is a ``file name'' comprised of the display names of
+ individual files. Each constituent component prior to the last
+ must name a directory file within TREE_URI.
+
+ Upon success, return 0 or 1 (contingent upon whether or not the
+ last component within NAME is a directory) and place the document
+ ID of the named file in ID_RETURN[0].
+
+ If the designated file can't be located, but each component of
+ NAME up to the last component can and is a directory, return -2
+ and the ID of the last component located in ID_RETURN[0].
+
+ If the designated file can't be located, return -1, or signal one
+ of OperationCanceledException, SecurityException,
+ FileNotFoundException, or UnsupportedOperationException. */
+
+ private int
+ documentIdFromName (String tree_uri, String name, String[] id_return)
+ {
+ /* Start the thread used to run SAF requests if it isn't already
+ running. */
+
+ if (storageThread == null)
+ {
+ storageThread = new EmacsSafThread (resolver);
+ storageThread.start ();
+ }
+
+ return storageThread.documentIdFromName (tree_uri, name,
+ id_return);
+ }
+
+ /* Return an encoded document URI representing a tree with the
+ specified IDENTIFIER supplied by the authority AUTHORITY.
+
+ Return null instead if Emacs does not have permanent access
+ to the specified document tree recorded on disk. */
+
+ public String
+ getTreeUri (String tree, String authority)
+ {
+ Uri uri, grantedUri;
+ List<UriPermission> permissions;
+
+ /* First, build the URI. */
+ tree = Uri.decode (tree);
+ uri = DocumentsContract.buildTreeDocumentUri (authority, tree);
+
+ /* Now, search for it within the list of persisted URI
+ permissions. */
+ permissions = resolver.getPersistedUriPermissions ();
+
+ for (UriPermission permission : permissions)
+ {
+ /* If the permission doesn't entitle Emacs to read access,
+ skip it. */
+
+ if (!permission.isReadPermission ())
+ continue;
+
+ grantedUri = permission.getUri ();
+
+ if (grantedUri.equals (uri))
+ return uri.toString ();
+ }
+
+ /* Emacs doesn't have permission to access this tree URI. */
+ return null;
+ }
+
+ /* Return file status for the document designated by the given
+ DOCUMENTID and tree URI. If DOCUMENTID is NULL, use the document
+ ID in URI itself.
+
+ Value is null upon failure, or an array of longs [MODE, SIZE,
+ MTIM] upon success, where MODE contains the file type and access
+ modes of the file as in `struct stat', SIZE is the size of the
+ file in BYTES or -1 if not known, and MTIM is the time of the
+ last modification to this file in milliseconds since 00:00,
+ January 1st, 1970.
+
+ OperationCanceledException and other typical exceptions may be
+ signaled upon receiving async input or other errors. */
+
+ public long[]
+ statDocument (String uri, String documentId)
+ {
+ /* Start the thread used to run SAF requests if it isn't already
+ running. */
+
+ if (storageThread == null)
+ {
+ storageThread = new EmacsSafThread (resolver);
+ storageThread.start ();
+ }
+
+ return storageThread.statDocument (uri, documentId);
+ }
+
+ /* Find out whether Emacs has access to the document designated by
+ the specified DOCUMENTID within the tree URI. If DOCUMENTID is
+ NULL, use the document ID in URI itself.
+
+ If WRITABLE, also check that the file is writable, which is true
+ if it is either a directory or its flags contains
+ FLAG_SUPPORTS_WRITE.
+
+ Value is 0 if the file is accessible, and one of the following if
+ not:
+
+ -1, if the file does not exist.
+ -2, if WRITABLE and the file is not writable.
+ -3, upon any other error.
+
+ In addition, arbitrary runtime exceptions (such as
+ SecurityException or UnsupportedOperationException) may be
+ thrown. */
+
+ public int
+ accessDocument (String uri, String documentId, boolean writable)
+ {
+ /* Start the thread used to run SAF requests if it isn't already
+ running. */
+
+ if (storageThread == null)
+ {
+ storageThread = new EmacsSafThread (resolver);
+ storageThread.start ();
+ }
+
+ return storageThread.accessDocument (uri, documentId, writable);
+ }
+
+ /* Open a cursor representing each entry within the directory
+ designated by the specified DOCUMENTID within the tree URI.
+
+ If DOCUMENTID is NULL, use the document ID within URI itself.
+ Value is NULL upon failure.
+
+ In addition, arbitrary runtime exceptions (such as
+ SecurityException or UnsupportedOperationException) may be
+ thrown. */
+
+ public Cursor
+ openDocumentDirectory (String uri, String documentId)
+ {
+ /* Start the thread used to run SAF requests if it isn't already
+ running. */
+
+ if (storageThread == null)
+ {
+ storageThread = new EmacsSafThread (resolver);
+ storageThread.start ();
+ }
+
+ return storageThread.openDocumentDirectory (uri, documentId);
+ }
+
+ /* Read a single directory entry from the specified CURSOR. Return
+ NULL if at the end of the directory stream, and a directory entry
+ with `d_name' set to NULL if an error occurs. */
+
+ public EmacsDirectoryEntry
+ readDirectoryEntry (Cursor cursor)
+ {
+ EmacsDirectoryEntry entry;
+ int index;
+ String name, type;
+
+ entry = new EmacsDirectoryEntry ();
+
+ while (true)
+ {
+ if (!cursor.moveToNext ())
+ return null;
+
+ /* First, retrieve the display name. */
+ index = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
+
+ if (index < 0)
+ /* Return an invalid directory entry upon failure. */
+ return entry;
+
+ try
+ {
+ name = cursor.getString (index);
+ }
+ catch (Exception exception)
+ {
+ return entry;
+ }
+
+ /* Skip this entry if its name cannot be represented. */
+
+ if (name.equals ("..") || name.equals (".") || name.contains ("/"))
+ continue;
+
+ /* Now, look for its type. */
+
+ index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+ if (index < 0)
+ /* Return an invalid directory entry upon failure. */
+ return entry;
+
+ try
+ {
+ type = cursor.getString (index);
+ }
+ catch (Exception exception)
+ {
+ return entry;
+ }
+
+ if (type != null
+ && type.equals (Document.MIME_TYPE_DIR))
+ entry.d_type = 1;
+ entry.d_name = name;
+ return entry;
+ }
+
+ /* Not reached. */
+ }
+
+ /* Open a file descriptor for a file document designated by
+ DOCUMENTID within the document tree identified by URI. If
+ TRUNCATE and the document already exists, truncate its contents
+ before returning.
+
+ On Android 9.0 and earlier, always open the document in
+ ``read-write'' mode; this instructs the document provider to
+ return a seekable file that is stored on disk and returns correct
+ file status.
+
+ Under newer versions of Android, open the document in a
+ non-writable mode if WRITE is false. This is possible because
+ these versions allow Emacs to explicitly request a seekable
+ on-disk file.
+
+ Value is NULL upon failure or a parcel file descriptor upon
+ success. Call `ParcelFileDescriptor.close' on this file
+ descriptor instead of using the `close' system call.
+
+ FileNotFoundException and/or SecurityException and
+ UnsupportedOperationException may be thrown upon failure. */
+
+ public ParcelFileDescriptor
+ openDocument (String uri, String documentId, boolean write,
+ boolean truncate)
+ {
+ /* Start the thread used to run SAF requests if it isn't already
+ running. */
+
+ if (storageThread == null)
+ {
+ storageThread = new EmacsSafThread (resolver);
+ storageThread.start ();
+ }
+
+ return storageThread.openDocument (uri, documentId, write,
+ truncate);
+ }
+
+ /* Create a new document with the given display NAME within the
+ directory identified by DOCUMENTID inside the document tree
+ designated by URI.
+
+ If DOCUMENTID is NULL, create the document inside the root of
+ that tree.
+
+ Either FileNotFoundException, SecurityException or
+ UnsupportedOperationException may be thrown upon failure.
+
+ Return the document ID of the new file upon success, NULL
+ otherwise. */
+
+ public String
+ createDocument (String uri, String documentId, String name)
+ throws FileNotFoundException
+ {
+ String mimeType, separator, mime, extension;
+ int index;
+ MimeTypeMap singleton;
+ Uri treeUri, directoryUri, docUri;
+
+ /* Try to get the MIME type for this document.
+ Default to ``application/octet-stream''. */
+
+ mimeType = "application/octet-stream";
+
+ /* Abuse WebView stuff to get the file's MIME type. */
+
+ index = name.lastIndexOf ('.');
+
+ if (index > 0)
+ {
+ singleton = MimeTypeMap.getSingleton ();
+ extension = name.substring (index + 1);
+ mime = singleton.getMimeTypeFromExtension (extension);
+
+ if (mime != null)
+ mimeType = mime;
+ }
+
+ /* Now parse URI. */
+ treeUri = Uri.parse (uri);
+
+ if (documentId == null)
+ documentId = DocumentsContract.getTreeDocumentId (treeUri);
+
+ /* And build a file URI referring to the directory. */
+
+ directoryUri
+ = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
+ documentId);
+
+ docUri = DocumentsContract.createDocument (resolver,
+ directoryUri,
+ mimeType, name);
+
+ if (docUri == null)
+ return null;
+
+ /* Invalidate the file status of the containing directory. */
+
+ if (storageThread != null)
+ storageThread.postInvalidateStat (treeUri, documentId);
+
+ /* Return the ID of the new document. */
+ return DocumentsContract.getDocumentId (docUri);
+ }
+
+ /* Like `createDocument', but create a directory instead of an
+ ordinary document. */
+
+ public String
+ createDirectory (String uri, String documentId, String name)
+ throws FileNotFoundException
+ {
+ int index;
+ Uri treeUri, directoryUri, docUri;
+
+ /* Now parse URI. */
+ treeUri = Uri.parse (uri);
+
+ if (documentId == null)
+ documentId = DocumentsContract.getTreeDocumentId (treeUri);
+
+ /* And build a file URI referring to the directory. */
+
+ directoryUri
+ = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
+ documentId);
+
+ /* If name ends with a directory separator character, delete
+ it. */
+
+ if (name.endsWith ("/"))
+ name = name.substring (0, name.length () - 1);
+
+ /* From Android's perspective, directories are just ordinary
+ documents with the `MIME_TYPE_DIR' type. */
+
+ docUri = DocumentsContract.createDocument (resolver,
+ directoryUri,
+ Document.MIME_TYPE_DIR,
+ name);
+
+ if (docUri == null)
+ return null;
+
+ /* Return the ID of the new document, but first invalidate the
+ state of the containing directory. */
+
+ if (storageThread != null)
+ storageThread.postInvalidateStat (treeUri, documentId);
+
+ return DocumentsContract.getDocumentId (docUri);
+ }
+
+ /* Delete the document identified by ID from the document tree
+ identified by URI. Return 0 upon success and -1 upon
+ failure.
+
+ NAME should be the name of the document being deleted, and is
+ used to invalidate the cache. */
+
+ public int
+ deleteDocument (String uri, String id, String name)
+ throws FileNotFoundException
+ {
+ Uri uriObject, tree;
+
+ tree = Uri.parse (uri);
+ uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, id);
+
+ if (DocumentsContract.deleteDocument (resolver, uriObject))
+ {
+ if (storageThread != null)
+ storageThread.postInvalidateCache (tree, id, name);
+
+ return 0;
+ }
+
+ return -1;
+ }
+
+ /* Rename the document designated by DOCID inside the directory tree
+ identified by URI, which should be within the directory
+ designated by DIR, to NAME. If the file can't be renamed because
+ it doesn't support renaming, return -1, 0 otherwise. */
+
+ public int
+ renameDocument (String uri, String docId, String dir, String name)
+ throws FileNotFoundException
+ {
+ Uri tree, uriObject;
+
+ tree = Uri.parse (uri);
+ uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, docId);
+
+ if (DocumentsContract.renameDocument (resolver, uriObject,
+ name)
+ != null)
+ {
+ /* Invalidate the cache. */
+ if (storageThread != null)
+ storageThread.postInvalidateCacheDir (tree, docId,
+ name);
+ return 0;
+ }
+
+ /* Handle errors specially, so `android_saf_rename_document' can
+ return ENXDEV. */
+ return -1;
+ }
+
+ /* Move the document designated by DOCID from the directory under
+ DIR_NAME designated by SRCID to the directory designated by
+ DSTID. If the ID of the document being moved changes as a
+ consequence of the movement, return the new ID, else NULL.
+
+ URI is the document tree containing all three documents. */
+
+ public String
+ moveDocument (String uri, String docId, String dirName,
+ String dstId, String srcId)
+ throws FileNotFoundException
+ {
+ Uri uri1, docId1, dstId1, srcId1;
+ Uri name;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+ throw new UnsupportedOperationException ("Documents aren't capable"
+ + " of being moved on Android"
+ + " versions before 7.0.");
+
+ uri1 = Uri.parse (uri);
+ docId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, docId);
+ dstId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, dstId);
+ srcId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, srcId);
+
+ /* Move the document; this function returns the new ID of the
+ document should it change. */
+ name = DocumentsContract.moveDocument (resolver, docId1,
+ srcId1, dstId1);
+
+ /* Now invalidate the caches for both DIRNAME and DOCID. */
+
+ if (storageThread != null)
+ {
+ storageThread.postInvalidateCacheDir (uri1, docId, dirName);
+
+ /* Invalidate the stat cache entries for both the source and
+ destination directories, since their contents have
+ changed. */
+ storageThread.postInvalidateStat (uri1, dstId);
+ storageThread.postInvalidateStat (uri1, srcId);
+ }
+
+ return (name != null
+ ? DocumentsContract.getDocumentId (name)
+ : null);
+ }
+
+ /* Return if there is a content provider by the name of AUTHORITY
+ supplying at least one tree URI Emacs retains persistent rights
+ to access. */
+
+ public boolean
+ validAuthority (String authority)
+ {
+ List<UriPermission> permissions;
+ Uri uri;
+
+ permissions = resolver.getPersistedUriPermissions ();
+
+ for (UriPermission permission : permissions)
+ {
+ uri = permission.getUri ();
+
+ if (DocumentsContract.isTreeUri (uri)
+ && permission.isReadPermission ()
+ && uri.getAuthority ().equals (authority))
+ return true;
+ }
+
+ return false;
+ }
+};