emacs-diffs
[Top][All Lists]
Advanced

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

feature/android 0198b8cffd8 03/10: Update Android port


From: Po Lu
Subject: feature/android 0198b8cffd8 03/10: Update Android port
Date: Wed, 15 Feb 2023 09:53:28 -0500 (EST)

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

    Update Android port
    
    * doc/emacs/android.texi (Android Environment): Document
    notifications permission.
    * java/org/gnu/emacs/EmacsEditable.java (EmacsEditable):
    * java/org/gnu/emacs/EmacsInputConnection.java
    (EmacsInputConnection): New files.
    * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Load
    library dependencies in a less verbose fashion.
    * java/org/gnu/emacs/EmacsView.java (EmacsView): Make imManager
    public.
    (onCreateInputConnection): Set InputType to TYPE_NULL for now.
    * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyDown)
    (onKeyUp, getEventUnicodeChar): Correctly handle key events with
    strings.
    * lisp/term/android-win.el (android-clear-preedit-text)
    (android-preedit-text): New special event handlers.
    * src/android.c (struct android_emacs_window): Add function
    lookup_string.
    (android_init_emacs_window): Adjust accordingly.
    (android_wc_lookup_string): New function.
    * src/androidgui.h (struct android_key_event): Improve
    commentary.
    (enum android_lookup_status): New enum.
    * src/androidterm.c (handle_one_android_event): Synchronize IM
    lookup code with X.
    * src/coding.c (from_unicode_buffer): Implement on Android.
    * src/coding.h:
    * src/sfnt.c: Fix commentary.
---
 doc/emacs/android.texi                       |   2 +
 java/org/gnu/emacs/EmacsEditable.java        | 300 +++++++++++++++++++++++++++
 java/org/gnu/emacs/EmacsInputConnection.java | 175 ++++++++++++++++
 java/org/gnu/emacs/EmacsNative.java          | 166 +++------------
 java/org/gnu/emacs/EmacsView.java            |   7 +-
 java/org/gnu/emacs/EmacsWindow.java          |  96 ++++++++-
 lisp/term/android-win.el                     |  36 ++++
 src/android.c                                | 111 ++++++++++
 src/androidgui.h                             |  20 +-
 src/androidterm.c                            | 103 +++++++--
 src/coding.c                                 |  36 +++-
 src/coding.h                                 |   4 +-
 src/sfnt.c                                   |  11 +-
 13 files changed, 880 insertions(+), 187 deletions(-)

diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
index 8df26560f02..308bd70c59c 100644
--- a/doc/emacs/android.texi
+++ b/doc/emacs/android.texi
@@ -334,6 +334,8 @@ permissions upon installation:
 @code{android.permission.TRANSMIT_IR}
 @item
 @code{android.permission.WAKE_LOCK}
+@item
+@code{android.permission.POST_NOTIFICATIONS}
 @end itemize
 
 Other permissions must be granted by the user through the system
