emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/android a158c1d5b96 06/10: Update Android port


From: Po Lu
Subject: feature/android a158c1d5b96 06/10: Update Android port
Date: Wed, 15 Feb 2023 09:53:30 -0500 (EST)

branch: feature/android
commit a158c1d5b964fda36f752998cef076d581dc4df4
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    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.
---
 configure.ac                                 |    2 +-
 doc/emacs/input.texi                         |   17 +
 doc/lispref/commands.texi                    |    9 +
 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 +-
 lisp/bindings.el                             |    3 +
 src/alloc.c                                  |   14 +
 src/android.c                                |  295 +++++++-
 src/android.h                                |   10 +
 src/androidgui.h                             |   60 ++
 src/androidterm.c                            | 1037 +++++++++++++++++++++++++-
 src/coding.c                                 |    2 +-
 src/frame.c                                  |   17 +
 src/frame.h                                  |   62 ++
 src/keyboard.c                               |   50 ++
 src/lisp.h                                   |    3 +
 src/process.c                                |    1 +
 src/textconv.c                               |  906 +++++++++++++++++++++-
 src/textconv.h                               |   38 +-
 src/xdisp.c                                  |   28 +
 src/xfns.c                                   |    2 +-
 src/xterm.c                                  |    2 +-
 27 files changed, 2787 insertions(+), 506 deletions(-)

diff --git a/configure.ac b/configure.ac
index 44529260892..fc95bcd09b6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7217,7 +7217,7 @@ if test "$window_system" != "none"; then
     [Define if you poll periodically to detect C-g.])
   WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o"
 
-  if test "$window_system" = "x11"; then
+  if test "$window_system" = "x11" || test "$REALLY_ANDROID" = "yes"; then
     AC_DEFINE([HAVE_TEXT_CONVERSION], [1],
       [Define if the window system has text conversion support.])
     WINDOW_SYSTEM_OBJ="$WINDOW_SYSTEM_OBJ textconv.o"
diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi
index b306a63b6cb..2463a75edcd 100644
--- a/doc/emacs/input.texi
+++ b/doc/emacs/input.texi
@@ -109,3 +109,20 @@ Emacs quitting.  @xref{Quitting}.
   The exact button is used to do this varies by system: on X, it is
 defined in the variable @code{x-quit-keysym}, and on Android, it is
 always the volume down button.
+
+@cindex text conversion, keyboards
+  Most input methods designed to work with on-screen keyboards perform
+buffer edits differently from desktop input methods.
+
+  On a conventional desktop windowing system, an input method will
+simply display the contents of any on going character compositions on
+screen, and send the appropriate key events to Emacs after completion.
+
+  However, on screen keyboard input methods directly perform edits to
+the selected window of each frame; this is known as ``text
+conversion'', or ``string conversion'' under the X Window System.
+
+  Text conversion is performed asynchronously whenever Emacs receives
+a request to perform the conversion from the input method.  After the
+conversion completes, a @code{text-conversion} event is sent.
+@xref{Misc Events,,, elisp, the Emacs Reference Manual}.
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 2c0787521a5..2807d3d61b2 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -2200,6 +2200,15 @@ the buffer in which the xwidget will be displayed, using
 A few other event types represent occurrences within the system.
 
 @table @code
+@cindex @code{text-conversion} event
+@item text-conversion
+This kind of event is sent @strong{after} a system-wide input method
+performs an edit to one or more buffers.
+
+Once the event is sent, the input method may already have made changes
+to multiple frames. @c TODO: allow querying which frames to which
+@c changes have been made.
+
 @cindex @code{delete-frame} event
 @item (delete-frame (@var{frame}))
 This kind of event indicates that the user gave the window manager
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 <https://www.gnu.org/licenses/>.  */
-
-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;
+  }
 };
