5package io.flutter.plugin.editing;
7import static io.flutter.Build.API_LEVELS;
9import android.annotation.TargetApi;
11import android.content.ClipboardManager;
16import android.text.DynamicLayout;
24import android.view.inputmethod.BaseInputConnection;
25import android.view.inputmethod.CursorAnchorInfo;
26import android.view.inputmethod.EditorInfo;
27import android.view.inputmethod.ExtractedText;
28import android.view.inputmethod.ExtractedTextRequest;
29import android.view.inputmethod.InputContentInfo;
30import android.view.inputmethod.InputMethodManager;
31import androidx.annotation.NonNull;
32import androidx.annotation.RequiresApi;
33import androidx.core.view.inputmethod.InputConnectionCompat;
35import io.flutter.embedding.engine.FlutterJNI;
36import io.flutter.embedding.engine.systemchannels.TextInputChannel;
37import java.io.ByteArrayOutputStream;
38import java.io.FileNotFoundException;
39import java.io.IOException;
40import java.io.InputStream;
41import java.util.HashMap;
46 private static final String TAG =
"InputConnectionAdaptor";
52 private final View mFlutterView;
53 private final int mClient;
56 private final EditorInfo mEditorInfo;
57 private ExtractedTextRequest mExtractRequest;
58 private boolean mMonitorCursorUpdate =
false;
59 private CursorAnchorInfo.Builder mCursorAnchorInfoBuilder;
60 private ExtractedText mExtractedText =
new ExtractedText();
61 private InputMethodManager mImm;
62 private final Layout mLayout;
65 private int batchEditNestDepth = 0;
67 @SuppressWarnings(
"deprecation")
74 EditorInfo editorInfo,
79 this.textInputChannel = textInputChannel;
82 mEditorInfo = editorInfo;
83 this.keyboardDelegate = keyboardDelegate;
92 Layout.Alignment.ALIGN_NORMAL,
96 mImm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
105 EditorInfo editorInfo) {
106 this(view, client, textInputChannel, keyboardDelegate, editable, editorInfo,
new FlutterJNI());
109 private ExtractedText getExtractedText(ExtractedTextRequest request) {
110 mExtractedText.startOffset = 0;
111 mExtractedText.partialStartOffset = -1;
112 mExtractedText.partialEndOffset = -1;
115 mExtractedText.text =
116 request ==
null || (request.flags & GET_TEXT_WITH_STYLES) == 0
119 return mExtractedText;
122 private CursorAnchorInfo getCursorAnchorInfo() {
123 if (mCursorAnchorInfoBuilder ==
null) {
124 mCursorAnchorInfoBuilder =
new CursorAnchorInfo.Builder();
126 mCursorAnchorInfoBuilder.reset();
129 mCursorAnchorInfoBuilder.setSelectionRange(
133 if (composingStart >= 0 && composingEnd > composingStart) {
134 mCursorAnchorInfoBuilder.setComposingText(
135 composingStart, mEditable.
toString().subSequence(composingStart, composingEnd));
137 mCursorAnchorInfoBuilder.setComposingText(-1,
"");
139 return mCursorAnchorInfoBuilder.build();
150 batchEditNestDepth += 1;
151 return super.beginBatchEdit();
156 boolean result = super.endBatchEdit();
157 batchEditNestDepth -= 1;
164 final boolean result = super.commitText(
text, newCursorPosition);
174 final boolean result = super.deleteSurroundingText(beforeLength, afterLength);
180 boolean result = super.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
194 if (
text.length() == 0) {
195 result = super.commitText(
text, newCursorPosition);
197 result = super.setComposingText(
text, newCursorPosition);
205 final boolean result = super.finishComposingText();
216 final boolean textMonitor = (
flags & GET_EXTRACTED_TEXT_MONITOR) != 0;
217 if (textMonitor == (mExtractRequest ==
null)) {
218 Log.
d(
TAG,
"The input method toggled text monitoring " + (textMonitor ?
"on" :
"off"));
222 mExtractRequest = textMonitor ? request :
null;
223 return getExtractedText(request);
228 if ((cursorUpdateMode & CURSOR_UPDATE_IMMEDIATE) != 0) {
229 mImm.updateCursorAnchorInfo(mFlutterView, getCursorAnchorInfo());
232 final boolean updated = (cursorUpdateMode & CURSOR_UPDATE_MONITOR) != 0;
233 if (updated != mMonitorCursorUpdate) {
234 Log.
d(
TAG,
"The input method toggled cursor monitoring " + (updated ?
"on" :
"off"));
238 mMonitorCursorUpdate = updated;
244 boolean result = super.clearMetaKeyStates(states);
250 super.closeConnection();
252 for (; batchEditNestDepth > 0; batchEditNestDepth--) {
267 private static int clampIndexToEditable(
int index, Editable editable) {
268 int clamped = Math.max(0, Math.min(editable.length(), index));
269 if (clamped != index) {
272 "Text selection index was clamped ("
276 +
") to remain in bounds. This may not be your fault, as some keyboards may select outside of bounds.");
290 if (
event.getAction() == KeyEvent.ACTION_DOWN) {
291 if (
event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
292 return handleHorizontalMovement(
true,
event.isShiftPressed());
293 }
else if (
event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
294 return handleHorizontalMovement(
false,
event.isShiftPressed());
295 }
else if (
event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
296 return handleVerticalMovement(
true,
event.isShiftPressed());
297 }
else if (
event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
298 return handleVerticalMovement(
false,
event.isShiftPressed());
301 }
else if ((
event.getKeyCode() == KeyEvent.KEYCODE_ENTER
302 ||
event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER)
303 && (InputType.TYPE_TEXT_FLAG_MULTI_LINE & mEditorInfo.inputType) == 0) {
308 final int selStart = Selection.getSelectionStart(mEditable);
309 final int selEnd = Selection.getSelectionEnd(mEditable);
310 final int character =
event.getUnicodeChar();
311 if (selStart < 0 || selEnd < 0 ||
character == 0) {
315 final int selMin = Math.min(selStart, selEnd);
316 final int selMax = Math.max(selStart, selEnd);
318 if (selMin != selMax) mEditable.delete(selMin, selMax);
319 mEditable.insert(selMin, String.valueOf((
char)
character));
328 private boolean handleHorizontalMovement(
boolean isLeft,
boolean isShiftPressed) {
329 final int selStart = Selection.getSelectionStart(mEditable);
330 final int selEnd = Selection.getSelectionEnd(mEditable);
332 if (selStart < 0 || selEnd < 0) {
336 final int newSelectionEnd =
339 : Math.min(flutterTextUtils.
getOffsetAfter(mEditable, selEnd), mEditable.length());
341 final boolean shouldCollapse = selStart == selEnd && !isShiftPressed;
343 if (shouldCollapse) {
351 private boolean handleVerticalMovement(
boolean isUp,
boolean isShiftPressed) {
352 final int selStart = Selection.getSelectionStart(mEditable);
353 final int selEnd = Selection.getSelectionEnd(mEditable);
355 if (selStart < 0 || selEnd < 0) {
359 final boolean shouldCollapse = selStart == selEnd && !isShiftPressed;
362 if (shouldCollapse) {
364 Selection.moveUp(mEditable, mLayout);
366 Selection.moveDown(mEditable, mLayout);
368 final int newSelection = Selection.getSelectionStart(mEditable);
372 Selection.extendUp(mEditable, mLayout);
374 Selection.extendDown(mEditable, mLayout);
376 setSelection(Selection.getSelectionStart(mEditable), Selection.getSelectionEnd(mEditable));
385 final boolean result = doPerformContextMenuAction(
id);
390 private boolean doPerformContextMenuAction(
int id) {
391 if (
id ==
android.R.id.selectAll) {
394 }
else if (
id ==
android.R.id.cut) {
395 int selStart = Selection.getSelectionStart(mEditable);
396 int selEnd = Selection.getSelectionEnd(mEditable);
397 if (selStart != selEnd) {
398 int selMin = Math.min(selStart, selEnd);
399 int selMax = Math.max(selStart, selEnd);
400 CharSequence textToCut = mEditable.subSequence(selMin, selMax);
401 ClipboardManager clipboard =
403 mFlutterView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
404 ClipData
clip = ClipData.newPlainText(
"text label?", textToCut);
405 clipboard.setPrimaryClip(
clip);
406 mEditable.delete(selMin, selMax);
410 }
else if (
id ==
android.R.id.copy) {
411 int selStart = Selection.getSelectionStart(mEditable);
412 int selEnd = Selection.getSelectionEnd(mEditable);
413 if (selStart != selEnd) {
414 CharSequence textToCopy =
415 mEditable.subSequence(Math.min(selStart, selEnd), Math.max(selStart, selEnd));
416 ClipboardManager clipboard =
418 mFlutterView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
419 clipboard.setPrimaryClip(ClipData.newPlainText(
"text label?", textToCopy));
422 }
else if (
id ==
android.R.id.paste) {
423 ClipboardManager clipboard =
424 (ClipboardManager) mFlutterView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
425 ClipData
clip = clipboard.getPrimaryClip();
427 CharSequence textToPaste =
clip.getItemAt(0).coerceToText(mFlutterView.getContext());
428 int selStart = Math.max(0, Selection.getSelectionStart(mEditable));
429 int selEnd = Math.max(0, Selection.getSelectionEnd(mEditable));
430 int selMin = Math.min(selStart, selEnd);
431 int selMax = Math.max(selStart, selEnd);
432 if (selMin != selMax) mEditable.delete(selMin, selMax);
433 mEditable.insert(selMin, textToPaste);
434 int newSelStart = selMin + textToPaste.length();
450 switch (actionCode) {
451 case EditorInfo.IME_ACTION_NONE:
452 textInputChannel.
newline(mClient);
454 case EditorInfo.IME_ACTION_UNSPECIFIED:
457 case EditorInfo.IME_ACTION_GO:
458 textInputChannel.
go(mClient);
460 case EditorInfo.IME_ACTION_SEARCH:
461 textInputChannel.
search(mClient);
463 case EditorInfo.IME_ACTION_SEND:
464 textInputChannel.
send(mClient);
466 case EditorInfo.IME_ACTION_NEXT:
467 textInputChannel.
next(mClient);
469 case EditorInfo.IME_ACTION_PREVIOUS:
473 case EditorInfo.IME_ACTION_DONE:
474 textInputChannel.
done(mClient);
486 && (
flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
488 inputContentInfo.requestPermission();
489 }
catch (Exception
e) {
496 if (inputContentInfo.getDescription().getMimeTypeCount() > 0) {
497 inputContentInfo.requestPermission();
499 final Uri uri = inputContentInfo.getContentUri();
500 final String mimeType = inputContentInfo.getDescription().getMimeType(0);
501 Context context = mFlutterView.getContext();
507 is = context.getContentResolver().openInputStream(uri);
508 }
catch (FileNotFoundException ex) {
509 inputContentInfo.releasePermission();
514 final byte[]
data = this.readStreamFully(is, 64 * 1024);
516 final Map<String, Object> obj =
new HashMap<>();
517 obj.put(
"mimeType", mimeType);
518 obj.put(
"data",
data);
519 obj.put(
"uri", uri.toString());
523 inputContentInfo.releasePermission();
528 inputContentInfo.releasePermission();
533 private byte[] readStreamFully(InputStream is,
int blocksize) {
534 ByteArrayOutputStream baos =
new ByteArrayOutputStream();
536 byte[]
buffer =
new byte[blocksize];
541 }
catch (IOException ex) {
543 if (
len == -1)
break;
546 return baos.toByteArray();
552 boolean textChanged,
boolean selectionChanged,
boolean composingRegionChanged) {
561 mImm.updateSelection(
568 if (mExtractRequest !=
null) {
569 mImm.updateExtractedText(
570 mFlutterView, mExtractRequest.token, getExtractedText(mExtractRequest));
572 if (mMonitorCursorUpdate) {
573 final CursorAnchorInfo
info = getCursorAnchorInfo();
574 mImm.updateCursorAnchorInfo(mFlutterView,
info);
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
static void d(@NonNull String tag, @NonNull String message)
void commitContent(int inputClientId, Map< String, Object > content)
void next(int inputClientId)
void unspecifiedAction(int inputClientId)
void newline(int inputClientId)
void go(int inputClientId)
void send(int inputClientId)
void search(int inputClientId)
void done(int inputClientId)
void performPrivateCommand(int inputClientId, @NonNull String action, @NonNull Bundle data)
void previous(int inputClientId)
int getOffsetAfter(CharSequence text, int offset)
int getOffsetBefore(CharSequence text, int offset)
final int getSelectionStart()
void removeEditingStateListener(EditingStateWatcher listener)
final int getComposingEnd()
final int getSelectionEnd()
void addEditingStateListener(EditingStateWatcher listener)
final int getComposingStart()
FlutterSemanticsFlag flags
def Build(configs, env, options)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
std::shared_ptr< const fml::Mapping > data