diff --git a/java/org/gnu/emacs/EmacsEditable.java 
b/java/org/gnu/emacs/EmacsEditable.java
new file mode 100644
index 00000000000..79af65a6ccd
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsEditable.java
@@ -0,0 +1,300 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <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
new file mode 100644
index 00000000000..897a393b984
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsInputConnection.java
@@ -0,0 +1,175 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
+import android.view.KeyEvent;
+
+import android.text.Editable;
+
+import android.util.Log;
+
+/* Android input methods, take number six.
+
+   See EmacsEditable for more details.  */
+
+public class EmacsInputConnection extends BaseInputConnection
+{
+  private static final String TAG = "EmacsInputConnection";
+  public EmacsView view;
+  private EmacsEditable editable;
+
+  /* The length of the last string to be committed.  */
+  private int lastCommitLength;
+
+  int currentLargeOffset;
+
+  public
+  EmacsInputConnection (EmacsView view)
+  {
+    super (view, false);
+    this.view = view;
+    this.editable = new EmacsEditable (this);
+  }
+
+  @Override
+  public Editable
+  getEditable ()
+  {
+    return editable;
+  }
+
+  @Override
+  public boolean
+  setComposingText (CharSequence text, int newCursorPosition)
+  {
+    editable.compositionStart ();
+    super.setComposingText (text, newCursorPosition);
+    return true;
+  }
+
+  @Override
+  public boolean
+  setComposingRegion (int start, int end)
+  {
+    int i;
+
+    if (lastCommitLength != 0)
+      {
+       Log.d (TAG, "Restarting composition for: " + lastCommitLength);
+
+       for (i = 0; i < lastCommitLength; ++i)
+         sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
+                                     KeyEvent.KEYCODE_DEL));
+
+       lastCommitLength = 0;
+      }
+
+    editable.compositionStart ();
+    super.setComposingRegion (start, end);
+    return true;
+  }
+  
+  @Override
+  public boolean
+  finishComposingText ()
+  {
+    editable.compositionEnd ();
+    return super.finishComposingText ();
+  }
+
+  @Override
+  public boolean
+  beginBatchEdit ()
+  {
+    editable.beginBatchEdit ();
+    return super.beginBatchEdit ();
+  }
+
+  @Override
+  public boolean
+  endBatchEdit ()
+  {
+    editable.endBatchEdit ();
+    return super.endBatchEdit ();
+  }
+  
+  @Override
+  public boolean
+  commitText (CharSequence text, int newCursorPosition)
+  {
+    editable.compositionEnd ();
+    super.commitText (text, newCursorPosition);
+
+    /* An observation is that input methods rarely recompose trailing
+       spaces.  Avoid re-setting the commit length in that case.  */
+
+    if (text.toString ().equals (" "))
+      lastCommitLength += 1;
+    else
+      /* At this point, the editable is now empty.
+        
+        The input method may try to cancel the edit upon a subsequent
+        backspace by calling setComposingRegion with a region that is
+        the length of TEXT.
+        
+        Record this length in order to be able to send backspace
+        events to ``delete'' the text in that case.  */
+      lastCommitLength = text.length ();
+
+    Log.d (TAG, "commitText: \"" + text + "\"");
+
+    return true;
+  }
+
+  /* Return a large offset, cycling through 100000, 30000, 0.
+     The offset is typically used to force the input method to update
+     its notion of ``surrounding text'', bypassing any caching that
+     it might have in progress.
+
+     There must be another way to do this, but I can't find it.  */
+
+  public int
+  largeSelectionOffset ()
+  {
+    switch (currentLargeOffset)
+      {
+      case 0:
+       currentLargeOffset = 100000;
+       return 100000;
+
+      case 100000:
+       currentLargeOffset = 30000;
+       return 30000;
+
+      case 30000:
+       currentLargeOffset = 0;
+       return 0;
+      }
+
+    currentLargeOffset = 0;
+    return -1;
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsNative.java 
b/java/org/gnu/emacs/EmacsNative.java
index 939348ba420..f0219843d35 100644
--- a/java/org/gnu/emacs/EmacsNative.java
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -25,6 +25,10 @@ import android.content.res.AssetManager;
 
 public class EmacsNative
 {
+  /* List of native libraries that must be loaded during class
+     initialization.  */
+  private static final String[] libraryDeps;
+
   /* Obtain the fingerprint of this build of Emacs.  The fingerprint
      can be used to determine the dump file name.  */
   public static native String getFingerprint ();
@@ -167,148 +171,26 @@ public class EmacsNative
        Every time you add a new shared library dependency to Emacs,
        please add it here as well.  */
 
-    try
-      {
-       System.loadLibrary ("png_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("selinux_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("crypto_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("pcre_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("packagelistparser_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("gnutls_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("gmp_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("nettle_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("p11-kit_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("tasn1_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("hogweed_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("jansson_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("jpeg_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("tiff_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("xml2_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
-      }
-
-    try
-      {
-       System.loadLibrary ("icuuc_emacs");
-      }
-    catch (UnsatisfiedLinkError exception)
-      {
-       /* Ignore this exception.  */
+    libraryDeps = new String[] { "png_emacs", "selinux_emacs",
+                                "crypto_emacs", "pcre_emacs",
+                                "packagelistparser_emacs",
+                                "gnutls_emacs", "gmp_emacs",
+                                "nettle_emacs", "p11-kit_emacs",
+                                "tasn1_emacs", "hogweed_emacs",
+                                "jansson_emacs", "jpeg_emacs",
+                                "tiff_emacs", "xml2_emacs",
+                                "icuuc_emacs", };
+
+    for (String dependency : libraryDeps)
+      {
+       try
+         {
+           System.loadLibrary (dependency);
+         }
+       catch (UnsatisfiedLinkError exception)
+         {
+           /* Ignore this exception.  */
+         }
       }
 
     System.loadLibrary ("emacs");
diff --git a/java/org/gnu/emacs/EmacsView.java 
b/java/org/gnu/emacs/EmacsView.java
index 4fc8104e31f..bc3716f6da8 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -94,7 +94,7 @@ public class EmacsView extends ViewGroup
   private long lastClipSerial;
 
   /* The InputMethodManager for this view's context.  */
-  private InputMethodManager imManager;
+  public InputMethodManager imManager;
 
   /* Whether or not this view is attached to a window.  */
   public boolean isAttachedToWindow;
@@ -558,8 +558,9 @@ public class EmacsView extends ViewGroup
        box that obscures Emacs.  */
     info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
 
-    /* But don't return an InputConnection, in order to force the on
-       screen keyboard to work correctly.  */
+    /* Set a reasonable inputType.  */
+    info.inputType = InputType.TYPE_NULL;
+
     return null;
   }
 
diff --git a/java/org/gnu/emacs/EmacsWindow.java 
b/java/org/gnu/emacs/EmacsWindow.java
index 9e2f2f53270..0eca35cec61 100644
--- a/java/org/gnu/emacs/EmacsWindow.java
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -23,6 +23,8 @@ import java.lang.IllegalStateException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import android.content.Context;
 
@@ -100,7 +102,7 @@ public class EmacsWindow extends EmacsHandleObject
 
   /* The button state and keyboard modifier mask at the time of the
      last button press or release event.  */
-  private int lastButtonState, lastModifiers;
+  public int lastButtonState, lastModifiers;
 
   /* Whether or not the window is mapped, and whether or not it is
      deiconified.  */
@@ -122,6 +124,10 @@ public class EmacsWindow extends EmacsHandleObject
      to quit Emacs.  */
   private long lastVolumeButtonRelease;
 
+  /* Linked list of character strings which were recently sent as
+     events.  */
+  public LinkedHashMap<Integer, String> eventStrings;
+
   public
   EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
               int width, int height, boolean overrideRedirect)
@@ -155,6 +161,19 @@ public class EmacsWindow extends EmacsHandleObject
       }
 
     scratchGC = new EmacsGC ((short) 0);
+
+    /* Create the map of input method-committed strings.  Keep at most
+       ten strings in the map.  */
+
+    eventStrings
+      = new LinkedHashMap<Integer, String> () {
+         @Override
+         protected boolean
+         removeEldestEntry (Map.Entry<Integer, String> entry)
+         {
+           return size () > 10;
+         }
+       };
   }
 
   public void
@@ -507,10 +526,40 @@ public class EmacsWindow extends EmacsHandleObject
     return view.getBitmap ();
   }
 
+  /* event.getCharacters is used because older input methods still
+     require it.  */
+  @SuppressWarnings ("deprecation")
+  public int
+  getEventUnicodeChar (KeyEvent event, int state)
+  {
+    String characters;
+
+    if (event.getUnicodeChar (state) != 0)
+      return event.getUnicodeChar (state);
+
+    characters = event.getCharacters ();
+
+    if (characters != null && characters.length () == 1)
+      return characters.charAt (0);
+
+    return characters == null ? 0 : -1;
+  }
+
+  public void
+  saveUnicodeString (int serial, String string)
+  {
+    eventStrings.put (serial, string);
+  }
+
+  /* event.getCharacters is used because older input methods still
+     require it.  */
+  @SuppressWarnings ("deprecation")
   public void
   onKeyDown (int keyCode, KeyEvent event)
   {
     int state, state_1;
+    long serial;
+    String characters;
 
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
       state = event.getModifiers ();
@@ -537,18 +586,28 @@ public class EmacsWindow extends EmacsHandleObject
     state_1
       = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
 
-    EmacsNative.sendKeyPress (this.handle,
-                             event.getEventTime (),
-                             state, keyCode,
-                             event.getUnicodeChar (state_1));
-    lastModifiers = state;
+    synchronized (eventStrings)
+      {
+       serial
+         = EmacsNative.sendKeyPress (this.handle,
+                                     event.getEventTime (),
+                                     state, keyCode,
+                                     getEventUnicodeChar (event,
+                                                          state_1));
+       lastModifiers = state;
+
+       characters = event.getCharacters ();
+
+       if (characters != null && characters.length () > 1)
+         saveUnicodeString ((int) serial, characters);
+      }
   }
 
   public void
   onKeyUp (int keyCode, KeyEvent event)
   {
     int state, state_1;
-    long time;
+    long time, serial;
 
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
       state = event.getModifiers ();
@@ -575,10 +634,12 @@ public class EmacsWindow extends EmacsHandleObject
     state_1
       = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
 
-    EmacsNative.sendKeyRelease (this.handle,
-                               event.getEventTime (),
-                               state, keyCode,
-                               event.getUnicodeChar (state_1));
+    serial
+      = EmacsNative.sendKeyRelease (this.handle,
+                                   event.getEventTime (),
+                                   state, keyCode,
+                                   getEventUnicodeChar (event,
+                                                        state_1));
     lastModifiers = state;
 
     if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
@@ -1105,4 +1166,17 @@ public class EmacsWindow extends EmacsHandleObject
        }
       });
   }
+
+  public String
+  lookupString (int eventSerial)
+  {
+    String any;
+
+    synchronized (eventStrings)
+      {
+       any = eventStrings.remove (eventSerial);
+      }
+
+    return any;
+  }
 };
diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el
index aa5992a9f09..0984b4d5840 100644
--- a/lisp/term/android-win.el
+++ b/lisp/term/android-win.el
@@ -166,6 +166,42 @@ VALUE should be something suitable for passing to
            (android-set-clipboard string))
           ((eq type 'PRIMARY)
            (setq android-primary-selection string)))))
