1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-26 23:31:55 -08:00
emacs/java/org/gnu/emacs/EmacsInputConnection.java
Po Lu 4c390f14f4 Clean up Android debug code
* java/org/gnu/emacs/EmacsInputConnection.java
(getSurroundingText): Don't print debug information if DEBUG_IC
is off.
2023-07-14 08:51:07 +08:00

698 lines
17 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
import android.view.inputmethod.TextSnapshot;
import android.util.Log;
/* Android input methods, take number six. See textconv.c for more
details; this is more-or-less a thin wrapper around that file. */
public final class EmacsInputConnection implements InputConnection
{
private static final String TAG = "EmacsInputConnection";
/* View associated with this input connection. */
private EmacsView view;
/* The handle ID associated with that view's window. */
private short windowHandle;
/* Number of batch edits currently underway. Used to avoid
synchronizing with the Emacs thread after each
`endBatchEdit'. */
private int batchEditCount;
/* Whether or not to synchronize and call `updateIC' with the
selection position after committing text.
This helps with on screen keyboard programs found in some vendor
versions of Android, which rely on immediate updates to the point
position after text is commited in order to place the cursor
within that text. */
private static boolean syncAfterCommit;
/* Whether or not to return empty text with the offset set to zero
if a request arrives that has no flags set and has requested no
characters at all.
This is necessary with on screen keyboard programs found in some
vendor versions of Android which don't rely on the documented
meaning of `ExtractedText.startOffset', and instead take the
selection offset inside at face value. */
private static boolean extractAbsoluteOffsets;
static
{
if (Build.MANUFACTURER.equalsIgnoreCase ("Huawei")
|| Build.MANUFACTURER.equalsIgnoreCase ("Honor"))
extractAbsoluteOffsets = syncAfterCommit = true;
/* The Samsung and Vivo keyboards take `selectionStart' at face
value if some text is returned, and also searches for words
solely within that text. However, when no text is returned, it
falls back to getTextAfterCursor and getTextBeforeCursor. */
if (Build.MANUFACTURER.equalsIgnoreCase ("Samsung")
|| Build.MANUFACTURER.equalsIgnoreCase ("Vivo"))
extractAbsoluteOffsets = true;
};
public
EmacsInputConnection (EmacsView view)
{
this.view = view;
this.windowHandle = view.window.handle;
}
/* The functions below are called by input methods whenever they
need to perform an edit. */
@Override
public boolean
beginBatchEdit ()
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "beginBatchEdit");
EmacsNative.beginBatchEdit (windowHandle);
/* Keep a record of the number of outstanding batch edits here as
well. */
batchEditCount++;
return true;
}
@Override
public boolean
endBatchEdit ()
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "endBatchEdit");
EmacsNative.endBatchEdit (windowHandle);
/* Subtract one from the UI thread record of the number of batch
edits currently under way. */
if (batchEditCount > 0)
batchEditCount -= 1;
return batchEditCount > 0;
}
public boolean
commitCompletion (CompletionInfo info)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitCompletion: " + info);
EmacsNative.commitCompletion (windowHandle,
info.getText ().toString (),
info.getPosition ());
return true;
}
@Override
public boolean
commitCorrection (CorrectionInfo info)
{
/* The input method calls this function not to commit text, but to
indicate that a subsequent edit will consist of a correction.
Emacs has no use for this information.
Of course this completely contradicts the provided
documentation, but this is how Android actually behaves. */
return false;
}
@Override
public boolean
commitText (CharSequence text, int newCursorPosition)
{
int[] selection;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
EmacsNative.commitText (windowHandle, text.toString (),
newCursorPosition);
if (syncAfterCommit)
{
/* Synchronize with the Emacs thread, obtain the new
selection, and report it immediately. */
selection = EmacsNative.getSelection (windowHandle);
if (EmacsService.DEBUG_IC && selection != null)
Log.d (TAG, "commitText: new selection is " + selection[0]
+ ", by " + selection[1]);
if (selection != null)
/* N.B. that the composing region is removed after text is
committed. */
view.imManager.updateSelection (view, selection[0],
selection[1], -1, -1);
}
return true;
}
@Override
public boolean
commitText (CharSequence text, int newCursorPosition,
TextAttribute textAttribute)
{
return commitText (text, newCursorPosition);
}
@Override
public boolean
deleteSurroundingText (int leftLength, int rightLength)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("deleteSurroundingText: "
+ leftLength + " " + rightLength));
EmacsNative.deleteSurroundingText (windowHandle, leftLength,
rightLength);
return true;
}
@Override
public boolean
deleteSurroundingTextInCodePoints (int leftLength, int rightLength)
{
/* Emacs returns characters which cannot be represented in a Java
`char' as NULL characters, so code points always reflect
characters themselves. */
return deleteSurroundingText (leftLength, rightLength);
}
@Override
public boolean
finishComposingText ()
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "finishComposingText");
EmacsNative.finishComposingText (windowHandle);
return true;
}
@Override
public String
getSelectedText (int flags)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getSelectedText: " + flags);
return EmacsNative.getSelectedText (windowHandle, flags);
}
@Override
public String
getTextAfterCursor (int length, int flags)
{
String string;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
string = EmacsNative.getTextAfterCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, " --> " + string);
return string;
}
@Override
public String
getTextBeforeCursor (int length, int flags)
{
String string;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
string = EmacsNative.getTextBeforeCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, " --> " + string);
return string;
}
@Override
public boolean
setComposingText (CharSequence text, int newCursorPosition)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("setComposingText: "
+ text + " ## " + newCursorPosition));
EmacsNative.setComposingText (windowHandle, text.toString (),
newCursorPosition);
return true;
}
@Override
public boolean
setComposingText (CharSequence text, int newCursorPosition,
TextAttribute textAttribute)
{
return setComposingText (text, newCursorPosition);
}
@Override
public boolean
setComposingRegion (int start, int end)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setComposingRegion: " + start + " " + end);
EmacsNative.setComposingRegion (windowHandle, start, end);
return true;
}
@Override
public boolean
setComposingRegion (int start, int end, TextAttribute textAttribute)
{
return setComposingRegion (start, end);
}
@Override
public boolean
performEditorAction (int editorAction)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performEditorAction: " + editorAction);
EmacsNative.performEditorAction (windowHandle, editorAction);
return true;
}
@Override
public boolean
performContextMenuAction (int contextMenuAction)
{
int action;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performContextMenuAction: " + contextMenuAction);
/* Translate the action in Java code. That way, a great deal of
JNI boilerplate can be avoided. */
switch (contextMenuAction)
{
case android.R.id.selectAll:
action = 0;
break;
case android.R.id.startSelectingText:
action = 1;
break;
case android.R.id.stopSelectingText:
action = 2;
break;
case android.R.id.cut:
action = 3;
break;
case android.R.id.copy:
action = 4;
break;
case android.R.id.paste:
action = 5;
break;
default:
return true;
}
EmacsNative.performContextMenuAction (windowHandle, action);
return true;
}
@Override
public ExtractedText
getExtractedText (ExtractedTextRequest request, int flags)
{
ExtractedText text;
int[] selection;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: " + request.hintMaxChars + ", "
+ request.hintMaxLines + " " + flags);
/* If a request arrives with hintMaxChars, hintMaxLines and flags
set to 0, and the system is known to be buggy, return an empty
extracted text object with the absolute selection positions. */
if (extractAbsoluteOffsets
&& request.hintMaxChars == 0
&& request.hintMaxLines == 0
&& flags == 0)
{
/* Obtain the selection. */
selection = EmacsNative.getSelection (windowHandle);
if (selection == null)
return null;
/* Create the workaround extracted text. */
text = new ExtractedText ();
text.partialStartOffset = -1;
text.partialEndOffset = -1;
text.text = "";
text.selectionStart = selection[0];
text.selectionEnd = selection[1];
}
else
text = EmacsNative.getExtractedText (windowHandle, request,
flags);
if (text == null)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: text is NULL");
return null;
}
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: " + text.text + " @"
+ text.startOffset + ":" + text.selectionStart
+ ", " + text.selectionEnd);
return text;
}
@Override
public boolean
setSelection (int start, int end)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setSelection: " + start + " " + end);
EmacsNative.setSelection (windowHandle, start, end);
return true;
}
@Override
/* ACTION_MULTIPLE is apparently obsolete. */
@SuppressWarnings ("deprecation")
public boolean
sendKeyEvent (KeyEvent key)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "sendKeyEvent: " + key);
/* Use the standard API if possible. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
view.imManager.dispatchKeyEventFromInputMethod (view, key);
else
{
/* Fall back to dispatching the event manually if not. */
switch (key.getAction ())
{
case KeyEvent.ACTION_DOWN:
view.onKeyDown (key.getKeyCode (), key);
break;
case KeyEvent.ACTION_UP:
view.onKeyUp (key.getKeyCode (), key);
break;
case KeyEvent.ACTION_MULTIPLE:
view.onKeyMultiple (key.getKeyCode (),
key.getRepeatCount (),
key);
break;
}
}
return true;
}
@Override
public boolean
requestCursorUpdates (int cursorUpdateMode)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "requestCursorUpdates: " + cursorUpdateMode);
EmacsNative.requestCursorUpdates (windowHandle, cursorUpdateMode);
return true;
}
@Override
public boolean
requestCursorUpdates (int cursorUpdateMode, int filter)
{
if (filter != 0)
return false;
return requestCursorUpdates (cursorUpdateMode);
}
@Override
public SurroundingText
getSurroundingText (int beforeLength, int afterLength,
int flags)
{
SurroundingText text;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("getSurroundingText: " + beforeLength + ", "
+ afterLength));
text = EmacsNative.getSurroundingText (windowHandle, beforeLength,
afterLength, flags);
if (EmacsService.DEBUG_IC && text != null)
Log.d (TAG, ("getSurroundingText: "
+ text.getSelectionStart ()
+ ","
+ text.getSelectionEnd ()
+ "+"
+ text.getOffset ()
+ ": "
+ text.getText ()));
return text;
}
@Override
public TextSnapshot
takeSnapshot ()
{
TextSnapshot snapshot;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
snapshot = EmacsNative.takeSnapshot (windowHandle);
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("takeSnapshot: "
+ snapshot.getSurroundingText ().getText ()
+ " @ " + snapshot.getCompositionEnd ()
+ ", " + snapshot.getCompositionStart ()));
return snapshot;
}
@Override
public void
closeConnection ()
{
batchEditCount = 0;
}
public void
reset ()
{
batchEditCount = 0;
}
/* Override functions which are not implemented. */
@Override
public Handler
getHandler ()
{
return null;
}
@Override
public boolean
commitContent (InputContentInfo inputContentInfo, int flags,
Bundle opts)
{
return false;
}
@Override
public boolean
setImeConsumesInput (boolean imeConsumesInput)
{
return false;
}
@Override
public boolean
clearMetaKeyStates (int states)
{
return false;
}
@Override
public boolean
reportFullscreenMode (boolean enabled)
{
return false;
}
@Override
public boolean
performSpellCheck ()
{
return false;
}
@Override
public boolean
performPrivateCommand (String action, Bundle data)
{
return false;
}
@Override
public int
getCursorCapsMode (int reqModes)
{
return 0;
}
}