root/java/org/gnu/emacs/EmacsInputConnection.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. beginBatchEdit
  2. endBatchEdit
  3. commitCompletion
  4. commitCorrection
  5. commitText
  6. commitText
  7. deleteSurroundingText
  8. deleteSurroundingTextInCodePoints
  9. finishComposingText
  10. getSelectedText
  11. getTextAfterCursor
  12. getTextBeforeCursor
  13. setComposingText
  14. setComposingText
  15. setComposingRegion
  16. setComposingRegion
  17. performEditorAction
  18. performContextMenuAction
  19. getExtractedText
  20. setSelection
  21. sendKeyEvent
  22. requestCursorUpdates
  23. requestCursorUpdates
  24. getSurroundingText
  25. takeSnapshot
  26. closeConnection
  27. reset
  28. getHandler
  29. commitContent
  30. setImeConsumesInput
  31. clearMetaKeyStates
  32. reportFullscreenMode
  33. performSpellCheck
  34. performPrivateCommand
  35. getCursorCapsMode

     1 /* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 package org.gnu.emacs;
    21 
    22 import android.os.Build;
    23 import android.os.Bundle;
    24 import android.os.Handler;
    25 
    26 import android.view.KeyEvent;
    27 
    28 import android.view.inputmethod.CompletionInfo;
    29 import android.view.inputmethod.CorrectionInfo;
    30 import android.view.inputmethod.ExtractedText;
    31 import android.view.inputmethod.ExtractedTextRequest;
    32 import android.view.inputmethod.InputConnection;
    33 import android.view.inputmethod.InputContentInfo;
    34 import android.view.inputmethod.SurroundingText;
    35 import android.view.inputmethod.TextAttribute;
    36 import android.view.inputmethod.TextSnapshot;
    37 
    38 import android.util.Log;
    39 
    40 /* Android input methods, take number six.  See textconv.c for more
    41    details; this is more-or-less a thin wrapper around that file.  */
    42 
    43 public final class EmacsInputConnection implements InputConnection
    44 {
    45   private static final String TAG = "EmacsInputConnection";
    46 
    47   /* View associated with this input connection.  */
    48   private EmacsView view;
    49 
    50   /* The handle ID associated with that view's window.  */
    51   private short windowHandle;
    52 
    53   /* Number of batch edits currently underway.  Used to avoid
    54      synchronizing with the Emacs thread after each
    55      `endBatchEdit'.  */
    56   private int batchEditCount;
    57 
    58   /* Whether or not to synchronize and call `updateIC' with the
    59      selection position after committing text.
    60 
    61      This helps with on screen keyboard programs found in some vendor
    62      versions of Android, which rely on immediate updates to the point
    63      position after text is commited in order to place the cursor
    64      within that text.  */
    65 
    66   private static boolean syncAfterCommit;
    67 
    68   /* Whether or not to return empty text with the offset set to zero
    69      if a request arrives that has no flags set and has requested no
    70      characters at all.
    71 
    72      This is necessary with on screen keyboard programs found in some
    73      vendor versions of Android which don't rely on the documented
    74      meaning of `ExtractedText.startOffset', and instead take the
    75      selection offset inside at face value.  */
    76 
    77   private static boolean extractAbsoluteOffsets;
    78 
    79   static
    80   {
    81     if (Build.MANUFACTURER.equalsIgnoreCase ("Huawei")
    82         || Build.MANUFACTURER.equalsIgnoreCase ("Honor"))
    83       extractAbsoluteOffsets = syncAfterCommit = true;
    84 
    85     /* The Samsung and Vivo keyboards take `selectionStart' at face
    86        value if some text is returned, and also searches for words
    87        solely within that text.  However, when no text is returned, it
    88        falls back to getTextAfterCursor and getTextBeforeCursor.  */
    89     if (Build.MANUFACTURER.equalsIgnoreCase ("Samsung")
    90         || Build.MANUFACTURER.equalsIgnoreCase ("Vivo"))
    91       extractAbsoluteOffsets = true;
    92   };
    93 
    94 
    95   public
    96   EmacsInputConnection (EmacsView view)
    97   {
    98     this.view = view;
    99     this.windowHandle = view.window.handle;
   100   }
   101 
   102 
   103   /* The functions below are called by input methods whenever they
   104      need to perform an edit.  */
   105 
   106   @Override
   107   public boolean
   108   beginBatchEdit ()
   109   {
   110     /* Return if the input connection is out of date.  */
   111     if (view.icSerial < view.icGeneration)
   112       return false;
   113 
   114     if (EmacsService.DEBUG_IC)
   115       Log.d (TAG, "beginBatchEdit");
   116 
   117     EmacsNative.beginBatchEdit (windowHandle);
   118 
   119     /* Keep a record of the number of outstanding batch edits here as
   120        well.  */
   121     batchEditCount++;
   122     return true;
   123   }
   124 
   125   @Override
   126   public boolean
   127   endBatchEdit ()
   128   {
   129     /* Return if the input connection is out of date.  */
   130     if (view.icSerial < view.icGeneration)
   131       return false;
   132 
   133     if (EmacsService.DEBUG_IC)
   134       Log.d (TAG, "endBatchEdit");
   135 
   136     EmacsNative.endBatchEdit (windowHandle);
   137 
   138     /* Subtract one from the UI thread record of the number of batch
   139        edits currently under way.  */
   140 
   141     if (batchEditCount > 0)
   142       batchEditCount -= 1;
   143 
   144     return batchEditCount > 0;
   145   }
   146 
   147   public boolean
   148   commitCompletion (CompletionInfo info)
   149   {
   150     /* Return if the input connection is out of date.  */
   151     if (view.icSerial < view.icGeneration)
   152       return false;
   153 
   154     if (EmacsService.DEBUG_IC)
   155       Log.d (TAG, "commitCompletion: " + info);
   156 
   157     EmacsNative.commitCompletion (windowHandle,
   158                                   info.getText ().toString (),
   159                                   info.getPosition ());
   160     return true;
   161   }
   162 
   163   @Override
   164   public boolean
   165   commitCorrection (CorrectionInfo info)
   166   {
   167     /* The input method calls this function not to commit text, but to
   168        indicate that a subsequent edit will consist of a correction.
   169        Emacs has no use for this information.
   170 
   171        Of course this completely contradicts the provided
   172        documentation, but this is how Android actually behaves.  */
   173     return false;
   174   }
   175 
   176   @Override
   177   public boolean
   178   commitText (CharSequence text, int newCursorPosition)
   179   {
   180     int[] selection;
   181 
   182     /* Return if the input connection is out of date.  */
   183     if (view.icSerial < view.icGeneration)
   184       return false;
   185 
   186     if (EmacsService.DEBUG_IC)
   187       Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
   188 
   189     EmacsNative.commitText (windowHandle, text.toString (),
   190                             newCursorPosition);
   191 
   192     if (syncAfterCommit)
   193       {
   194         /* Synchronize with the Emacs thread, obtain the new
   195            selection, and report it immediately.  */
   196 
   197         selection = EmacsNative.getSelection (windowHandle);
   198 
   199         if (EmacsService.DEBUG_IC && selection != null)
   200           Log.d (TAG, "commitText: new selection is " + selection[0]
   201                  + ", by " + selection[1]);
   202 
   203         if (selection != null)
   204           /* N.B. that the composing region is removed after text is
   205              committed.  */
   206           view.imManager.updateSelection (view, selection[0],
   207                                           selection[1], -1, -1);
   208       }
   209 
   210     return true;
   211   }
   212 
   213   @Override
   214   public boolean
   215   commitText (CharSequence text, int newCursorPosition,
   216               TextAttribute textAttribute)
   217   {
   218     return commitText (text, newCursorPosition);
   219   }
   220 
   221   @Override
   222   public boolean
   223   deleteSurroundingText (int leftLength, int rightLength)
   224   {
   225     /* Return if the input connection is out of date.  */
   226     if (view.icSerial < view.icGeneration)
   227       return false;
   228 
   229     if (EmacsService.DEBUG_IC)
   230       Log.d (TAG, ("deleteSurroundingText: "
   231                    + leftLength + " " + rightLength));
   232 
   233     EmacsNative.deleteSurroundingText (windowHandle, leftLength,
   234                                        rightLength);
   235     return true;
   236   }
   237 
   238   @Override
   239   public boolean
   240   deleteSurroundingTextInCodePoints (int leftLength, int rightLength)
   241   {
   242     /* Emacs returns characters which cannot be represented in a Java
   243        `char' as NULL characters, so code points always reflect
   244        characters themselves.  */
   245     return deleteSurroundingText (leftLength, rightLength);
   246   }
   247 
   248   @Override
   249   public boolean
   250   finishComposingText ()
   251   {
   252     /* Return if the input connection is out of date.  */
   253     if (view.icSerial < view.icGeneration)
   254       return false;
   255 
   256     if (EmacsService.DEBUG_IC)
   257       Log.d (TAG, "finishComposingText");
   258 
   259     EmacsNative.finishComposingText (windowHandle);
   260     return true;
   261   }
   262 
   263   @Override
   264   public String
   265   getSelectedText (int flags)
   266   {
   267     /* Return if the input connection is out of date.  */
   268     if (view.icSerial < view.icGeneration)
   269       return null;
   270 
   271     if (EmacsService.DEBUG_IC)
   272       Log.d (TAG, "getSelectedText: " + flags);
   273 
   274     return EmacsNative.getSelectedText (windowHandle, flags);
   275   }
   276 
   277   @Override
   278   public String
   279   getTextAfterCursor (int length, int flags)
   280   {
   281     String string;
   282 
   283     /* Return if the input connection is out of date.  */
   284     if (view.icSerial < view.icGeneration)
   285       return null;
   286 
   287     if (EmacsService.DEBUG_IC)
   288       Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
   289 
   290     string = EmacsNative.getTextAfterCursor (windowHandle, length,
   291                                              flags);
   292 
   293     if (EmacsService.DEBUG_IC)
   294       Log.d (TAG, "   --> " + string);
   295 
   296     return string;
   297   }
   298 
   299   @Override
   300   public String
   301   getTextBeforeCursor (int length, int flags)
   302   {
   303     String string;
   304 
   305     /* Return if the input connection is out of date.  */
   306     if (view.icSerial < view.icGeneration)
   307       return null;
   308 
   309     if (EmacsService.DEBUG_IC)
   310       Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
   311 
   312     string = EmacsNative.getTextBeforeCursor (windowHandle, length,
   313                                               flags);
   314 
   315     if (EmacsService.DEBUG_IC)
   316       Log.d (TAG, "   --> " + string);
   317 
   318     return string;
   319   }
   320 
   321   @Override
   322   public boolean
   323   setComposingText (CharSequence text, int newCursorPosition)
   324   {
   325     /* Return if the input connection is out of date.  */
   326     if (view.icSerial < view.icGeneration)
   327       return false;
   328 
   329     if (EmacsService.DEBUG_IC)
   330       Log.d (TAG, ("setComposingText: "
   331                    + text + " ## " + newCursorPosition));
   332 
   333     EmacsNative.setComposingText (windowHandle, text.toString (),
   334                                   newCursorPosition);
   335     return true;
   336   }
   337 
   338   @Override
   339   public boolean
   340   setComposingText (CharSequence text, int newCursorPosition,
   341                     TextAttribute textAttribute)
   342   {
   343     return setComposingText (text, newCursorPosition);
   344   }
   345 
   346   @Override
   347   public boolean
   348   setComposingRegion (int start, int end)
   349   {
   350     /* Return if the input connection is out of date.  */
   351     if (view.icSerial < view.icGeneration)
   352       return false;
   353 
   354     if (EmacsService.DEBUG_IC)
   355       Log.d (TAG, "setComposingRegion: " + start + " " + end);
   356 
   357     EmacsNative.setComposingRegion (windowHandle, start, end);
   358     return true;
   359   }
   360 
   361   @Override
   362   public boolean
   363   setComposingRegion (int start, int end, TextAttribute textAttribute)
   364   {
   365     return setComposingRegion (start, end);
   366   }
   367 
   368   @Override
   369   public boolean
   370   performEditorAction (int editorAction)
   371   {
   372     /* Return if the input connection is out of date.  */
   373     if (view.icSerial < view.icGeneration)
   374       return false;
   375 
   376     if (EmacsService.DEBUG_IC)
   377       Log.d (TAG, "performEditorAction: " + editorAction);
   378 
   379     EmacsNative.performEditorAction (windowHandle, editorAction);
   380     return true;
   381   }
   382 
   383   @Override
   384   public boolean
   385   performContextMenuAction (int contextMenuAction)
   386   {
   387     int action;
   388 
   389     /* Return if the input connection is out of date.  */
   390     if (view.icSerial < view.icGeneration)
   391       return false;
   392 
   393     if (EmacsService.DEBUG_IC)
   394       Log.d (TAG, "performContextMenuAction: " + contextMenuAction);
   395 
   396     /* Translate the action in Java code.  That way, a great deal of
   397        JNI boilerplate can be avoided.  */
   398 
   399     switch (contextMenuAction)
   400       {
   401       case android.R.id.selectAll:
   402         action = 0;
   403         break;
   404 
   405       case android.R.id.startSelectingText:
   406         action = 1;
   407         break;
   408 
   409       case android.R.id.stopSelectingText:
   410         action = 2;
   411         break;
   412 
   413       case android.R.id.cut:
   414         action = 3;
   415         break;
   416 
   417       case android.R.id.copy:
   418         action = 4;
   419         break;
   420 
   421       case android.R.id.paste:
   422         action = 5;
   423         break;
   424 
   425       default:
   426         return true;
   427       }
   428 
   429     EmacsNative.performContextMenuAction (windowHandle, action);
   430     return true;
   431   }
   432 
   433   @Override
   434   public ExtractedText
   435   getExtractedText (ExtractedTextRequest request, int flags)
   436   {
   437     ExtractedText text;
   438     int[] selection;
   439 
   440     /* Return if the input connection is out of date.  */
   441     if (view.icSerial < view.icGeneration)
   442       return null;
   443 
   444     if (EmacsService.DEBUG_IC)
   445       Log.d (TAG, "getExtractedText: " + request.hintMaxChars + ", "
   446              + request.hintMaxLines + " " + flags);
   447 
   448     /* If a request arrives with hintMaxChars, hintMaxLines and flags
   449        set to 0, and the system is known to be buggy, return an empty
   450        extracted text object with the absolute selection positions.  */
   451 
   452     if (extractAbsoluteOffsets
   453         && request.hintMaxChars == 0
   454         && request.hintMaxLines == 0
   455         && flags == 0)
   456       {
   457         /* Obtain the selection.  */
   458         selection = EmacsNative.getSelection (windowHandle);
   459         if (selection == null)
   460           return null;
   461 
   462         /* Create the workaround extracted text.  */
   463         text = new ExtractedText ();
   464         text.partialStartOffset = -1;
   465         text.partialEndOffset = -1;
   466         text.text = "";
   467         text.selectionStart = selection[0];
   468         text.selectionEnd = selection[1];
   469       }
   470     else
   471       text = EmacsNative.getExtractedText (windowHandle, request,
   472                                            flags);
   473 
   474     if (text == null)
   475       {
   476         if (EmacsService.DEBUG_IC)
   477           Log.d (TAG, "getExtractedText: text is NULL");
   478 
   479         return null;
   480       }
   481 
   482     if (EmacsService.DEBUG_IC)
   483       Log.d (TAG, "getExtractedText: " + text.text + " @"
   484              + text.startOffset + ":" + text.selectionStart
   485              + ", " + text.selectionEnd);
   486 
   487     return text;
   488   }
   489 
   490   @Override
   491   public boolean
   492   setSelection (int start, int end)
   493   {
   494     /* Return if the input connection is out of date.  */
   495     if (view.icSerial < view.icGeneration)
   496       return false;
   497 
   498     if (EmacsService.DEBUG_IC)
   499       Log.d (TAG, "setSelection: " + start + " " + end);
   500 
   501     EmacsNative.setSelection (windowHandle, start, end);
   502     return true;
   503   }
   504 
   505   @Override
   506   /* ACTION_MULTIPLE is apparently obsolete.  */
   507   @SuppressWarnings ("deprecation")
   508   public boolean
   509   sendKeyEvent (KeyEvent key)
   510   {
   511     /* Return if the input connection is out of date.  */
   512     if (view.icSerial < view.icGeneration)
   513       return false;
   514 
   515     if (EmacsService.DEBUG_IC)
   516       Log.d (TAG, "sendKeyEvent: " + key);
   517 
   518     /* Use the standard API if possible.  */
   519 
   520     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
   521       view.imManager.dispatchKeyEventFromInputMethod (view, key);
   522     else
   523       {
   524         /* Fall back to dispatching the event manually if not.  */
   525 
   526         switch (key.getAction ())
   527           {
   528           case KeyEvent.ACTION_DOWN:
   529             view.onKeyDown (key.getKeyCode (), key);
   530             break;
   531 
   532           case KeyEvent.ACTION_UP:
   533             view.onKeyUp (key.getKeyCode (), key);
   534             break;
   535 
   536           case KeyEvent.ACTION_MULTIPLE:
   537             view.onKeyMultiple (key.getKeyCode (),
   538                                 key.getRepeatCount (),
   539                                 key);
   540             break;
   541           }
   542       }
   543 
   544     return true;
   545   }
   546 
   547   @Override
   548   public boolean
   549   requestCursorUpdates (int cursorUpdateMode)
   550   {
   551     /* Return if the input connection is out of date.  */
   552     if (view.icSerial < view.icGeneration)
   553       return false;
   554 
   555     if (EmacsService.DEBUG_IC)
   556       Log.d (TAG, "requestCursorUpdates: " + cursorUpdateMode);
   557 
   558     EmacsNative.requestCursorUpdates (windowHandle, cursorUpdateMode);
   559     return true;
   560   }
   561 
   562   @Override
   563   public boolean
   564   requestCursorUpdates (int cursorUpdateMode, int filter)
   565   {
   566     if (filter != 0)
   567       return false;
   568 
   569     return requestCursorUpdates (cursorUpdateMode);
   570   }
   571 
   572   @Override
   573   public SurroundingText
   574   getSurroundingText (int beforeLength, int afterLength,
   575                       int flags)
   576   {
   577     SurroundingText text;
   578 
   579     /* Return if the input connection is out of date.  */
   580     if (view.icSerial < view.icGeneration)
   581       return null;
   582 
   583     if (EmacsService.DEBUG_IC)
   584       Log.d (TAG, ("getSurroundingText: " + beforeLength + ", "
   585                    + afterLength));
   586 
   587     text = EmacsNative.getSurroundingText (windowHandle, beforeLength,
   588                                            afterLength, flags);
   589 
   590     if (EmacsService.DEBUG_IC && text != null)
   591       Log.d (TAG, ("getSurroundingText: "
   592                    + text.getSelectionStart ()
   593                    + ","
   594                    + text.getSelectionEnd ()
   595                    + "+"
   596                    + text.getOffset ()
   597                    + ": "
   598                    + text.getText ()));
   599 
   600     return text;
   601   }
   602 
   603   @Override
   604   public TextSnapshot
   605   takeSnapshot ()
   606   {
   607     TextSnapshot snapshot;
   608 
   609     /* Return if the input connection is out of date.  */
   610     if (view.icSerial < view.icGeneration)
   611       return null;
   612 
   613     snapshot = EmacsNative.takeSnapshot (windowHandle);
   614 
   615     if (EmacsService.DEBUG_IC)
   616       Log.d (TAG, ("takeSnapshot: "
   617                    + snapshot.getSurroundingText ().getText ()
   618                    + " @ " + snapshot.getCompositionEnd ()
   619                    + ", " + snapshot.getCompositionStart ()));
   620 
   621     return snapshot;
   622   }
   623 
   624   @Override
   625   public void
   626   closeConnection ()
   627   {
   628     batchEditCount = 0;
   629   }
   630 
   631 
   632 
   633   public void
   634   reset ()
   635   {
   636     batchEditCount = 0;
   637   }
   638 
   639 
   640   /* Override functions which are not implemented.  */
   641 
   642   @Override
   643   public Handler
   644   getHandler ()
   645   {
   646     return null;
   647   }
   648 
   649   @Override
   650   public boolean
   651   commitContent (InputContentInfo inputContentInfo, int flags,
   652                  Bundle opts)
   653   {
   654     return false;
   655   }
   656 
   657   @Override
   658   public boolean
   659   setImeConsumesInput (boolean imeConsumesInput)
   660   {
   661     return false;
   662   }
   663 
   664   @Override
   665   public boolean
   666   clearMetaKeyStates (int states)
   667   {
   668     return false;
   669   }
   670 
   671   @Override
   672   public boolean
   673   reportFullscreenMode (boolean enabled)
   674   {
   675     return false;
   676   }
   677 
   678   @Override
   679   public boolean
   680   performSpellCheck ()
   681   {
   682     return false;
   683   }
   684 
   685   @Override
   686   public boolean
   687   performPrivateCommand (String action, Bundle data)
   688   {
   689     return false;
   690   }
   691 
   692   @Override
   693   public int
   694   getCursorCapsMode (int reqModes)
   695   {
   696     return 0;
   697   }
   698 }

/* [<][>][^][v][top][bottom][index][help] */