diff --git a/lisp/bindings.el b/lisp/bindings.el
index c77b64c05da..057c870958d 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -1521,6 +1521,9 @@ if `inhibit-field-text-motion' is non-nil."
 (define-key special-event-map [sigusr1] 'ignore)
 (define-key special-event-map [sigusr2] 'ignore)
 
+;; Text conversion
+(define-key global-map [text-conversion] 'ignore)
+
 ;; Don't look for autoload cookies in this file.
 ;; Local Variables:
 ;; no-update-autoloads: t
diff --git a/src/alloc.c b/src/alloc.c
index bc43f22005d..6d8658e7bb0 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -6939,6 +6939,11 @@ static void
 mark_frame (struct Lisp_Vector *ptr)
 {
   struct frame *f = (struct frame *) ptr;
+#ifdef HAVE_TEXT_CONVERSION
+  struct text_conversion_action *tem;
+#endif
+
+
   mark_vectorlike (&ptr->header);
   mark_face_cache (f->face_cache);
 #ifdef HAVE_WINDOW_SYSTEM
@@ -6950,6 +6955,15 @@ mark_frame (struct Lisp_Vector *ptr)
         mark_vectorlike (&font->header);
     }
 #endif
+
+#ifdef HAVE_TEXT_CONVERSION
+  mark_object (f->conversion.compose_region_start);
+  mark_object (f->conversion.compose_region_end);
+  mark_object (f->conversion.compose_region_overlay);
+
+  for (tem = f->conversion.actions; tem; tem = tem->next)
+    mark_object (tem->data);
+#endif
 }
 
 static void
diff --git a/src/android.c b/src/android.c
index 479eb9b10d4..8f446224dab 100644
--- a/src/android.c
+++ b/src/android.c
@@ -98,6 +98,8 @@ struct android_emacs_service
   jmethodID sync;
   jmethodID browse_url;
   jmethodID restart_emacs;
+  jmethodID update_ic;
+  jmethodID reset_ic;
 };
 
 struct android_emacs_pixmap
@@ -207,7 +209,7 @@ static struct android_emacs_window window_class;
 
 /* The last event serial used.  This is a 32 bit value, but it is
    stored in unsigned long to be consistent with X.  */
-static unsigned int event_serial;
+unsigned int event_serial;
 
 /* Unused pointer used to control compiler optimizations.  */
 void *unused_pointer;
@@ -408,6 +410,10 @@ android_handle_sigusr1 (int sig, siginfo_t *siginfo, void 
*arg)
 
 #endif
 
+/* Semaphore used to indicate completion of a query.
+   This should ideally be defined further down.  */
+static sem_t android_query_sem;
+
 /* Set up the global event queue by initializing the mutex and two
    condition variables, and the linked list of events.  This must be
    called before starting the Emacs thread.  Also, initialize the
@@ -438,6 +444,7 @@ android_init_events (void)
 
   sem_init (&android_pselect_sem, 0, 0);
   sem_init (&android_pselect_start_sem, 0, 0);
+  sem_init (&android_query_sem, 0, 0);
 
   event_queue.events.next = &event_queue.events;
   event_queue.events.last = &event_queue.events;
@@ -538,7 +545,7 @@ android_next_event (union android_event *event_return)
   pthread_mutex_unlock (&event_queue.mutex);
 }
 
-static void
+void
 android_write_event (union android_event *event)
 {
   struct android_event_container *container;
@@ -576,6 +583,10 @@ android_select (int nfds, fd_set *readfds, fd_set 
*writefds,
   static char byte;
 #endif
 
+  /* Check for and run anything the UI thread wants to run on the main
+     thread.  */
+  android_check_query ();
+
   pthread_mutex_lock (&event_queue.mutex);
 
   if (event_queue.num_events)
@@ -634,6 +645,10 @@ android_select (int nfds, fd_set *readfds, fd_set 
*writefds,
   if (nfds_return < 0)
     errno = EINTR;
 
+  /* Now check for and run anything the UI thread wants to run in the
+     main thread.  */
+  android_check_query ();
+
   return nfds_return;
 }
 
@@ -1431,7 +1446,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
 
   /* This may be called from multiple threads.  setEmacsParams should
      only ever be called once.  */
-  if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_RELAXED))
+  if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_SEQ_CST))
     {
       ANDROID_THROW (env, "java/lang/IllegalArgumentException",
                     "Emacs was already initialized!");
@@ -1705,6 +1720,10 @@ android_init_emacs_service (void)
   FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;)"
               "Ljava/lang/String;");
   FIND_METHOD (restart_emacs, "restartEmacs", "()V");
+  FIND_METHOD (update_ic, "updateIC",
+              "(Lorg/gnu/emacs/EmacsWindow;IIII)V");
+  FIND_METHOD (reset_ic, "resetIC",
+              "(Lorg/gnu/emacs/EmacsWindow;I)V");
 #undef FIND_METHOD
 }
 
@@ -1834,7 +1853,7 @@ android_init_emacs_window (void)
 #undef FIND_METHOD
 }
 
-extern JNIEXPORT void JNICALL
+JNIEXPORT void JNICALL
 NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
                         jobject dump_file_object, jint api_level)
 {
@@ -1928,19 +1947,19 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, 
jarray argv,
   emacs_abort ();
 }
 
-extern JNIEXPORT void JNICALL
+JNIEXPORT void JNICALL
 NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object)
 {
   emacs_abort ();
 }
 
-extern JNIEXPORT void JNICALL
+JNIEXPORT void JNICALL
 NATIVE_NAME (quit) (JNIEnv *env, jobject object)
 {
   Vquit_flag = Qt;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
                                   jshort window, jlong time,
                                   jint x, jint y, jint width,
@@ -1961,7 +1980,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
                            jshort window, jlong time,
                            jint state, jint keycode,
@@ -1981,7 +2000,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
                              jshort window, jlong time,
                              jint state, jint keycode,
@@ -2001,7 +2020,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
                           jshort window, jlong time)
 {
@@ -2016,7 +2035,7 @@ NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
                            jshort window, jlong time)
 {
@@ -2031,7 +2050,7 @@ NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
   return ++event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
                                jshort window, jint action)
 {
@@ -2046,7 +2065,7 @@ NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
                               jshort window, jint x, jint y,
                               jlong time)
@@ -2064,7 +2083,7 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
                               jshort window, jint x, jint y,
                               jlong time)
@@ -2082,7 +2101,7 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
                                jshort window, jint x, jint y,
                                jlong time)
@@ -2100,7 +2119,7 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
                               jshort window, jint x, jint y,
                               jlong time, jint state,
@@ -2121,7 +2140,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
                                 jshort window, jint x, jint y,
                                 jlong time, jint state,
@@ -2142,7 +2161,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
                             jshort window, jint x, jint y,
                             jlong time, jint pointer_id)
@@ -2161,7 +2180,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
                           jshort window, jint x, jint y,
                           jlong time, jint pointer_id)
@@ -2180,7 +2199,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
                             jshort window, jint x, jint y,
                             jlong time, jint pointer_id)
@@ -2199,7 +2218,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
                         jshort window, jint x, jint y,
                         jlong time, jint state,
@@ -2221,7 +2240,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
                             jshort window)
 {
@@ -2235,7 +2254,7 @@ NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
                               jshort window)
 {
@@ -2249,7 +2268,7 @@ NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
                               jshort window, jint menu_event_id)
 {
@@ -2264,7 +2283,7 @@ NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject 
object,
   return event_serial;
 }
 
-extern JNIEXPORT jlong JNICALL
+JNIEXPORT jlong JNICALL
 NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
                          jshort window, jint x, jint y,
                          jint width, jint height)
@@ -2283,6 +2302,23 @@ NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
   return event_serial;
 }
 
+/* Forward declarations of deadlock prevention functions.  */
+
+static void android_begin_query (void);
+static void android_end_query (void);
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (beginSynchronous) (JNIEnv *env, jobject object)
+{
+  android_begin_query ();
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (endSynchronous) (JNIEnv *env, jobject object)
+{
+  android_end_query ();
+}
+
 #ifdef __clang__
 #pragma clang diagnostic pop
 #else
@@ -5155,6 +5191,215 @@ android_get_current_api_level (void)
 
 
 
+/* Whether or not a query is currently being made.  */
+static bool android_servicing_query;
+
+/* Function that is waiting to be run in the Emacs thread.  */
+static void (*android_query_function) (void *);
+
+/* Context for that function.  */
+static void *android_query_context;
+
+/* Deadlock protection.  The UI thread and the Emacs thread must
+   sometimes make synchronous queries to each other, which are
+   normally answered inside each thread's respective event loop.
+   Deadlocks can happen when both threads simultaneously make such
+   synchronous queries and block waiting for each others responses.
+
+   The Emacs thread can be interrupted to service any queries made by
+   the UI thread, but is not possible the other way around.
+
+   To avoid such deadlocks, an atomic counter is provided.  This
+   counter is incremented every time a query starts, and is set to
+   zerp every time one ends.  If the UI thread tries to make a query
+   and sees that the counter is non-zero, it simply returns so that
+   its event loop can proceed to perform and respond to the query.  If
+   the Emacs thread sees the same thing, then it stops to service all
+   queries being made by the input method, then proceeds to make its
+   query.  */
+
+/* Run any function that the UI thread has asked to run, and then
+   signal its completion.  */
+
+void
+android_check_query (void)
+{
+  void (*proc) (void *);
+  void *closure;
+
+  if (!__atomic_load_n (&android_servicing_query, __ATOMIC_SEQ_CST))
+    return;
+
+  /* First, load the procedure and closure.  */
+  __atomic_load (&android_query_context, &closure, __ATOMIC_SEQ_CST);
+  __atomic_load (&android_query_function, &proc, __ATOMIC_SEQ_CST);
+
+  if (!proc)
+    return;
+
+  proc (closure);
+
+  /* Finish the query.  */
+  __atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST);
+  __atomic_store_n (&android_query_function, NULL, __ATOMIC_SEQ_CST);
+  __atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST);
+
+  /* Signal completion.  */
+  sem_post (&android_query_sem);
+}
+
+/* Notice that the Emacs thread will start blocking waiting for a
+   response from the UI thread.  Process any pending queries from the
+   UI thread.
+
+   This function may be called from Java.  */
+
+static void
+android_begin_query (void)
+{
+  if (__atomic_test_and_set (&android_servicing_query,
+                            __ATOMIC_SEQ_CST))
+    {
+      /* Answer the query that is currently being made.  */
+      assert (android_query_function != NULL);
+      android_check_query ();
+
+      /* Wait for that query to complete.  */
+      while (__atomic_load_n (&android_servicing_query,
+                             __ATOMIC_SEQ_CST))
+       ;;
+    }
+}
+
+/* Notice that a query has stopped.  This function may be called from
+   Java.  */
+
+static void
+android_end_query (void)
+{
+  __atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST);
+}
+
+/* Synchronously ask the Emacs thread to run the specified PROC with
+   the given CLOSURE.  Return if this fails, or once PROC is run.
+
+   PROC may be run from inside maybe_quit.
+
+   It is not okay to run Lisp code which signals or performs non
+   trivial tasks inside PROC.
+
+   Return 1 if the Emacs thread is currently waiting for the UI thread
+   to respond and PROC could not be run, or 0 otherwise.  */
+
+int
+android_run_in_emacs_thread (void (*proc) (void *), void *closure)
+{
+  union android_event event;
+
+  event.xaction.type = ANDROID_WINDOW_ACTION;
+  event.xaction.serial = ++event_serial;
+  event.xaction.window = 0;
+  event.xaction.action = 0;
+
+  /* Set android_query_function and android_query_context.  */
+  __atomic_store_n (&android_query_context, closure, __ATOMIC_SEQ_CST);
+  __atomic_store_n (&android_query_function, proc, __ATOMIC_SEQ_CST);
+
+  /* Don't allow deadlocks to happen; make sure the Emacs thread is
+     not waiting for something to be done.  */
+
+  if (__atomic_test_and_set (&android_servicing_query,
+                            __ATOMIC_SEQ_CST))
+    {
+      __atomic_store_n (&android_query_context, NULL,
+                       __ATOMIC_SEQ_CST);
+      __atomic_store_n (&android_query_function, NULL,
+                       __ATOMIC_SEQ_CST);
+
+      return 1;
+    }
+
+  /* Send a dummy event.  `android_check_query' will be called inside
+     wait_reading_process_output after the event arrives.
+
+     Otherwise, android_select will call android_check_thread the next
+     time it is entered.  */
+  android_write_event (&event);
+
+  /* Start waiting for the function to be executed.  */
+  while (sem_wait (&android_query_sem) < 0)
+    ;;
+
+  return 0;
+}
+
+
+
+/* Input method related functions.  */
+
+/* Change WINDOW's active selection to the characters between
+   SELECTION_START and SELECTION_END.
+
+   Also, update the composing region to COMPOSING_REGION_START and
+   COMPOSING_REGION_END.
+
+   If any value cannot fit in jint, then the behavior of the input
+   method is undefined.  */
+
+void
+android_update_ic (android_window window, ptrdiff_t selection_start,
+                  ptrdiff_t selection_end, ptrdiff_t composing_region_start,
+                  ptrdiff_t composing_region_end)
+{
+  jobject object;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      emacs_service,
+                                      service_class.update_ic,
+                                      object,
+                                      (jint) selection_start,
+                                      (jint) selection_end,
+                                      (jint) composing_region_start,
+                                      (jint) composing_region_end);
+  android_exception_check ();
+}
+
+/* Reinitialize any ongoing input method connection on WINDOW.
+
+   Any input method that is connected to WINDOW will invalidate its
+   cache of the buffer contents.
+
+   MODE controls certain aspects of the input method's behavior:
+
+     - If MODE is ANDROID_IC_MODE_NULL, the input method will be
+       deactivated, and an ASCII only keyboard will be displayed
+       instead.
+
+     - If MODE is ANDROID_IC_MODE_ACTION, the input method will
+       edit text normally, but send ``return'' as a key event.
+       This is useful inside the mini buffer.
+
+     - If MODE is ANDROID_IC_MODE_TEXT, the input method is free
+       to behave however it wants.  */
+
+void
+android_reset_ic (android_window window, enum android_ic_mode mode)
+{
+  jobject object;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      emacs_service,
+                                      service_class.reset_ic,
+                                      object, (jint) mode);
+  android_exception_check ();
+}
+
+
+
 #else /* ANDROID_STUBIFY */
 
 /* X emulation functions for Android.  */
diff --git a/src/android.h b/src/android.h
index 9006f5f34c5..ec4fa33dfc3 100644
--- a/src/android.h
+++ b/src/android.h
@@ -108,6 +108,16 @@ extern void android_closedir (struct android_dir *);
 
 extern Lisp_Object android_browse_url (Lisp_Object);
 
+
+
+/* Event loop functions.  */
+
+extern void android_check_query (void);
+extern int android_run_in_emacs_thread (void (*) (void *), void *);
+extern void android_write_event (union android_event *);
+
+extern unsigned int event_serial;
+
 #endif
 
 /* JNI functions should not be built when Emacs is stubbed out for the
diff --git a/src/androidgui.h b/src/androidgui.h
index 1f0d34e67aa..25dc6754fff 100644
--- a/src/androidgui.h
+++ b/src/androidgui.h
@@ -235,6 +235,7 @@ enum android_event_type
     ANDROID_DEICONIFIED,
     ANDROID_CONTEXT_MENU,
     ANDROID_EXPOSE,
+    ANDROID_INPUT_METHOD,
   };
 
 struct android_any_event
@@ -419,6 +420,52 @@ struct android_menu_event
   int menu_event_id;
 };
 
+enum android_ime_operation
+  {
+    ANDROID_IME_COMMIT_TEXT,
+    ANDROID_IME_DELETE_SURROUNDING_TEXT,
+    ANDROID_IME_FINISH_COMPOSING_TEXT,
+    ANDROID_IME_SET_COMPOSING_TEXT,
+    ANDROID_IME_SET_COMPOSING_REGION,
+    ANDROID_IME_SET_POINT,
+    ANDROID_IME_START_BATCH_EDIT,
+    ANDROID_IME_END_BATCH_EDIT,
+  };
+
+struct android_ime_event
+{
+  /* Type of the event.  */
+  enum android_event_type type;
+
+  /* The event serial.  */
+  unsigned long serial;
+
+  /* The associated window.  */
+  android_window window;
+
+  /* What operation is being performed.  */
+  enum android_ime_operation operation;
+
+  /* The details of the operation.  START and END provide buffer
+     indices, and may actually mean ``left'' and ``right''.  */
+  ptrdiff_t start, end, position;
+
+  /* The number of characters in TEXT.  */
+  size_t length;
+
+  /* TEXT is either NULL, or a pointer to LENGTH bytes of malloced
+     UTF-16 encoded text that must be decoded by Emacs.
+
+     POSITION is where point should end up after the text is
+     committed, relative to TEXT.  If POSITION is less than 0, it is
+     relative to TEXT's start; otherwise, it is relative to its
+     end.  */
+  unsigned short *text;
+
+  /* Value to set the counter to after the operation completes.  */
+  unsigned long counter;
+};
+
 union android_event
 {
   enum android_event_type type;
@@ -447,6 +494,9 @@ union android_event
 
   /* This is only used to transmit selected menu items.  */
   struct android_menu_event menu;
+
+  /* This is used to dispatch input method editing requests.  */
+  struct android_ime_event ime;
 };
 
 enum
@@ -463,6 +513,13 @@ enum android_lookup_status
     ANDROID_LOOKUP_BOTH,
   };
 
+enum android_ic_mode
+  {
+    ANDROID_IC_MODE_NULL   = 0,
+    ANDROID_IC_MODE_ACTION = 1,
+    ANDROID_IC_MODE_TEXT   = 2,
+  };
+
 extern int android_pending (void);
 extern void android_next_event (union android_event *);
 
