|
|
@@ -0,0 +1,310 @@
|
|
|
+using System.Collections.Concurrent;
|
|
|
+using System.Threading;
|
|
|
+using Android.OS;
|
|
|
+using Android.Runtime;
|
|
|
+using Android.Text;
|
|
|
+using Android.Views;
|
|
|
+using Android.Views.InputMethods;
|
|
|
+using Avalonia.Android.Platform.SkiaPlatform;
|
|
|
+using Avalonia.Input;
|
|
|
+using Avalonia.Input.TextInput;
|
|
|
+using Java.Lang;
|
|
|
+
|
|
|
+namespace Avalonia.Android.Platform.Input
|
|
|
+{
|
|
|
+ internal class AvaloniaInputConnection : Object, IInputConnection
|
|
|
+ {
|
|
|
+ private readonly TopLevelImpl _toplevel;
|
|
|
+ private readonly IAndroidInputMethod _inputMethod;
|
|
|
+ private readonly TextEditBuffer _editBuffer;
|
|
|
+ private readonly ConcurrentQueue<EditCommand> _commandQueue;
|
|
|
+
|
|
|
+ private int _batchLevel = 0;
|
|
|
+
|
|
|
+ public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod)
|
|
|
+ {
|
|
|
+ _toplevel = toplevel;
|
|
|
+ _inputMethod = inputMethod;
|
|
|
+ _editBuffer = new TextEditBuffer(_inputMethod, toplevel);
|
|
|
+ _commandQueue = new ConcurrentQueue<EditCommand>();
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ExtractedTextToken { get; private set; }
|
|
|
+
|
|
|
+ public IAndroidInputMethod InputMethod => _inputMethod;
|
|
|
+
|
|
|
+ public TopLevelImpl Toplevel => _toplevel;
|
|
|
+
|
|
|
+ public bool IsInBatchEdit => _batchLevel > 0;
|
|
|
+ public bool IsInMonitorMode { get; private set; }
|
|
|
+
|
|
|
+ public Handler? Handler => null;
|
|
|
+
|
|
|
+ public TextEditBuffer EditBuffer => _editBuffer;
|
|
|
+
|
|
|
+ public bool IsInUpdate { get; set; }
|
|
|
+
|
|
|
+ public bool SetComposingRegion(int start, int end)
|
|
|
+ {
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ QueueCommand(new CompositionRegionCommand(start, end));
|
|
|
+ }
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool SetComposingText(ICharSequence? text, int newCursorPosition)
|
|
|
+ {
|
|
|
+ if (text is null)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ var compositionText = text.SubSequence(0, text.Length());
|
|
|
+ QueueCommand(new CompositionTextCommand(compositionText, newCursorPosition));
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool SetSelection(int start, int end)
|
|
|
+ {
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ if (IsInUpdate)
|
|
|
+ new SelectionCommand(start, end).Apply(EditBuffer);
|
|
|
+ else
|
|
|
+ QueueCommand(new SelectionCommand(start, end));
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool BeginBatchEdit()
|
|
|
+ {
|
|
|
+ _batchLevel = Interlocked.Increment(ref _batchLevel);
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool EndBatchEdit()
|
|
|
+ {
|
|
|
+ _batchLevel = Interlocked.Decrement(ref _batchLevel);
|
|
|
+
|
|
|
+ if (!IsInBatchEdit)
|
|
|
+ {
|
|
|
+ IsInUpdate = true;
|
|
|
+ while (_commandQueue.TryDequeue(out var command))
|
|
|
+ {
|
|
|
+ command.Apply(_editBuffer);
|
|
|
+ }
|
|
|
+ IsInUpdate = false;
|
|
|
+ }
|
|
|
+ return IsInBatchEdit;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool CommitText(ICharSequence? text, int newCursorPosition)
|
|
|
+ {
|
|
|
+ if (InputMethod.Client is null || text is null)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ var committedText = text.SubSequence(0, text.Length());
|
|
|
+ QueueCommand(new CommitTextCommand(committedText, newCursorPosition));
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool DeleteSurroundingText(int beforeLength, int afterLength)
|
|
|
+ {
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ QueueCommand(new DeleteRegionCommand(beforeLength, afterLength));
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
|
|
|
+ {
|
|
|
+ switch (actionCode)
|
|
|
+ {
|
|
|
+ case ImeAction.Done:
|
|
|
+ {
|
|
|
+ _inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ImeAction.Next:
|
|
|
+ {
|
|
|
+ FocusManager.GetFocusManager(_toplevel.InputRoot)?
|
|
|
+ .TryMoveFocus(NavigationDirection.Next);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ExtractedText? GetExtractedText(ExtractedTextRequest? request, [GeneratedEnum] GetTextFlags flags)
|
|
|
+ {
|
|
|
+ IsInMonitorMode = ((int)flags & (int)TextExtractFlags.Monitor) != 0;
|
|
|
+
|
|
|
+ ExtractedTextToken = IsInMonitorMode ? request?.Token ?? 0 : ExtractedTextToken;
|
|
|
+
|
|
|
+ if (!_inputMethod.IsActive)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ var extract = new ExtractedText
|
|
|
+ {
|
|
|
+ Flags = 0,
|
|
|
+ PartialStartOffset = -1,
|
|
|
+ PartialEndOffset = -1,
|
|
|
+ SelectionStart = _editBuffer.Selection.Start,
|
|
|
+ SelectionEnd = _editBuffer.Selection.End,
|
|
|
+ StartOffset = 0
|
|
|
+ };
|
|
|
+
|
|
|
+ extract.Text = new SpannableString(_editBuffer.Text);
|
|
|
+
|
|
|
+ return extract;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool PerformContextMenuAction(int id)
|
|
|
+ {
|
|
|
+ if (InputMethod.Client is not { } client)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ switch (id)
|
|
|
+ {
|
|
|
+ case global::Android.Resource.Id.SelectAll:
|
|
|
+ client.ExecuteContextMenuAction(ContextMenuAction.SelectAll);
|
|
|
+ return true;
|
|
|
+ case global::Android.Resource.Id.Cut:
|
|
|
+ client.ExecuteContextMenuAction(ContextMenuAction.Cut);
|
|
|
+ return true;
|
|
|
+ case global::Android.Resource.Id.Copy:
|
|
|
+ client.ExecuteContextMenuAction(ContextMenuAction.Copy);
|
|
|
+ return true;
|
|
|
+ case global::Android.Resource.Id.Paste:
|
|
|
+ client.ExecuteContextMenuAction(ContextMenuAction.Paste);
|
|
|
+ return true;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ClearMetaKeyStates([GeneratedEnum] MetaKeyStates states)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void CloseConnection()
|
|
|
+ {
|
|
|
+ _commandQueue.Clear();
|
|
|
+ _batchLevel = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool CommitCompletion(CompletionInfo? text)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool CommitContent(InputContentInfo inputContentInfo, [GeneratedEnum] InputContentFlags flags, Bundle? opts)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool CommitCorrection(CorrectionInfo? correctionInfo)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool DeleteSurroundingTextInCodePoints(int beforeLength, int afterLength)
|
|
|
+ {
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ QueueCommand(new DeleteRegionInCodePointsCommand(beforeLength, afterLength));
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool FinishComposingText()
|
|
|
+ {
|
|
|
+ if (InputMethod.IsActive)
|
|
|
+ {
|
|
|
+ QueueCommand(new FinishComposingCommand());
|
|
|
+ }
|
|
|
+
|
|
|
+ return InputMethod.IsActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ [return: GeneratedEnum]
|
|
|
+ public CapitalizationMode GetCursorCapsMode([GeneratedEnum] CapitalizationMode reqModes)
|
|
|
+ {
|
|
|
+ return TextUtils.GetCapsMode(_editBuffer.Text, _editBuffer.Selection.Start, reqModes);
|
|
|
+ }
|
|
|
+
|
|
|
+ public ICharSequence? GetSelectedTextFormatted([GeneratedEnum] GetTextFlags flags)
|
|
|
+ {
|
|
|
+ return new SpannableString(_editBuffer.SelectedText);
|
|
|
+ }
|
|
|
+
|
|
|
+ public ICharSequence? GetTextAfterCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)
|
|
|
+ {
|
|
|
+ var end = Math.Min(_editBuffer.Selection.End, _editBuffer.Text.Length);
|
|
|
+ return new SpannableString(_editBuffer.Text.Substring(end, Math.Min(n, _editBuffer.Text.Length - end)));
|
|
|
+ }
|
|
|
+
|
|
|
+ public ICharSequence? GetTextBeforeCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)
|
|
|
+ {
|
|
|
+ var start = Math.Max(0, _editBuffer.Selection.Start - n);
|
|
|
+ var length = _editBuffer.Selection.Start - start;
|
|
|
+ return _editBuffer.Text == null ? null : new SpannableString(_editBuffer.Text.Substring(start, length));
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool PerformPrivateCommand(string? action, Bundle? data)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ReportFullscreenMode(bool enabled)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool RequestCursorUpdates(int cursorUpdateMode)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool SendKeyEvent(KeyEvent? e)
|
|
|
+ {
|
|
|
+ _inputMethod.View.DispatchKeyEvent(e);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void QueueCommand(EditCommand command)
|
|
|
+ {
|
|
|
+ BeginBatchEdit();
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ _commandQueue.Enqueue(command);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ EndBatchEdit();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|