+
+;;; Character composition display.
+
+(defvar android-preedit-overlay nil
+  "The overlay currently used to display preedit text from a compose 
sequence.")
+
+;; With some input methods, text gets inserted before Emacs is told to
+;; remove any preedit text that was displayed, which causes both the
+;; preedit overlay and the text to be visible for a brief period of
+;; time.  This pre-command-hook clears the overlay before any command
+;; and should be set whenever a preedit overlay is visible.
+(defun android-clear-preedit-text ()
+  "Clear the pre-edit overlay and remove itself from pre-command-hook.
+This function should be installed in `pre-command-hook' whenever
+preedit text is displayed."
+  (when android-preedit-overlay
+    (delete-overlay android-preedit-overlay)
+    (setq android-preedit-overlay nil))
+  (remove-hook 'pre-command-hook #'android-clear-preedit-text))
+
+(defun android-preedit-text (event)
+  "Display preedit text from a compose sequence in EVENT.
+EVENT is a preedit-text event."
+  (interactive "e")
+  (when android-preedit-overlay
+    (delete-overlay android-preedit-overlay)
+    (setq android-preedit-overlay nil)
+    (remove-hook 'pre-command-hook #'android-clear-preedit-text))
+  (when (nth 1 event)
+    (let ((string (propertize (nth 1 event) 'face '(:underline t))))
+      (setq android-preedit-overlay (make-overlay (point) (point)))
+      (add-hook 'pre-command-hook #'android-clear-preedit-text)
+      (overlay-put android-preedit-overlay 'window (selected-window))
+      (overlay-put android-preedit-overlay 'before-string string))))
+
+(define-key special-event-map [preedit-text] 'android-preedit-text)
 
 (provide 'android-win)
 ;; android-win.el ends here.
diff --git a/src/android.c b/src/android.c
index ebd374addbf..479eb9b10d4 100644
--- a/src/android.c
+++ b/src/android.c
@@ -125,6 +125,7 @@ struct android_emacs_window
   jclass class;
   jmethodID swap_buffers;
   jmethodID toggle_on_screen_keyboard;
+  jmethodID lookup_string;
 };
 
 /* The API level of the current device.  */
@@ -1829,6 +1830,7 @@ android_init_emacs_window (void)
   FIND_METHOD (swap_buffers, "swapBuffers", "()V");
   FIND_METHOD (toggle_on_screen_keyboard,
               "toggleOnScreenKeyboard", "(Z)V");
+  FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;");
 #undef FIND_METHOD
 }
 
@@ -4104,6 +4106,115 @@ android_sync (void)
   android_exception_check ();
 }
 
+int
+android_wc_lookup_string (android_key_pressed_event *event,
+                         wchar_t *buffer_return, int wchars_buffer,
+                         int *keysym_return,
+                         enum android_lookup_status *status_return)
+{
+  enum android_lookup_status status;
+  int rc;
+  jobject window, string;
+  const jchar *characters;
+  jsize size;
+  size_t i;
+
+  status = ANDROID_LOOKUP_NONE;
+  rc = 0;
+
+  /* See if an actual lookup has to be made.  Note that while
+     BUFFER_RETURN is wchar_t, the returned characters are always in
+     UCS.  */
+
+  if (event->unicode_char != (uint32_t) -1)
+    {
+      if (event->unicode_char)
+       {
+         if (wchars_buffer < 1)
+           {
+             *status_return = ANDROID_BUFFER_OVERFLOW;
+             return 0;
+           }
+         else
+           {
+             buffer_return[0] = event->unicode_char;
+             status = ANDROID_LOOKUP_CHARS;
+             rc = 1;
+           }
+       }
+
+      *keysym_return = event->keycode;
+
+      if (status == ANDROID_LOOKUP_CHARS)
+       status = ANDROID_LOOKUP_BOTH;
+      else
+       {
+         status = ANDROID_LOOKUP_KEYSYM;
+         rc = 0;
+       }
+
+      *status_return = status;
+
+      return rc;
+    }
+
+  /* Now look up the window.  */
+  rc = 0;
+
+  if (!android_handles[event->window].handle
+      || (android_handles[event->window].type
+         != ANDROID_HANDLE_WINDOW))
+    status = ANDROID_LOOKUP_NONE;
+  else
+    {
+      window = android_handles[event->window].handle;
+      string
+       = (*android_java_env)->CallObjectMethod (android_java_env, window,
+                                                window_class.lookup_string,
+                                                (jint) event->serial);
+      android_exception_check ();
+
+      if (!string)
+       status = ANDROID_LOOKUP_NONE;
+      else
+       {
+         /* Now return this input method string.  */
+         characters = (*android_java_env)->GetStringChars (android_java_env,
+                                                           string, NULL);
+         android_exception_check ();
+
+         /* Figure out how big the string is.  */
+         size = (*android_java_env)->GetStringLength (android_java_env,
+                                                      string);
+
+         /* Copy over the string data.  */
+         for (i = 0; i < MIN ((unsigned int) wchars_buffer, size); ++i)
+           buffer_return[i] = characters[i];
+
+         if (i < size)
+           status = ANDROID_BUFFER_OVERFLOW;
+         else
+           status = ANDROID_LOOKUP_CHARS;
+
+         /* Return the number of characters that should have been
+            written.  */
+
+         if (size > INT_MAX)
+           rc = INT_MAX;
+         else
+           rc = size;
+
+         (*android_java_env)->ReleaseStringChars (android_java_env, string,
+                                                  characters);
+         android_exception_check ();
+         ANDROID_DELETE_LOCAL_REF (string);
+       }
+    }
+
+  *status_return = status;
+  return rc;
+}
+
 
 
 /* Low level drawing primitives.  */
diff --git a/src/androidgui.h b/src/androidgui.h
index 619c5e44f15..1f0d34e67aa 100644
--- a/src/androidgui.h
+++ b/src/androidgui.h
@@ -209,7 +209,7 @@ typedef char Emacs_Cursor;
 #ifndef ANDROID_STUBIFY
 
 /* Universal NULL handle.  */
-static const int ANDROID_NONE;
+static const int ANDROID_NONE, ANDROID_NO_SYMBOL;
 
 /* Keep these as conceptually close to X as possible: that makes
    synchronizing code between the ports much easier.  */
@@ -259,9 +259,14 @@ struct android_key_event
   android_time time;
   unsigned int state;
   unsigned int keycode;
+
+  /* If this field is -1, then android_lookup_string should be called
+     to retrieve the associated individual characters.  */
   unsigned int unicode_char;
 };
 
+typedef struct android_key_event android_key_pressed_event;
+
 /* These hard coded values are Android modifier keycodes derived
    through experimentation.  */
 
@@ -449,6 +454,15 @@ enum
     ANDROID_CURRENT_TIME = 0L,
   };
 
+enum android_lookup_status
+  {
+    ANDROID_BUFFER_OVERFLOW,
+    ANDROID_LOOKUP_NONE,
+    ANDROID_LOOKUP_CHARS,
+    ANDROID_LOOKUP_KEYSYM,
+    ANDROID_LOOKUP_BOTH,
+  };
+
 extern int android_pending (void);
 extern void android_next_event (union android_event *);
 
@@ -537,6 +551,10 @@ extern void android_translate_coordinates (android_window, 
int,
                                           int, int *, int *);
 extern void android_sync (void);
 
+extern int android_wc_lookup_string (android_key_pressed_event *,
+                                    wchar_t *, int, int *,
+                                    enum android_lookup_status *);
+
 #endif
 
 
diff --git a/src/androidterm.c b/src/androidterm.c
index 4aee1a90b68..a57d5623c66 100644
--- a/src/androidterm.c
+++ b/src/androidterm.c
@@ -541,6 +541,8 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
   Lisp_Object window;
   int scroll_height;
   double scroll_unit;
+  int keysym;
+  ptrdiff_t nchars, i;
 
   /* It is okay for this to not resemble handle_one_xevent so much.
      Differences in event handling code are much less nasty than
@@ -550,6 +552,7 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
   hlinfo = &dpyinfo->mouse_highlight;
   *finish = ANDROID_EVENT_NORMAL;
   any = android_window_to_frame (dpyinfo, event->xany.window);
+  nchars = 0;
 
   if (any && any->wait_event_type == event->type)
     any->wait_event_type = 0; /* Indicates we got it.  */
@@ -616,6 +619,13 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
            android_flush_dirty_back_buffer_on (mouse_frame);
        }
 
+      if (!f)
+       goto OTHER;
+
+      wchar_t copy_buffer[129];
+      wchar_t *copy_bufptr = copy_buffer;
+      int copy_bufsiz = 128 * sizeof (wchar_t);
+
       event->xkey.state
        |= android_emacs_to_android_modifiers (dpyinfo,
                                               extra_keyboard_modifiers);
@@ -627,31 +637,100 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
        = android_android_to_emacs_modifiers (dpyinfo, modifiers);
       inev.ie.timestamp = event->xkey.time;
 
-      /* First deal with keysyms which have defined translations to
-        characters.  */
+      keysym = event->xkey.keycode;
+
+      {
+       enum android_lookup_status status_return;
+
+       nchars = android_wc_lookup_string (&event->xkey, copy_bufptr,
+                                          copy_bufsiz, &keysym,
+                                          &status_return);
+
+       /* android_lookup_string can't be called twice, so there's no
+          way to recover from buffer overflow.  */
+       if (status_return == ANDROID_BUFFER_OVERFLOW)
+         goto done_keysym;
+       else if (status_return == ANDROID_LOOKUP_NONE)
+         {
+           /* Don't skip preedit text events.  */
+           if (event->xkey.keycode != (uint32_t) -1)
+             goto done_keysym;
+         }
+       else if (status_return == ANDROID_LOOKUP_CHARS)
+         keysym = ANDROID_NO_SYMBOL;
+       else if (status_return != ANDROID_LOOKUP_KEYSYM
+                && status_return != ANDROID_LOOKUP_BOTH)
+         emacs_abort ();
+
+       /* Deal with pre-edit text events.  On Android, these are
+          simply encoded as events with associated strings and a
+          keycode set to ``-1''.  */
+
+       if (event->xkey.keycode == (uint32_t) -1)
+         {
+           inev.ie.kind = PREEDIT_TEXT_EVENT;
+           inev.ie.arg = Qnil;
+
+           /* If text was looked up, decode it and make it the
+              preedit text.  */
+
+           if (status_return == ANDROID_LOOKUP_CHARS && nchars)
+             {
+               copy_bufptr[nchars] = 0;
+               inev.ie.arg = from_unicode_buffer (copy_bufptr);
+             }
 
-      if (event->xkey.unicode_char >= 32
-         && event->xkey.unicode_char < 128)
+           goto done_keysym;
+         }
+      }
+
+      if (nchars == 1 && copy_bufptr[0] >= 32)
        {
-         inev.ie.kind = ASCII_KEYSTROKE_EVENT;
-         inev.ie.code = event->xkey.unicode_char;
+         /* Deal with characters.  */
+
+         if (copy_bufptr[0] < 128)
+           inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+         else
+           inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+
+         inev.ie.code = copy_bufptr[0];
        }
-      else if (event->xkey.unicode_char < 32)
+      else if (nchars < 2 && keysym)
        {
          /* If the key is a modifier key, just return.  */
-         if (ANDROID_IS_MODIFIER_KEY (event->xkey.keycode))
+         if (ANDROID_IS_MODIFIER_KEY (keysym))
            goto done_keysym;
 
          /* Next, deal with special ``characters'' by giving the
             keycode to keyboard.c.  */
          inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
-         inev.ie.code = event->xkey.keycode;
+         inev.ie.code = keysym;
        }
       else
        {
-         /* Finally, deal with Unicode characters.  */
-         inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
-         inev.ie.code = event->xkey.unicode_char;
+         /* Finally, deal with strings.  */
+
+         for (i = 0; i < nchars; ++i)
+           {
+             inev.ie.kind = (SINGLE_BYTE_CHAR_P (copy_bufptr[i])
+                             ? ASCII_KEYSTROKE_EVENT
+                             : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+             inev.ie.code = copy_bufptr[i];
+
+             /* If the character is actually '\n', then change this
+                to RET.  */
+
+             if (copy_bufptr[i] == '\n')
+               {
+                 inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+                 inev.ie.code = 66;
+               }
+
+             kbd_buffer_store_buffered_event (&inev, hold_quit);
+           }
+
+         count += nchars;
+         inev.ie.kind = NO_EVENT;  /* Already stored above.  */
        }
 
       goto done_keysym;
diff --git a/src/coding.c b/src/coding.c
index a2e0d7040f8..be5a0252ede 100644
--- a/src/coding.c
+++ b/src/coding.c
@@ -8495,7 +8495,7 @@ preferred_coding_system (void)
   return CODING_ID_NAME (id);
 }
 
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
 
 Lisp_Object
 from_unicode (Lisp_Object str)
@@ -8513,10 +8513,34 @@ from_unicode (Lisp_Object str)
 Lisp_Object
 from_unicode_buffer (const wchar_t *wstr)
 {
-  /* We get one of the two final null bytes for free.  */
-  ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr);
-  AUTO_STRING_WITH_LEN (str, (char *) wstr, len);
-  return from_unicode (str);
+  if (sizeof (wchar_t) == 2)
+    {
+      /* We get one of the two final null bytes for free.  */
+      ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr);
+      AUTO_STRING_WITH_LEN (str, (char *) wstr, len);
+      return from_unicode (str);
+    }
+  else
+    {
+      /* This code is used only on Android, where little endian UTF-16
+        strings are extended to 32-bit wchar_t.  */
+
+      uint16_t *words;
+      size_t length, i;
+
+      length = wcslen (wstr) + 1;
+
+      USE_SAFE_ALLOCA;
+      SAFE_NALLOCA (words, sizeof *words, length);
+
+      for (i = 0; i < length - 1; ++i)
+       words[i] = wstr[i];
+
+      words[i] = '\0';
+      AUTO_STRING_WITH_LEN (str, (char *) words,
+                           (length - 1) * sizeof *words);
+      return unbind_to (sa_count, from_unicode (str));
+    }
 }
 
 wchar_t *
@@ -8536,7 +8560,7 @@ to_unicode (Lisp_Object str, Lisp_Object *buf)
   return WCSDATA (*buf);
 }
 
-#endif /* WINDOWSNT || CYGWIN */
+#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */
 
 
 /*** 8. Emacs Lisp library functions ***/
diff --git a/src/coding.h b/src/coding.h
index b8d4f5f27e1..08c29c884a5 100644
--- a/src/coding.h
+++ b/src/coding.h
@@ -709,7 +709,7 @@ extern void encode_coding_object (struct coding_system *,
 /* Defined in this file.  */
 INLINE int surrogates_to_codepoint (int, int);
 
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
 
 /* These functions use Lisp string objects to store the UTF-16LE
    strings that modern versions of Windows expect.  These strings are
@@ -732,7 +732,7 @@ extern Lisp_Object from_unicode (Lisp_Object str);
 /* Convert WSTR to an Emacs string.  */
 extern Lisp_Object from_unicode_buffer (const wchar_t *wstr);
 
-#endif /* WINDOWSNT || CYGWIN */
+#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */
 
 /* Macros for backward compatibility.  */
 
diff --git a/src/sfnt.c b/src/sfnt.c
index cf36f388819..38f6984b93c 100644
--- a/src/sfnt.c
+++ b/src/sfnt.c
@@ -3758,16 +3758,7 @@ sfnt_edge_sort (struct sfnt_edge *edges, size_t size)
    coordinates, and incrementing the Y axis by SFNT_POLY_STEP instead
    of 1.  SFNT_POLY_STEP is chosen to always keep Y aligned to a grid
    placed such that there are always 1 << SFNT_POLY_SHIFT positions
-   available for each integral pixel coordinate.
-
-   Moving upwards is performed using Bresenham's algorithm.  Prior to
-   an edge being created, a single slope error is computed.  This is
-   how much X should increase for each increase in Y.
-
-   Upon each increase in Y, X initially does not move.  Instead, the
-   ``slope error'' is accumulated; once it exceeds the sample size,
-   one sample is subtracted from the accumulator, and X is increased
-   by one step.  */
+   available for each integral pixel coordinate.  */
 
 static void
 sfnt_poly_edges (struct sfnt_edge *edges, size_t size,



reply via email to

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