@@ -554,6 +611,9 @@ extern void android_sync (void);
 extern int android_wc_lookup_string (android_key_pressed_event *,
                                     wchar_t *, int, int *,
                                     enum android_lookup_status *);
+extern void android_update_ic (android_window, ptrdiff_t, ptrdiff_t,
+                              ptrdiff_t, ptrdiff_t);
+extern void android_reset_ic (android_window, enum android_ic_mode);
 
 #endif
 
diff --git a/src/androidterm.c b/src/androidterm.c
index a57d5623c66..a44bae954da 100644
--- a/src/androidterm.c
+++ b/src/androidterm.c
@@ -20,6 +20,9 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <config.h>
 #include <stdio.h>
 #include <math.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <semaphore.h>
 
 #include "lisp.h"
 #include "androidterm.h"
@@ -28,6 +31,8 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "android.h"
 #include "buffer.h"
 #include "window.h"
+#include "textconv.h"
+#include "coding.h"
 
 /* This is a chain of structures for all the X displays currently in
    use.  */
@@ -59,6 +64,12 @@ enum
     ANDROID_EVENT_DROP,
   };
 
+/* Find the frame whose window has the identifier WDESC.
+
+   This is like x_window_to_frame in xterm.c, except that DPYINFO may
+   be NULL, as there is only at most one Android display, and is only
+   specified in order to stay consistent with X.  */
+
 static struct frame *
 android_window_to_frame (struct android_display_info *dpyinfo,
                         android_window wdesc)
@@ -73,7 +84,7 @@ android_window_to_frame (struct android_display_info *dpyinfo,
     {
       f = XFRAME (frame);
 
-      if (!FRAME_ANDROID_P (f) || FRAME_DISPLAY_INFO (f) != dpyinfo)
+      if (!FRAME_ANDROID_P (f))
        continue;
 
       if (FRAME_ANDROID_WINDOW (f) == wdesc)
@@ -527,6 +538,103 @@ android_find_tool (struct frame *f, int pointer_id)
   return NULL;
 }
 
+/* Decode STRING, an array of N little endian UTF-16 characters, into
+   a Lisp string.  Return Qnil if the string is too large, and the
+   encoded string otherwise.  */
+
+static Lisp_Object
+android_decode_utf16 (unsigned short *utf16, size_t n)
+{
+  struct coding_system coding;
+  ptrdiff_t size;
+
+  if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
+    return Qnil;
+
+  /* Set up the coding system.  Decoding a UTF-16 string (with no BOM)
+     should not signal.  */
+
+  memset (&coding, 0, sizeof coding);
+
+  setup_coding_system (Qutf_16le, &coding);
+  coding.source = (const unsigned char *) utf16;
+  decode_coding_object (&coding, Qnil, 0, 0, size,
+                       size, Qt);
+
+  return coding.dst_object;
+}
+
+/* Handle a single input method event EVENT, delivered to the frame
+   F.
+
+   Perform the text conversion action specified inside.  */
+
+static void
+android_handle_ime_event (union android_event *event, struct frame *f)
+{
+  Lisp_Object text;
+
+  /* First, decode the text if necessary.  */
+
+  switch (event->ime.operation)
+    {
+    case ANDROID_IME_COMMIT_TEXT:
+    case ANDROID_IME_FINISH_COMPOSING_TEXT:
+    case ANDROID_IME_SET_COMPOSING_TEXT:
+      text = android_decode_utf16 (event->ime.text,
+                                  event->ime.length);
+      xfree (event->ime.text);
+      break;
+
+    default:
+      break;
+    }
+
+  /* Finally, perform the appropriate conversion action.  */
+
+  switch (event->ime.operation)
+    {
+    case ANDROID_IME_COMMIT_TEXT:
+      commit_text (f, text, event->ime.position,
+                  event->ime.counter);
+      break;
+
+    case ANDROID_IME_DELETE_SURROUNDING_TEXT:
+      delete_surrounding_text (f, event->ime.start,
+                              event->ime.end,
+                              event->ime.counter);
+      break;
+
+    case ANDROID_IME_FINISH_COMPOSING_TEXT:
+      finish_composing_text (f, event->ime.counter);
+      break;
+
+    case ANDROID_IME_SET_COMPOSING_TEXT:
+      set_composing_text (f, text, event->ime.position,
+                         event->ime.counter);
+      break;
+
+    case ANDROID_IME_SET_COMPOSING_REGION:
+      set_composing_region (f, event->ime.start,
+                           event->ime.end,
+                           event->ime.counter);
+      break;
+
+    case ANDROID_IME_SET_POINT:
+      textconv_set_point (f, event->ime.position,
+                         event->ime.counter);
+      break;
+
+    case ANDROID_IME_START_BATCH_EDIT:
+      start_batch_edit (f, event->ime.counter);
+      break;
+
+    case ANDROID_IME_END_BATCH_EDIT:
+      end_batch_edit (f, event->ime.counter);
+      break;
+    }
+}
+
 static int
 handle_one_android_event (struct android_display_info *dpyinfo,
                          union android_event *event, int *finish,
@@ -745,6 +853,17 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
 
     case ANDROID_WINDOW_ACTION:
 
+      /* This is a special event sent by android_run_in_emacs_thread
+        used to make Android run stuff.  */
+
+      if (!event->xaction.window && !event->xaction.action)
+       {
+         /* Check for and run anything the UI thread wants to run on the main
+            thread.  */
+         android_check_query ();
+         goto OTHER;
+       }
+
       f = any;
 
       if (event->xaction.action == 0)
@@ -1334,6 +1453,19 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
 
       goto OTHER;
 
+      /* Input method events.  textconv.c functions are called here to
+        queue events, which are then executed in a safe context
+        inside keyboard.c.  */
+    case ANDROID_INPUT_METHOD:
+
+      if (!any)
+       /* Free any text allocated for this event.  */
+       xfree (event->ime.text);
+      else
+       android_handle_ime_event (event, any);
+
+      goto OTHER;
+
     default:
       goto OTHER;
     }
@@ -4148,6 +4280,904 @@ android_draw_window_divider (struct window *w, int x0, 
int x1, int y0, int y1)
 
 
 
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#else
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif
+
+/* Input method related functions.  Some of these are called from Java
+   within the UI thread.  */
+
+/* A counter used to decide when an editing request completes.  */
+static unsigned long edit_counter;
+
+/* The last counter known to have completed.  */
+static unsigned long last_edit_counter;
+
+/* Semaphore posted every time the counter increases.  */
+static sem_t edit_sem;
+
+/* Try to synchronize with the UI thread, waiting a certain amount of
+   time for outstanding editing requests to complete.
+
+   Every time one of the text retrieval functions is called and an
+   editing request is made, Emacs gives the main thread approximately
+   50 ms to process it, in order to mostly keep the input method in
+   sync with the buffer contents.  */
+
+static void
+android_sync_edit (void)
+{
+  struct timespec start, end, rem;
+
+  if (__atomic_load_n (&last_edit_counter,
+                      __ATOMIC_SEQ_CST)
+      == edit_counter)
+    return;
+
+  start = current_timespec ();
+  end = timespec_add (start, make_timespec (0, 50000000));
+
+  while (true)
+    {
+      rem = timespec_sub (end, current_timespec ());
+
+      /* Timeout.  */
+      if (timespec_sign (rem) < 0)
+       break;
+
+      if (__atomic_load_n (&last_edit_counter,
+                          __ATOMIC_SEQ_CST)
+         == edit_counter)
+       break;
+
+      sem_timedwait (&edit_sem, &end);
+    }
+}
+
+/* Return a copy of the specified Java string and its length in
+   *LENGTH.  Use the JNI environment ENV.  Value is NULL if copying
+   *the string fails.  */
+
+static unsigned short *
+android_copy_java_string (JNIEnv *env, jstring string, size_t *length)
+{
+  jsize size, i;
+  const jchar *java;
+  unsigned short *buffer;
+
+  size = (*env)->GetStringLength (env, string);
+  buffer = malloc (size * sizeof *buffer);
+
+  if (!buffer)
+    return NULL;
+
+  java = (*env)->GetStringChars (env, string, NULL);
+
+  if (!java)
+    {
+      free (buffer);
+      return NULL;
+    }
+
+  for (i = 0; i < size; ++i)
+    buffer[i] = java[i];
+
+  *length = size;
+  (*env)->ReleaseStringChars (env, string, java);
+  return buffer;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (beginBatchEdit) (JNIEnv *env, jobject object, jshort window)
+{
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_START_BATCH_EDIT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (endBatchEdit) (JNIEnv *env, jobject object, jshort window)
+{
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_END_BATCH_EDIT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (commitCompletion) (JNIEnv *env, jobject object, jshort window,
+                               jstring completion_text, jint position)
+{
+  union android_event event;
+  unsigned short *text;
+  size_t length;
+
+  /* First, obtain a copy of the Java string.  */
+  text = android_copy_java_string (env, completion_text, &length);
+
+  if (!text)
+    return;
+
+  /* Next, populate the event.  Events will always eventually be
+     delivered on Android, so handle_one_android_event can be relied
+     on to free text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_COMMIT_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = min (length, PTRDIFF_MAX);
+  event.ime.position = position;
+  event.ime.text = text;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (commitText) (JNIEnv *env, jobject object, jshort window,
+                         jstring commit_text, jint position)
+{
+  union android_event event;
+  unsigned short *text;
+  size_t length;
+
+  /* First, obtain a copy of the Java string.  */
+  text = android_copy_java_string (env, commit_text, &length);
+
+  if (!text)
+    return;
+
+  /* Next, populate the event.  Events will always eventually be
+     delivered on Android, so handle_one_android_event can be relied
+     on to free text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_COMMIT_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = min (length, PTRDIFF_MAX);
+  event.ime.position = position;
+  event.ime.text = text;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (deleteSurroundingText) (JNIEnv *env, jobject object,
+                                    jshort window, jint left_length,
+                                    jint right_length)
+{
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_DELETE_SURROUNDING_TEXT;
+  event.ime.start = left_length;
+  event.ime.end = right_length;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (finishComposingText) (JNIEnv *env, jobject object,
+                                  jshort window)
+{
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+/* Structure describing the context used for a text query.  */
+
+struct android_conversion_query_context
+{
+  /* The conversion request.  */
+  struct textconv_callback_struct query;
+
+  /* The window the request is being made on.  */
+  android_window window;
+
+  /* Whether or not the request was successful.  */
+  bool success;
+};
+
+/* Obtain the text from the frame whose window is that specified in
+   DATA using the text conversion query specified there.
+
+   Adjust the query position to skip over any active composing region.
+
+   Set ((struct android_conversion_query_context *) DATA)->success on
+   success.  */
+
+static void
+android_perform_conversion_query (void *data)
+{
+  struct android_conversion_query_context *context;
+  struct frame *f;
+
+  context = data;
+
+  /* Find the frame associated with the window.  */
+  f = android_window_to_frame (NULL, context->window);
+
+  if (!f)
+    return;
+
+  textconv_query (f, &context->query,
+                 TEXTCONV_SKIP_CONVERSION_REGION);
+
+  /* context->query.text will have been set even if textconv_query
+     returns 1.  */
+
+  context->success = true;
+}
+
+/* Convert a string BUFFERS containing N characters in Emacs's
+   internal multibyte encoding to a Java string utilizing the
+   specified JNI environment.
+
+   If N is equal to BYTES, then BUFFER is a single byte buffer.
+   Otherwise, BUFFER is a multibyte buffer.
+
+   Make sure N and BYTES are absolutely correct, or you are asking for
+   trouble.
+
+   Value is the string upon success, NULL otherwise.  Any exceptions
+   generated are not cleared.  */
+
+static jstring
+android_text_to_string (JNIEnv *env, char *buffer, ptrdiff_t n,
+                       ptrdiff_t bytes)
+{
+  jchar *utf16;
+  size_t size, index;
+  jstring string;
+  int encoded;
+
+  if (n == bytes)
+    {
+      /* This buffer holds no multibyte characters.  */
+
+      if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
+       return NULL;
+
+      utf16 = malloc (size);
+      index = 0;
+
+      if (!utf16)
+       return NULL;
+
+      while (n--)
+       {
+         utf16[index] = buffer[index];
+         index++;
+       }
+
+      string = (*env)->NewString (env, utf16, bytes);
+      free (utf16);
+
+      return string;
+    }
+
+  /* Allocate enough to hold N characters.  */
+
+  if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
+    return NULL;
+
+  utf16 = malloc (size);
+  index = 0;
+
+  if (!utf16)
+    return NULL;
+
+  while (n--)
+    {
+      eassert (CHAR_HEAD_P (*buffer));
+      encoded = STRING_CHAR ((unsigned char *) buffer);
+
+      /* Now figure out how to save ENCODED into the string.
+         Emacs operates on multibyte characters, not UTF-16
+         characters with surrogate pairs as Android does.
+
+         However, character positions in Java are represented in 2
+         byte units, meaning that the text position reported to
+         Android can become out of sync if characters are found in a
+         buffer that require surrogate pairs.
+
+         The hack used by Emacs is to simply replace each multibyte
+         character that doesn't fit in a jchar with the NULL
+         character.  */
+
+      if (encoded >= 65536)
+       encoded = 0;
+
+      utf16[index++] = encoded;
+      buffer += BYTES_BY_CHAR_HEAD (*buffer);
+    }
+
+  /* Create the string.  */
+  string = (*env)->NewString (env, utf16, index);
+  free (utf16);
+  return string;
+}
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object,
+                              jshort window)
+{
+  return NULL;
+}
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getTextAfterCursor) (JNIEnv *env, jobject object, jshort window,
+                                 jint length, jint flags)
+{
+  struct android_conversion_query_context context;
+  jstring string;
+
+  /* First, set up the conversion query.  */
+  context.query.position = 0;
+  context.query.direction = TEXTCONV_FORWARD_CHAR;
+  context.query.factor = min (length, 65535);
+  context.query.operation = TEXTCONV_RETRIEVAL;
+
+  /* Next, set the rest of the context.  */
+  context.window = window;
+  context.success = false;
+
+  /* Now try to perform the query.  */
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_perform_conversion_query,
+                                  &context))
+    return NULL;
+
+  if (!context.success)
+    return NULL;
+
+  /* context->query.text now contains the text in Emacs's internal
+     UTF-8 based encoding.
+
+     Convert it to Java's UTF-16 encoding, which is the same as
+     UTF-16, except that NULL bytes are encoded as surrogate pairs.
+
+     This assumes that `free' can free data allocated with xmalloc.  */
+
+  string = android_text_to_string (env, context.query.text.text,
+                                  context.query.text.length,
+                                  context.query.text.bytes);
+  free (context.query.text.text);
+
+  return string;
+}
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getTextBeforeCursor) (JNIEnv *env, jobject object, jshort window,
+                                  jint length, jint flags)
+{
+  struct android_conversion_query_context context;
+  jstring string;
+
+  /* First, set up the conversion query.  */
+  context.query.position = 0;
+  context.query.direction = TEXTCONV_BACKWARD_CHAR;
+  context.query.factor = min (length, 65535);
+  context.query.operation = TEXTCONV_RETRIEVAL;
+
+  /* Next, set the rest of the context.  */
+  context.window = window;
+  context.success = false;
+
+  /* Now try to perform the query.  */
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_perform_conversion_query,
+                                  &context))
+    return NULL;
+
+  if (!context.success)
+    return NULL;
+
+  /* context->query.text now contains the text in Emacs's internal
+     UTF-8 based encoding.
+
+     Convert it to Java's UTF-16 encoding, which is the same as
+     UTF-16, except that NULL bytes are encoded as surrogate pairs.
+
+     This assumes that `free' can free data allocated with xmalloc.  */
+
+  string = android_text_to_string (env, context.query.text.text,
+                                  context.query.text.length,
+                                  context.query.text.bytes);
+  free (context.query.text.text);
+
+  return string;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setComposingText) (JNIEnv *env, jobject object, jshort window,
+                               jstring composing_text,
+                               jint new_cursor_position)
+{
+  union android_event event;
+  unsigned short *text;
+  size_t length;
+
+  /* First, obtain a copy of the Java string.  */
+  text = android_copy_java_string (env, composing_text, &length);
+
+  if (!text)
+    return;
+
+  /* Next, populate the event.  Events will always eventually be
+     delivered on Android, so handle_one_android_event can be relied
+     on to free text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_SET_COMPOSING_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = min (length, PTRDIFF_MAX);
+  event.ime.position = new_cursor_position;
+  event.ime.text = text;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setComposingRegion) (JNIEnv *env, jobject object, jshort window,
+                                 jint start, jint end)
+{
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_SET_COMPOSING_REGION;
+  event.ime.start = start;
+  event.ime.end = end;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setSelection) (JNIEnv *env, jobject object, jshort window,
+                           jint start, jint end)
+{
+  union android_event event;
+
+  /* While IMEs want access to the entire selection, Emacs only
+     supports setting the point.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_SET_POINT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = start;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+}
+
+/* Structure describing the context for `getSelection'.  */
+
+struct android_get_selection_context
+{
+  /* The window in question.  */
+  android_window window;
+
+  /* The position of the window's point when it was last
+     redisplayed.  */
+  ptrdiff_t point;
+};
+
+/* Function run on the main thread by `getSelection'.
+   Place the character position of point in PT.  */
+
+static void
+android_get_selection (void *data)
+{
+  struct android_get_selection_context *context;
+  struct frame *f;
+  struct window *w;
+
+  context = data;
+
+  /* Look up the associated frame and its selected window.  */
+  f = android_window_to_frame (NULL, context->window);
+
+  if (!f)
+    context->point = -1;
+  else
+    {
+      w = XWINDOW (f->selected_window);
+
+      /* Return W's point at the time of the last redisplay.  This is
+         rather important to keep the input method consistent with the
+         contents of the display.  */
+      context->point = w->last_point;
+    }
+}
+
+JNIEXPORT jint JNICALL
+NATIVE_NAME (getSelection) (JNIEnv *env, jobject object, jshort window)
+{
+  struct android_get_selection_context context;
+
+  context.window = window;
+
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_get_selection,
+                                  &context))
+    return -1;
+
+  if (context.point == -1)
+    return -1;
+
+  return min (context.point, TYPE_MAXIMUM (jint));
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object,
+                                  jshort window, int action)
+{
+  union android_event event;
+
+  /* Undocumented behavior: performEditorAction is apparently expected
+     to finish composing any text.  */
+
+  NATIVE_NAME (finishComposingText) (env, object, window);
+
+  event.xkey.type = ANDROID_KEY_PRESS;
+  event.xkey.serial = ++event_serial;
+  event.xkey.window = window;
+  event.xkey.time = 0;
+  event.xkey.state = 0;
+  event.xkey.keycode = 66;
+  event.xkey.unicode_char = 0;
+
+  android_write_event (&event);
+}
+
+struct android_get_extracted_text_context
+{
+  /* The parameters of the request.  */
+  int hint_max_chars;
+
+  /* Token for the request.  */
+  int token;
+
+  /* The returned text, or NULL.  */
+  char *text;
+
+  /* The size of that text in characters and bytes.  */
+  ptrdiff_t length, bytes;
+
+  /* Offsets into that text.  */
+  ptrdiff_t start, offset;
+
+  /* The window.  */
+  android_window window;
+};
+
+/* Return the extracted text in the extracted text context specified
+   by DATA.  */
+
+static void
+android_get_extracted_text (void *data)
+{
+  struct android_get_extracted_text_context *request;
+  struct frame *f;
+
+  request = data;
+
+  /* Find the frame associated with the window.  */
+  f = android_window_to_frame (NULL, request->window);
+
+  if (!f)
+    return;
+
+  /* Now get the extracted text.  */
+  request->text
+    = get_extracted_text (f, min (request->hint_max_chars, 600),
+                         &request->start, &request->offset,
+                         &request->length, &request->bytes);
+}
+
+/* Structure describing the `ExtractedTextRequest' class.
+   Valid only on the UI thread.  */
+
+struct android_extracted_text_request_class
+{
+  bool initialized;
+  jfieldID hint_max_chars;
+  jfieldID token;
+};
+
+/* Structure describing the `ExtractedText' class.
+   Valid only on the UI thread.  */
+
+struct android_extracted_text_class
+{
+  jclass class;
+  jmethodID constructor;
+  jfieldID partial_start_offset;
+  jfieldID partial_end_offset;
+  jfieldID selection_start;
+  jfieldID selection_end;
+  jfieldID start_offset;
+  jfieldID text;
+};
+
+JNIEXPORT jobject JNICALL
+NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object,
+                               jshort window, jobject request,
+                               jint flags)
+{
+  struct android_get_extracted_text_context context;
+  static struct android_extracted_text_request_class request_class;
+  static struct android_extracted_text_class text_class;
+  jstring string;
+  jclass class;
+  jobject object;
+
+  /* TODO: report changes to extracted text.  */
+
+  /* Initialize both classes if necessary.  */
+
+  if (!request_class.initialized)
+    {
+      class
+       = (*env)->FindClass (env, ("android/view/inputmethod"
+                                  "/ExtractedTextRequest"));
+      assert (class);
+
+      request_class.hint_max_chars
+       = (*env)->GetFieldID (env, class, "hintMaxChars", "I");
+      assert (request_class.hint_max_chars);
+
+      request_class.token
+       = (*env)->GetFieldID (env, class, "token", "I");
+      assert (request_class.token);
+
+      request_class.initialized = true;
+    }
+
+  if (!text_class.class)
+    {
+      text_class.class
+       = (*env)->FindClass (env, ("android/view/inputmethod"
+                                  "/ExtractedText"));
+      assert (text_class.class);
+
+      class
+       = text_class.class
+       = (*env)->NewGlobalRef (env, text_class.class);
+      assert (text_class.class);
+
+      text_class.partial_start_offset
+       = (*env)->GetFieldID (env, class, "partialStartOffset", "I");
+      text_class.partial_end_offset
+       = (*env)->GetFieldID (env, class, "partialEndOffset", "I");
+      text_class.selection_start
+       = (*env)->GetFieldID (env, class, "selectionStart", "I");
+      text_class.selection_end
+       = (*env)->GetFieldID (env, class, "selectionEnd", "I");
+      text_class.start_offset
+       = (*env)->GetFieldID (env, class, "startOffset", "I");
+      text_class.text
+       = (*env)->GetFieldID (env, class, "text", "Ljava/lang/CharSequence;");
+      text_class.constructor
+       = (*env)->GetMethodID (env, class, "<init>", "()V");
+    }
+
+  context.hint_max_chars
+    = (*env)->GetIntField (env, request, request_class.hint_max_chars);
+  context.token
+    = (*env)->GetIntField (env, request, request_class.token);
+  context.text = NULL;
+  context.window = window;
+
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_get_extracted_text,
+                                  &context))
+    return NULL;
+
+  if (!context.text)
+    return NULL;
+
+  /* Encode the returned text.  */
+  string = android_text_to_string (env, context.text, context.length,
+                                  context.bytes);
+  free (context.text);
+
+  if (!string)
+    return NULL;
+
+  /* Create an ExtractedText object containing this information.  */
+  object = (*android_java_env)->NewObject (env, text_class.class,
+                                          text_class.constructor);
+  if (!object)
+    return NULL;
+
+  (*env)->SetIntField (env, object, text_class.partial_start_offset, -1);
+  (*env)->SetIntField (env, object, text_class.partial_end_offset, -1);
+  (*env)->SetIntField (env, object, text_class.selection_start,
+                      min (context.offset, TYPE_MAXIMUM (jint)));
+  (*env)->SetIntField (env, object, text_class.selection_end,
+                      min (context.offset, TYPE_MAXIMUM (jint)));
+  (*env)->SetIntField (env, object, text_class.start_offset,
+                      min (context.start, TYPE_MAXIMUM (jint)));
+  (*env)->SetObjectField (env, object, text_class.text, string);
+  return object;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#else
+#pragma GCC diagnostic pop
+#endif
+
+
+
+/* Tell the input method where the composing region and selection of
+   F's selected window is located.  W should be F's selected window;
+   if it is NULL, then F->selected_window is used in its place.  */
+
+static void
+android_update_selection (struct frame *f, struct window *w)
+{
+  ptrdiff_t start, end, point;
+
+  if (MARKERP (f->conversion.compose_region_start))
+    {
+      eassert (MARKERP (f->conversion.compose_region_end));
+
+      start = marker_position (f->conversion.compose_region_start);
+      end = marker_position (f->conversion.compose_region_end);
+    }
+  else
+    start = -1, end = -1;
+
+  /* Now constrain START and END to the maximium size of a Java
+     integer.  */
+  start = min (start, TYPE_MAXIMUM (jint));
+  end = min (end, TYPE_MAXIMUM (jint));
+
+  if (!w)
+    w = XWINDOW (f->selected_window);
+
+  /* Figure out where the point is.  */
+  point = min (w->last_point, TYPE_MAXIMUM (jint));
+
+  /* Send the update.  */
+  android_update_ic (FRAME_ANDROID_WINDOW (f), point, point,
+                    start, end);
+}
+
+/* Notice that the input method connection to F should be reset as a
+   result of a change to its contents.  */
+
+static void
+android_reset_conversion (struct frame *f)
+{
+  /* Reset the input method.
+
+     Pick an appropriate ``input mode'' based on whether or not the
+     minibuffer window is selected; this controls whether or not
+     ``RET'' inserts a newline or sends an actual key event.  */
+  android_reset_ic (FRAME_ANDROID_WINDOW (f),
+                   (EQ (f->selected_window,
+                        f->minibuffer_window)
+                    ? ANDROID_IC_MODE_ACTION
+                    : ANDROID_IC_MODE_TEXT));
+
+  /* Move its selection to the specified position.  */
+  android_update_selection (f, NULL);
+}
+
+/* Notice that point has moved in the F's selected window's selected
+   buffer.  W is the window, and BUFFER is that buffer.  */
+
+static void
+android_set_point (struct frame *f, struct window *w,
+                  struct buffer *buffer)
+{
+  android_update_selection (f, w);
+}
+
+/* Notice that the composition region on F's old selected window has
+   changed.  */
+
+static void
+android_compose_region_changed (struct frame *f)
+{
+  android_update_selection (f, XWINDOW (f->old_selected_window));
+}
+
+/* Notice that the text conversion has completed.  */
+
+static void
+android_notify_conversion (unsigned long counter)
+{
+  int sval;
+
+  if (last_edit_counter < counter)
+    __atomic_store_n (&last_edit_counter, counter,
+                     __ATOMIC_SEQ_CST);
+
+  sem_getvalue (&edit_sem, &sval);
+
+  if (sval <= 0)
+    sem_post (&edit_sem);
+}
+
+/* Android text conversion interface.  */
+
+static struct textconv_interface text_conversion_interface =
+  {
+    android_reset_conversion,
+    android_set_point,
+    android_compose_region_changed,
+    android_notify_conversion,
+  };
+
+
+
 extern frame_parm_handler android_frame_parm_handlers[];
 
 #endif /* !ANDROID_STUBIFY */
@@ -4327,6 +5357,11 @@ android_term_init (void)
 
   /* Set the baud rate to the same value it gets set to on X.  */
   baud_rate = 19200;
+
+#ifndef ANDROID_STUBIFY
+  sem_init (&edit_sem, false, 0);
+  register_textconv_interface (&text_conversion_interface);
+#endif
 }
 
 
diff --git a/src/coding.c b/src/coding.c
index be5a0252ede..3ac4ada2939 100644
--- a/src/coding.c
+++ b/src/coding.c
@@ -11759,7 +11759,7 @@ syms_of_coding (void)
   DEFSYM (Qutf_8_unix, "utf-8-unix");
   DEFSYM (Qutf_8_emacs, "utf-8-emacs");
 
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
   /* No, not utf-16-le: that one has a BOM.  */
   DEFSYM (Qutf_16le, "utf-16le");
 #endif
diff --git a/src/frame.c b/src/frame.c
index a1d73d0799d..874e8c4cac1 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -997,6 +997,16 @@ make_frame (bool mini_p)
   f->select_mini_window_flag = false;
   /* This one should never be zero.  */
   f->change_stamp = 1;
+
+#ifdef HAVE_TEXT_CONVERSION
+  f->conversion.compose_region_start = Qnil;
+  f->conversion.compose_region_end = Qnil;
+  f->conversion.compose_region_overlay = Qnil;
+  f->conversion.batch_edit_count = 0;
+  f->conversion.batch_edit_flags = 0;
+  f->conversion.actions = NULL;
+#endif
+
   root_window = make_window ();
   rw = XWINDOW (root_window);
   if (mini_p)
@@ -2264,6 +2274,13 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
     f->terminal = 0;             /* Now the frame is dead.  */
     unblock_input ();
 
+  /* Clear markers and overlays set by F on behalf of an input
+     method.  */
+#ifdef HAVE_TEXT_CONVERSION
+  if (FRAME_WINDOW_P (f))
+    reset_frame_state (f);
+#endif
+
     /* If needed, delete the terminal that this frame was on.
        (This must be done after the frame is killed.)  */
     terminal->reference_count--;
diff --git a/src/frame.h b/src/frame.h
index 4405c2df860..27484936ff1 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -76,6 +76,63 @@ enum ns_appearance_type
 #endif
 #endif /* HAVE_WINDOW_SYSTEM */
 
+#ifdef HAVE_TEXT_CONVERSION
+
+enum text_conversion_operation
+  {
+    TEXTCONV_START_BATCH_EDIT,
+    TEXTCONV_END_BATCH_EDIT,
+    TEXTCONV_COMMIT_TEXT,
+    TEXTCONV_FINISH_COMPOSING_TEXT,
+    TEXTCONV_SET_COMPOSING_TEXT,
+    TEXTCONV_SET_COMPOSING_REGION,
+    TEXTCONV_SET_POINT,
+    TEXTCONV_DELETE_SURROUNDING_TEXT,
+  };
+
+/* Structure describing a single edit being performed by the input
+   method that should be executed in the context of
+   kbd_buffer_get_event.  */
+
+struct text_conversion_action
+{
+  /* The next text conversion action.  */
+  struct text_conversion_action *next;
+
+  /* Any associated data.  */
+  Lisp_Object data;
+
+  /* The operation being performed.  */
+  enum text_conversion_operation operation;
+
+  /* Counter value.  */
+  unsigned long counter;
+};
+
+/* Structure describing the text conversion state associated with a
+   frame.  */
+
+struct text_conversion_state
+{
+  /* List of text conversion actions associated with this frame.  */
+  struct text_conversion_action *actions;
+
+  /* Markers representing the composing region.  */
+  Lisp_Object compose_region_start, compose_region_end;
+
+  /* Overlay representing the composing region.  */
+  Lisp_Object compose_region_overlay;
+
+  /* The number of ongoing ``batch edits'' that are causing point
+     reporting to be delayed.  */
+  int batch_edit_count;
+
+  /* Mask containing what must be updated after batch edits end.  */
+  int batch_edit_flags;
+};
+
+#endif
+
 /* The structure representing a frame.  */
 
 struct frame
@@ -664,6 +721,11 @@ struct frame
   enum ns_appearance_type ns_appearance;
   bool_bf ns_transparent_titlebar;
 #endif
+
+#ifdef HAVE_TEXT_CONVERSION
+  /* Text conversion state used by certain input methods.  */
+  struct text_conversion_state conversion;
+#endif
 } GCALIGNED_STRUCT;
 
 /* Most code should use these functions to set Lisp fields in struct frame.  */
diff --git a/src/keyboard.c b/src/keyboard.c
index 11fa1e62c89..538fdffc199 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -44,6 +44,11 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "atimer.h"
 #include "process.h"
 #include "menu.h"
+
+#ifdef HAVE_TEXT_CONVERSION
+#include "textconv.h"
+#endif
+
 #include <errno.h>
 
 #ifdef HAVE_PTHREAD
@@ -3020,6 +3025,10 @@ read_char (int commandflag, Lisp_Object map,
     {
       struct buffer *prev_buffer = current_buffer;
       last_input_event = c;
+
+      /* All a `text-conversion' event does is prevent Emacs from
+        staying idle.  It is not useful.  */
+
       call4 (Qcommand_execute, tem, Qnil, Fvector (1, &last_input_event), Qt);
 
       if (CONSP (c) && !NILP (Fmemq (XCAR (c), Vwhile_no_input_ignore_events))
@@ -3582,6 +3591,11 @@ readable_events (int flags)
     return 1;
 #endif
 
+#ifdef HAVE_TEXT_CONVERSION
+  if (detect_conversion_events ())
+    return 1;
+#endif
+
   if (!(flags & READABLE_EVENTS_IGNORE_SQUEEZABLES) && some_mouse_moved ())
     return 1;
   if (single_kboard)
@@ -3914,6 +3928,11 @@ kbd_buffer_get_event (KBOARD **kbp,
 
   had_pending_selection_requests = false;
 #endif
+#ifdef HAVE_TEXT_CONVERSION
+  bool had_pending_conversion_events;
+
+  had_pending_conversion_events = false;
+#endif
 
 #ifdef subprocesses
   if (kbd_on_hold_p () && kbd_buffer_nr_stored () < KBD_BUFFER_SIZE / 4)
@@ -3977,6 +3996,13 @@ kbd_buffer_get_event (KBOARD **kbp,
          had_pending_selection_requests = true;
          break;
        }
+#endif
+#ifdef HAVE_TEXT_CONVERSION
+      if (detect_conversion_events ())
+       {
+         had_pending_conversion_events = true;
+         break;
+       }
 #endif
       if (end_time)
        {
@@ -4024,6 +4050,14 @@ kbd_buffer_get_event (KBOARD **kbp,
     x_handle_pending_selection_requests ();
 #endif
 
+#ifdef HAVE_TEXT_CONVERSION
+  /* Handle pending ``text conversion'' requests from an input
+     method.  */
+
+  if (had_pending_conversion_events)
+    handle_pending_conversion_events ();
+#endif
+
   if (CONSP (Vunread_command_events))
     {
       Lisp_Object first;
@@ -4380,12 +4414,25 @@ kbd_buffer_get_event (KBOARD **kbp,
 #ifdef HAVE_X_WINDOWS
   else if (had_pending_selection_requests)
     obj = Qnil;
+#endif
+#ifdef HAVE_TEXT_CONVERSION
+  /* This is an internal event used to prevent Emacs from becoming
+     idle immediately after a text conversion operation.  */
+  else if (had_pending_conversion_events)
+    obj = Qtext_conversion;
 #endif
   else
     /* We were promised by the above while loop that there was
        something for us to read!  */
     emacs_abort ();
 
+#ifdef HAVE_TEXT_CONVERSION
+  /* While not implemented as keyboard commands, changes made by the
+     input method still mean that Emacs is no longer idle.  */
+  if (had_pending_conversion_events)
+    timer_stop_idle ();
+#endif
+
   input_pending = readable_events (0);
 
   Vlast_event_frame = internal_last_event_frame;
@@ -12902,6 +12949,9 @@ See also `pre-command-hook'.  */);
 
   DEFSYM (Qcoding, "coding");
   DEFSYM (Qtouchscreen, "touchscreen");
+#ifdef HAVE_TEXT_CONVERSION
+  DEFSYM (Qtext_conversion, "text-conversion");
+#endif
 
   Fset (Qecho_area_clear_hook, Qnil);
 
diff --git a/src/lisp.h b/src/lisp.h
index 894b939b7b9..2dc51d32481 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -5230,7 +5230,10 @@ extern char *emacs_root_dir (void);
 
 #ifdef HAVE_TEXT_CONVERSION
 /* Defined in textconv.c.  */
+extern void reset_frame_state (struct frame *);
 extern void report_selected_window_change (struct frame *);
+extern void report_point_change (struct frame *, struct window *,
+                                struct buffer *);
 #endif
 
 #ifdef HAVE_NATIVE_COMP
diff --git a/src/process.c b/src/process.c
index 9a8b0d7fd85..e7ccb2c604e 100644
--- a/src/process.c
+++ b/src/process.c
@@ -121,6 +121,7 @@ static struct rlimit nofile_limit;
 
 #ifdef HAVE_ANDROID
 #include "android.h"
+#include "androidterm.h"
 #endif
 
 #ifdef HAVE_WINDOW_SYSTEM
diff --git a/src/textconv.c b/src/textconv.c
index d5db6d11717..b62ed7d1365 100644
--- a/src/textconv.c
+++ b/src/textconv.c
@@ -25,7 +25,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
    ability to ``undo'' or ``edit'' previously composed text.  This is
    most commonly seen in input methods for CJK laguages for X Windows,
    and is extensively used throughout Android by input methods for all
-   kinds of scripts.  */
+   kinds of scripts.
+
+   In addition, these input methods may also need to make detailed
+   edits to the content of a buffer.  That is also handled here.  */
 
 #include <config.h>
 
@@ -44,6 +47,15 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 static struct textconv_interface *text_interface;
 
+/* Flags used to determine what must be sent after a batch edit
+   ends.  */
+
+enum textconv_batch_edit_flags
+  {
+    PENDING_POINT_CHANGE   = 1,
+    PENDING_COMPOSE_CHANGE = 2,
+  };
+
 
 
 /* Copy the portion of the current buffer described by BEG, BEG_BYTE,
@@ -94,14 +106,18 @@ copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
    Then, either delete that text from the buffer if QUERY->operation
    is TEXTCONV_SUBSTITUTION, or return 0.
 
+   If FLAGS & TEXTCONV_SKIP_CONVERSION_REGION, then first move PT past
+   the conversion region in the specified direction if it is inside.
+
    Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION
    or if deleting the text was successful, and 1 otherwise.  */
 
 int
-textconv_query (struct frame *f, struct textconv_callback_struct *query)
+textconv_query (struct frame *f, struct textconv_callback_struct *query,
+               int flags)
 {
   specpdl_ref count;
-  ptrdiff_t pos, pos_byte, end, end_byte;
+  ptrdiff_t pos, pos_byte, end, end_byte, start;
   ptrdiff_t temp, temp1;
   char *buffer;
 
@@ -113,14 +129,46 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
   /* Inhibit quitting.  */
   specbind (Qinhibit_quit, Qt);
 
-  /* Temporarily switch to F's selected window.  */
-  Fselect_window (f->selected_window, Qt);
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  Fselect_window ((WINDOW_LIVE_P (f->old_selected_window)
+                  ? f->old_selected_window
+                  : f->selected_window), Qt);
 
   /* Now find the appropriate text bounds for QUERY.  First, move
      point QUERY->position steps forward or backwards.  */
 
   pos = PT;
 
+  /* Next, if POS lies within the conversion region and the caller
+     asked for it to be moved away, move it away from the conversion
+     region.  */
+
+  if (flags & TEXTCONV_SKIP_CONVERSION_REGION
+      && MARKERP (f->conversion.compose_region_start))
+    {
+      start = marker_position (f->conversion.compose_region_start);
+      end = marker_position (f->conversion.compose_region_end);
+
+      if (pos >= start && pos < end)
+       {
+         switch (query->direction)
+           {
+           case TEXTCONV_FORWARD_CHAR:
+           case TEXTCONV_FORWARD_WORD:
+           case TEXTCONV_CARET_DOWN:
+           case TEXTCONV_NEXT_LINE:
+           case TEXTCONV_LINE_START:
+             pos = end;
+             break;
+
+           default:
+             pos = max (BEGV, start - 1);
+             break;
+           }
+       }
+    }
+
   /* If pos is outside the accessible part of the buffer or if it
      overflows, move back to point or to the extremes of the
      accessible region.  */
@@ -287,6 +335,828 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
   return 0;
 }
 
+/* Reset F's text conversion state.  Delete any overlays or
+   markers inside.  */
+
+void
+reset_frame_state (struct frame *f)
+{
+  struct text_conversion_action *last, *next;
+
+  /* Make the composition region markers point elsewhere.  */
+
+  if (!NILP (f->conversion.compose_region_start))
+    {
+      Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
+      Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
+      f->conversion.compose_region_start = Qnil;
+      f->conversion.compose_region_end = Qnil;
+    }
+
+  /* Delete the composition region overlay.  */
+
+  if (!NILP (f->conversion.compose_region_overlay))
+    Fdelete_overlay (f->conversion.compose_region_overlay);
+
+  /* Delete each text conversion action queued up.  */
+
+  next = f->conversion.actions;
+  while (next)
+    {
+      last = next;
+      next = next->next;
+
+      /* Say that the conversion is finished.  */
+      if (text_interface && text_interface->notify_conversion)
+       text_interface->notify_conversion (last->counter);
+
+      xfree (last);
+    }
+  f->conversion.actions = NULL;
+
+  /* Clear batch edit state.  */
+  f->conversion.batch_edit_count = 0;
+  f->conversion.batch_edit_flags = 0;
+}
+
+/* Return whether or not there are pending edits from an input method
+   on any frame.  */
+
+bool
+detect_conversion_events (void)
+{
+  Lisp_Object tail, frame;
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      if (XFRAME (frame)->conversion.actions)
+       return true;
+    }
+
+  return false;
+}
+
+/* Restore the selected window WINDOW.  */
+
+static void
+restore_selected_window (Lisp_Object window)
+{
+  /* FIXME: not sure what to do if WINDOW has been deleted.  */
+  Fselect_window (window, Qt);
+}
+
+/* Commit the given text in the composing region.  If there is no
+   composing region, then insert the text after F's selected window's
+   last point instead.  Finally, remove the composing region.  */
+
+static void
+really_commit_text (struct frame *f, EMACS_INT position,
+                   Lisp_Object text)
+{
+  specpdl_ref count;
+  ptrdiff_t wanted, start, end;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  Fselect_window (f->old_selected_window, Qt);
+
+  /* Now detect whether or not there is a composing region.
+     If there is, then replace it with TEXT.  Don't do that
+     otherwise.  */
+
+  if (MARKERP (f->conversion.compose_region_start))
+    {
+      /* Replace its contents.  */
+      start = marker_position (f->conversion.compose_region_start);
+      end = marker_position (f->conversion.compose_region_end);
+      safe_del_range (start, end);
+      Finsert (1, &text);
+
+      /* Move to a the position specified in POSITION.  */
+
+      if (position < 0)
+       {
+         wanted
+           = marker_position (f->conversion.compose_region_start);
+
+         if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
+             || wanted < BEGV)
+           wanted = BEGV;
+
+         if (wanted > ZV)
+           wanted = ZV;
+
+         set_point (wanted);
+       }
+      else
+       {
+         wanted
+           = marker_position (f->conversion.compose_region_end);
+
+         if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
+             || wanted > ZV)
+           wanted = ZV;
+
+         if (wanted < BEGV)
+           wanted = BEGV;
+
+         set_point (wanted);
+       }
+
+      /* Make the composition region markers point elsewhere.  */
+
+      if (!NILP (f->conversion.compose_region_start))
+       {
+         Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
+         Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
+         f->conversion.compose_region_start = Qnil;
+         f->conversion.compose_region_end = Qnil;
+       }
+
+      /* Delete the composition region overlay.  */
+
+      if (!NILP (f->conversion.compose_region_overlay))
+       Fdelete_overlay (f->conversion.compose_region_overlay);
+    }
+  else
+    {
+      /* Otherwise, move the text and point to an appropriate
+        location.  */
+      wanted = PT;
+      Finsert (1, &text);
+
+      if (position < 0)
+       {
+         if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
+             || wanted < BEGV)
+           wanted = BEGV;
+
+         if (wanted > ZV)
+           wanted = ZV;
+
+         set_point (wanted);
+       }
+      else
+       {
+         wanted = PT;
+
+         if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
+             || wanted > ZV)
+           wanted = ZV;
+
+         if (wanted < BEGV)
+           wanted = BEGV;
+
+         set_point (wanted);
+       }
+    }
+
+  unbind_to (count, Qnil);
+}
+
+/* Remove the composition region on the frame F, while leaving its
+   contents intact.  */
+
+static void
+really_finish_composing_text (struct frame *f)
+{
+  if (!NILP (f->conversion.compose_region_start))
+    {
+      Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
+      Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
+      f->conversion.compose_region_start = Qnil;
+      f->conversion.compose_region_end = Qnil;
+    }
+
+  /* Delete the composition region overlay.  */
+
+  if (!NILP (f->conversion.compose_region_overlay))
+    Fdelete_overlay (f->conversion.compose_region_overlay);
+}
+
+/* Set the composing text on F to TEXT.  Then, move point to an
+   appropriate position relative to POSITION, and call
+   `compose_region_changed' in the text conversion interface should
+   point not have been changed relative to F's old selected window's
+   last point.  */
+
+static void
+really_set_composing_text (struct frame *f, ptrdiff_t position,
+                          Lisp_Object text)
+{
+  specpdl_ref count;
+  ptrdiff_t start, wanted, end;
+  struct window *w;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  w = XWINDOW (f->old_selected_window);
+  Fselect_window (f->old_selected_window, Qt);
+
+  /* Now set up the composition region if necessary.  */
+
+  if (!MARKERP (f->conversion.compose_region_start))
+    {
+      f->conversion.compose_region_start = Fmake_marker ();
+      f->conversion.compose_region_end = Fmake_marker ();
+      Fset_marker (f->conversion.compose_region_start,
+                  Fpoint (), Qnil);
+      Fset_marker (f->conversion.compose_region_end,
+                  Fpoint (), Qnil);
+      Fset_marker_insertion_type (f->conversion.compose_region_end,
+                                 Qt);
+    }
+  else
+    {
+      /* Delete the text between the start of the composition region
+        and its end.  TODO: avoid this widening.  */
+      record_unwind_protect (save_restriction_restore,
+                            save_restriction_save ());
+      Fwiden ();
+      start = marker_position (f->conversion.compose_region_start);
+      end = marker_position (f->conversion.compose_region_end);
+      safe_del_range (start, end);
+      set_point (start);
+    }
+
+  /* Insert the new text.  */
+  Finsert (1, &text);
+
+  /* Now move point to an appropriate location.  */
+  if (position < 0)
+    {
+      wanted = start;
+
+      if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
+         || wanted < BEGV)
+       wanted = BEGV;
+
+      if (wanted > ZV)
+       wanted = ZV;
+    }
+  else
+    {
+      end = marker_position (f->conversion.compose_region_end);
+      wanted = end;
+
+      /* end should be PT after the edit.  */
+      eassert (end == PT);
+
+      if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
+         || wanted > ZV)
+       wanted = ZV;
+
+      if (wanted < BEGV)
+       wanted = BEGV;
+    }
+
+  set_point (wanted);
+
+  /* If PT hasn't changed, the conversion region definitely has.
+     Otherwise, redisplay will update the input method instead.  */
+
+  if (PT == w->last_point
+      && text_interface
+      && text_interface->compose_region_changed)
+    {
+      if (f->conversion.batch_edit_count > 0)
+       f->conversion.batch_edit_flags |= PENDING_COMPOSE_CHANGE;
+      else
+       text_interface->compose_region_changed (f);
+    }
+
+  unbind_to (count, Qnil);
+}
+
+/* Set the composing region to START by END.  Make it that it is not
+   already set.  */
+
+static void
+really_set_composing_region (struct frame *f, ptrdiff_t start,
+                            ptrdiff_t end)
+{
+  specpdl_ref count;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  /* If MAX (0, start) == end, then this should behave the same as
+     really_finish_composing_text.  */
+
+  if (max (0, start) == max (0, end))
+    {
+      really_finish_composing_text (f);
+      return;
+    }
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  Fselect_window (f->old_selected_window, Qt);
+
+  /* Now set up the composition region if necessary.  */
+
+  if (!MARKERP (f->conversion.compose_region_start))
+    {
+      f->conversion.compose_region_start = Fmake_marker ();
+      f->conversion.compose_region_end = Fmake_marker ();
+      Fset_marker_insertion_type (f->conversion.compose_region_end,
+                                 Qt);
+    }
+
+  Fset_marker (f->conversion.compose_region_start,
+              make_fixnum (start), Qnil);
+  Fset_marker (f->conversion.compose_region_end,
+              make_fixnum (end), Qnil);
+
+  unbind_to (count, Qnil);
+}
+
+/* Delete LEFT and RIGHT chars around point.  */
+
+static void
+really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
+                               ptrdiff_t right)
+{
+  specpdl_ref count;
+  ptrdiff_t start, end;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  Fselect_window (f->old_selected_window, Qt);
+
+  start = max (BEGV, PT - left);
+  end = min (ZV, PT + right);
+
+  safe_del_range (start, end);
+  unbind_to (count, Qnil);
+}
+
+/* Set point in F to POSITION.
+
+   If it has not changed, signal an update through the text input
+   interface, which is necessary for the IME to acknowledge that the
+   change has completed.  */
+
+static void
+really_set_point (struct frame *f, ptrdiff_t point)
+{
+  specpdl_ref count;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  Fselect_window (f->old_selected_window, Qt);
+
+  if (point == PT)
+    {
+      if (f->conversion.batch_edit_count > 0)
+       f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+      else
+       text_interface->point_changed (f,
+                                      XWINDOW (f->old_selected_window),
+                                      current_buffer);
+    }
+  else
+    /* Set the point.  */
+    Fgoto_char (make_fixnum (point));
+
+  unbind_to (count, Qnil);
+}
+
+/* Complete the edit specified by the counter value inside *TOKEN.  */
+
+static void
+complete_edit (void *token)
+{
+  if (text_interface && text_interface->notify_conversion)
+    text_interface->notify_conversion (*(unsigned long *) token);
+}
+
+/* Process and free the text conversion ACTION.  F must be the frame
+   on which ACTION will be performed.  */
+
+static void
+handle_pending_conversion_events_1 (struct frame *f,
+                                   struct text_conversion_action *action)
+{
+  Lisp_Object data;
+  enum text_conversion_operation operation;
+  struct buffer *buffer;
+  struct window *w;
+  specpdl_ref count;
+  unsigned long token;
+
+  /* Next, process this action and free it.  */
+
+  data = action->data;
+  operation = action->operation;
+  token = action->counter;
+  xfree (action);
+
+  /* Make sure completion is signalled.  */
+  count = SPECPDL_INDEX ();
+  record_unwind_protect_ptr (complete_edit, &token);
+
+  switch (operation)
+    {
+    case TEXTCONV_START_BATCH_EDIT:
+      f->conversion.batch_edit_count++;
+      break;
+
+    case TEXTCONV_END_BATCH_EDIT:
+      if (f->conversion.batch_edit_count > 0)
+       f->conversion.batch_edit_count--;
+
+      if (!WINDOW_LIVE_P (f->old_selected_window))
+       break;
+
+      if (f->conversion.batch_edit_flags & PENDING_POINT_CHANGE)
+       {
+         w = XWINDOW (f->old_selected_window);
+         buffer = XBUFFER (WINDOW_BUFFER (w));
+
+         text_interface->point_changed (f, w, buffer);
+       }
+
+      if (f->conversion.batch_edit_flags & PENDING_COMPOSE_CHANGE)
+       text_interface->compose_region_changed (f);
+
+      f->conversion.batch_edit_flags = 0;
+      break;
+
+    case TEXTCONV_COMMIT_TEXT:
+      really_commit_text (f, XFIXNUM (XCAR (data)), XCDR (data));
+      break;
+
+    case TEXTCONV_FINISH_COMPOSING_TEXT:
+      really_finish_composing_text (f);
+      break;
+
+    case TEXTCONV_SET_COMPOSING_TEXT:
+      really_set_composing_text (f, XFIXNUM (XCAR (data)),
+                                XCDR (data));
+      break;
+
+    case TEXTCONV_SET_COMPOSING_REGION:
+      really_set_composing_region (f, XFIXNUM (XCAR (data)),
+                                  XFIXNUM (XCDR (data)));
+      break;
+
+    case TEXTCONV_SET_POINT:
+      really_set_point (f, XFIXNUM (data));
+      break;
+
+    case TEXTCONV_DELETE_SURROUNDING_TEXT:
+      really_delete_surrounding_text (f, XFIXNUM (XCAR (data)),
+                                     XFIXNUM (XCDR (data)));
+      break;
+    }
+
+  unbind_to (count, Qnil);
+}
+
+/* Process any outstanding text conversion events.
+   This may run Lisp or signal.  */
+
+void
+handle_pending_conversion_events (void)
+{
+  struct frame *f;
+  Lisp_Object tail, frame;
+  struct text_conversion_action *action, *next;
+  bool handled;
+
+  handled = false;
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+
+      /* Test if F has any outstanding conversion events.  Then
+        process them in bottom to up order.  */
+      for (action = f->conversion.actions; action; action = next)
+       {
+         /* Redisplay in between if there is more than one
+            action.  */
+
+         if (handled)
+           redisplay ();
+
+         /* Unlink this action.  */
+         next = action->next;
+         f->conversion.actions = next;
+
+         /* Handle and free the action.  */
+         handle_pending_conversion_events_1 (f, action);
+         handled = true;
+       }
+    }
+}
+
+/* Start a ``batch edit'' in F.  During a batch edit, point_changed
+   will not be called until the batch edit ends.
+
+   Process the actual operation in the event loop in keyboard.c; then,
+   call `notify_conversion' in the text conversion interface with
+   COUNTER.  */
+
+void
+start_batch_edit (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_START_BATCH_EDIT;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* End a ``batch edit''.  It is ok to call this function even if a
+   batch edit has not yet started, in which case it does nothing.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+end_batch_edit (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_END_BATCH_EDIT;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Insert the specified STRING into F's current buffer's composition
+   region, and set point to POSITION relative to STRING.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+commit_text (struct frame *f, Lisp_Object string,
+            ptrdiff_t position, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_COMMIT_TEXT;
+  action->data = Fcons (make_fixnum (position), string);
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Remove the composition region and its overlay from F's current
+   buffer.  Leave the text being composed intact.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+finish_composing_text (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_FINISH_COMPOSING_TEXT;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Insert the given STRING and make it the currently active
+   composition.
+
+   If there is currently no composing region, then the new value of
+   point is used as the composing region.
+
+   Then, the composing region is replaced with the text in the
+   specified string.
+
+   Finally, move point to new_point, which is relative to either the
+   start or the end of OBJECT depending on whether or not it is less
+   than zero.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+set_composing_text (struct frame *f, Lisp_Object object,
+                   ptrdiff_t new_point, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_SET_COMPOSING_TEXT;
+  action->data = Fcons (make_fixnum (new_point),
+                       object);
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Make the region between START and END the currently active
+   ``composing region''.
+
+   The ``composing region'' is a region of text in the buffer that is
+   about to undergo editing by the input method.  */
+
+void
+set_composing_region (struct frame *f, ptrdiff_t start,
+                     ptrdiff_t end, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  if (start > MOST_POSITIVE_FIXNUM)
+    start = MOST_POSITIVE_FIXNUM;
+
+  if (end > MOST_POSITIVE_FIXNUM)
+    end = MOST_POSITIVE_FIXNUM;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_SET_COMPOSING_REGION;
+  action->data = Fcons (make_fixnum (start),
+                       make_fixnum (end));
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Move point in F's selected buffer to POINT.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+textconv_set_point (struct frame *f, ptrdiff_t point,
+                   unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  if (point > MOST_POSITIVE_FIXNUM)
+    point = MOST_POSITIVE_FIXNUM;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_SET_POINT;
+  action->data = make_fixnum (point);
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Delete LEFT and RIGHT characters around point in F's old selected
+   window.  */
+
+void
+delete_surrounding_text (struct frame *f, ptrdiff_t left,
+                        ptrdiff_t right, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_DELETE_SURROUNDING_TEXT;
+  action->data = Fcons (make_fixnum (left),
+                       make_fixnum (right));
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Return N characters of text around point in F's old selected
+   window.
+
+   Set *N to the actual number of characters returned, *START_RETURN
+   to the position of the first character returned, *OFFSET to the
+   offset of point within that text, *LENGTH to the actual number of
+   characters returned, and *BYTES to the actual number of bytes
+   returned.
+
+   Value is NULL upon failure, and a malloced string upon success.  */
+
+char *
+get_extracted_text (struct frame *f, ptrdiff_t n,
+                   ptrdiff_t *start_return,
+                   ptrdiff_t *offset, ptrdiff_t *length,
+                   ptrdiff_t *bytes)
+{
+  specpdl_ref count;
+  ptrdiff_t start, end, start_byte, end_byte;
+  char *buffer;
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return NULL;
+
+  /* Save the excursion, as there will be extensive changes to the
+     selected window.  */
+  count = SPECPDL_INDEX ();
+  record_unwind_protect_excursion ();
+
+  /* Inhibit quitting.  */
+  specbind (Qinhibit_quit, Qt);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  Fselect_window (f->old_selected_window, Qt);
+
+  /* Figure out the bounds of the text to return.  */
+  start = PT - n / 2;
+  end = PT + n - n / 2;
+  start = max (start, BEGV);
+  end = min (end, ZV);
+  buffer = NULL;
+
+  /* Detect overflow.  */
+
+  if (!(start <= PT <= end))
+    goto finish;
+
+  /* Convert the character positions to byte positions.  */
+  start_byte = CHAR_TO_BYTE (start);
+  end_byte = CHAR_TO_BYTE (end);
+
+  /* Extract the text from the buffer.  */
+  buffer = xmalloc (end_byte - start_byte);
+  copy_buffer (start, start_byte, end, end_byte,
+              buffer);
+
+  /* Return the offsets.  */
+  *start_return = start;
+  *offset = PT - start;
+  *length = end - start;
+  *bytes = end_byte - start_byte;
+
+ finish:
+  unbind_to (count, Qnil);
+  return buffer;
+}
+
 
 
 /* Window system interface.  These are called from the rest of
@@ -298,16 +1168,40 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
 void
 report_selected_window_change (struct frame *f)
 {
+  reset_frame_state (f);
+
   if (!text_interface)
     return;
 
   text_interface->reset (f);
 }
 
+/* Notice that the point in F's selected window's current buffer has
+   changed.
+
+   F is the frame whose selected window was changed, W is the window
+   in question, and BUFFER is that window's current buffer.
+
+   Tell the text conversion interface about the change; it will likely
+   pass the information on to the system input method.  */
+
+void
+report_point_change (struct frame *f, struct window *window,
+                    struct buffer *buffer)
+{
+  if (!text_interface || !text_interface->point_changed)
+    return;
+
+  if (f->conversion.batch_edit_count > 0)
+    f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+  else
+    text_interface->point_changed (f, window, buffer);
+}
+
 /* Register INTERFACE as the text conversion interface.  */
 
 void
-register_texconv_interface (struct textconv_interface *interface)
+register_textconv_interface (struct textconv_interface *interface)
 {
   text_interface = interface;
 }
diff --git a/src/textconv.h b/src/textconv.h
index f6e7eb7925f..ce003847637 100644
--- a/src/textconv.h
+++ b/src/textconv.h
@@ -34,6 +34,20 @@ struct textconv_interface
      happen if the window is deleted or switches buffers, or an
      unexpected buffer change occurs.) */
   void (*reset) (struct frame *);
+
+  /* Notice that point has moved in the specified frame's selected
+     window's selected buffer.  The second argument is the window
+     whose point changed, and the third argument is the buffer.  */
+  void (*point_changed) (struct frame *, struct window *,
+                        struct buffer *);
+
+  /* Notice that the preconversion region has changed without point
+     being moved.  */
+  void (*compose_region_changed) (struct frame *);
+
+  /* Notice that an asynch conversion identified by COUNTER has
+     completed.  */
+  void (*notify_conversion) (unsigned long);
 };
 
 
@@ -103,7 +117,27 @@ struct textconv_callback_struct
   struct textconv_conversion_text text;
 };
 
-extern int textconv_query (struct frame *, struct textconv_callback_struct *);
-extern void register_texconv_interface (struct textconv_interface *);
+#define TEXTCONV_SKIP_CONVERSION_REGION (1 << 0)
+
+extern int textconv_query (struct frame *, struct textconv_callback_struct *,
+                          int);
+extern bool detect_conversion_events (void);
+extern void handle_pending_conversion_events (void);
+extern void start_batch_edit (struct frame *, unsigned long);
+extern void end_batch_edit (struct frame *, unsigned long);
+extern void commit_text (struct frame *, Lisp_Object, ptrdiff_t,
+                        unsigned long);
+extern void finish_composing_text (struct frame *, unsigned long);
+extern void set_composing_text (struct frame *, Lisp_Object,
+                               ptrdiff_t, unsigned long);
+extern void set_composing_region (struct frame *, ptrdiff_t, ptrdiff_t,
+                                 unsigned long);
+extern void textconv_set_point (struct frame *, ptrdiff_t, unsigned long);
+extern void delete_surrounding_text (struct frame *, ptrdiff_t,
+                                    ptrdiff_t, unsigned long);
+extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *,
+                                ptrdiff_t *, ptrdiff_t *, ptrdiff_t *);
+
+extern void register_textconv_interface (struct textconv_interface *);
 
 #endif /* _TEXTCONV_H_ */
diff --git a/src/xdisp.c b/src/xdisp.c
index 8bbb80b1c8c..4b91fcf31ca 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -17267,6 +17267,9 @@ static void
 mark_window_display_accurate_1 (struct window *w, bool accurate_p)
 {
   struct buffer *b = XBUFFER (w->contents);
+#ifdef HAVE_TEXT_CONVERSION
+  ptrdiff_t prev_point;
+#endif
 
   w->last_modified = accurate_p ? BUF_MODIFF (b) : 0;
   w->last_overlay_modified = accurate_p ? BUF_OVERLAY_MODIFF (b) : 0;
@@ -17296,11 +17299,36 @@ mark_window_display_accurate_1 (struct window *w, 
bool accurate_p)
       w->last_cursor_vpos = w->cursor.vpos;
       w->last_cursor_off_p = w->cursor_off_p;
 
+#ifdef HAVE_TEXT_CONVERSION
+      prev_point = w->last_point;
+#endif
+
       if (w == XWINDOW (selected_window))
        w->last_point = BUF_PT (b);
       else
        w->last_point = marker_position (w->pointm);
 
+#ifdef HAVE_TEXT_CONVERSION
+      /* Point motion is only propagated to the input method for use
+        in text conversion during a redisplay.  While this can lead
+        to inconsistencies when point has moved but the change has
+        not yet been displayed, it leads to better results most of
+        the time, as point often changes within calls to
+        `save-excursion', and the only way to detect such calls is to
+        observe that the next redisplay never ends with those changes
+        applied.
+
+         Changes to buffer text are immediately propagated to the
+         input method, and the position of point is also updated
+         during such a change, so the consequences are not that
+         severe.  */
+
+      if (prev_point != w->last_point
+         && FRAME_WINDOW_P (WINDOW_XFRAME (w))
+         && w == XWINDOW (WINDOW_XFRAME (w)->selected_window))
+       report_point_change (WINDOW_XFRAME (w), w, b);
+#endif
+
       w->window_end_valid = true;
       w->update_mode_line = false;
       w->preserve_vscroll_p = false;
diff --git a/src/xfns.c b/src/xfns.c
index 9e004f6a678..0e4a25de04a 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -3861,7 +3861,7 @@ xic_string_conversion_callback (XIC ic, XPointer 
client_data,
     request.operation = TEXTCONV_RETRIEVAL;
 
   /* Now perform the string conversion.  */
-  rc = textconv_query (f, &request);
+  rc = textconv_query (f, &request, 0);
 
   if (rc)
     {
diff --git a/src/xterm.c b/src/xterm.c
index ccaacec2c57..49b88de2759 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -31615,7 +31615,7 @@ init_xterm (void)
 #endif
 
 #ifdef HAVE_X_I18N
-  register_texconv_interface (&text_conversion_interface);
+  register_textconv_interface (&text_conversion_interface);
 #endif
 }
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]