未验证 提交 9cdb5a9b 编写于 作者: A Ali Mahdiyar 提交者: GitHub

Custom unicode handling for Android backspace via JNI to ICU (#17960)

上级 d6aa099d
......@@ -752,6 +752,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/PluginReg
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StandardMessageCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StringCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/FlutterTextUtils.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/AccessibilityEventsDelegate.java
......
......@@ -201,6 +201,7 @@ android_java_sources = [
"io/flutter/plugin/common/StandardMessageCodec.java",
"io/flutter/plugin/common/StandardMethodCodec.java",
"io/flutter/plugin/common/StringCodec.java",
"io/flutter/plugin/editing/FlutterTextUtils.java",
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
"io/flutter/plugin/editing/TextInputPlugin.java",
"io/flutter/plugin/platform/AccessibilityEventsDelegate.java",
......
......@@ -146,6 +146,20 @@ public class FlutterJNI {
@NonNull
public static native FlutterCallbackInformation nativeLookupCallbackInformation(long handle);
// ----- Start FlutterTextUtils Methods ----
public native boolean nativeFlutterTextUtilsIsEmoji(int codePoint);
public native boolean nativeFlutterTextUtilsIsEmojiModifier(int codePoint);
public native boolean nativeFlutterTextUtilsIsEmojiModifierBase(int codePoint);
public native boolean nativeFlutterTextUtilsIsVariationSelector(int codePoint);
public native boolean nativeFlutterTextUtilsIsRegionalIndicator(int codePoint);
// ----- End Engine FlutterTextUtils Methods ----
@Nullable private Long nativePlatformViewId;
@Nullable private AccessibilityDelegate accessibilityDelegate;
@Nullable private PlatformMessageHandler platformMessageHandler;
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugin.editing;
import io.flutter.embedding.engine.FlutterJNI;
class FlutterTextUtils {
public static final int LINE_FEED = 0x0A;
public static final int CARRIAGE_RETURN = 0x0D;
public static final int COMBINING_ENCLOSING_KEYCAP = 0x20E3;
public static final int CANCEL_TAG = 0xE007F;
public static final int ZERO_WIDTH_JOINER = 0x200D;
private final FlutterJNI flutterJNI;
public FlutterTextUtils(FlutterJNI flutterJNI) {
this.flutterJNI = flutterJNI;
}
public boolean isEmoji(int codePoint) {
return flutterJNI.nativeFlutterTextUtilsIsEmoji(codePoint);
}
public boolean isEmojiModifier(int codePoint) {
return flutterJNI.nativeFlutterTextUtilsIsEmojiModifier(codePoint);
}
public boolean isEmojiModifierBase(int codePoint) {
return flutterJNI.nativeFlutterTextUtilsIsEmojiModifierBase(codePoint);
}
public boolean isVariationSelector(int codePoint) {
return flutterJNI.nativeFlutterTextUtilsIsVariationSelector(codePoint);
}
public boolean isRegionalIndicatorSymbol(int codePoint) {
return flutterJNI.nativeFlutterTextUtilsIsRegionalIndicator(codePoint);
}
public boolean isTagSpecChar(int codePoint) {
return 0xE0020 <= codePoint && codePoint <= 0xE007E;
}
public boolean isKeycapBase(int codePoint) {
return ('0' <= codePoint && codePoint <= '9') || codePoint == '#' || codePoint == '*';
}
/**
* Start offset for backspace key or moving left from the current offset. Same methods are also
* included in Android APIs but they don't work as expected in API Levels lower than 24. Reference
* for the logic in this code is the Android source code.
*
* @see <a target="_new"
* href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android10-s3-release/core/java/android/text/method/BaseKeyListener.java#111">https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android10-s3-release/core/java/android/text/method/BaseKeyListener.java#111</a>
*/
public int getOffsetBefore(CharSequence text, int offset) {
if (offset <= 1) {
return 0;
}
int codePoint = Character.codePointBefore(text, offset);
int deleteCharCount = Character.charCount(codePoint);
int lastOffset = offset - deleteCharCount;
if (lastOffset == 0) {
return 0;
}
// Line Feed
if (codePoint == LINE_FEED) {
codePoint = Character.codePointBefore(text, lastOffset);
if (codePoint == CARRIAGE_RETURN) {
++deleteCharCount;
}
return offset - deleteCharCount;
}
// Flags
if (isRegionalIndicatorSymbol(codePoint)) {
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
int regionalIndicatorSymbolCount = 1;
while (lastOffset > 0 && isRegionalIndicatorSymbol(codePoint)) {
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
regionalIndicatorSymbolCount++;
}
if (regionalIndicatorSymbolCount % 2 == 0) {
deleteCharCount += 2;
}
return offset - deleteCharCount;
}
// Keycaps
if (codePoint == COMBINING_ENCLOSING_KEYCAP) {
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
if (lastOffset > 0 && isVariationSelector(codePoint)) {
int tmpCodePoint = Character.codePointBefore(text, lastOffset);
if (isKeycapBase(tmpCodePoint)) {
deleteCharCount += Character.charCount(codePoint) + Character.charCount(tmpCodePoint);
}
} else if (isKeycapBase(codePoint)) {
deleteCharCount += Character.charCount(codePoint);
}
return offset - deleteCharCount;
}
/**
* Following if statements for Emoji tag sequence and Variation selector are skipping these
* modifiers for going through the last statement that is for handling emojis. They return the
* offset if they don't find proper base characters
*/
// Emoji Tag Sequence
if (codePoint == CANCEL_TAG) { // tag_end
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
while (lastOffset > 0 && isTagSpecChar(codePoint)) { // tag_spec
deleteCharCount += Character.charCount(codePoint);
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
}
if (!isEmoji(codePoint)) { // tag_base not found. Just delete the end.
return offset - 2;
}
deleteCharCount += Character.charCount(codePoint);
}
if (isVariationSelector(codePoint)) {
codePoint = Character.codePointBefore(text, lastOffset);
if (!isEmoji(codePoint)) {
return offset - deleteCharCount;
}
deleteCharCount += Character.charCount(codePoint);
lastOffset -= deleteCharCount;
}
if (isEmoji(codePoint)) {
boolean isZwj = false;
int lastSeenVariantSelectorCharCount = 0;
do {
if (isZwj) {
deleteCharCount += Character.charCount(codePoint) + lastSeenVariantSelectorCharCount + 1;
isZwj = false;
}
lastSeenVariantSelectorCharCount = 0;
if (isEmojiModifier(codePoint)) {
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
if (lastOffset > 0 && isVariationSelector(codePoint)) {
codePoint = Character.codePointBefore(text, lastOffset);
if (!isEmoji(codePoint)) {
return offset - deleteCharCount;
}
lastSeenVariantSelectorCharCount = Character.charCount(codePoint);
lastOffset -= Character.charCount(codePoint);
}
if (isEmojiModifierBase(codePoint)) {
deleteCharCount += lastSeenVariantSelectorCharCount + Character.charCount(codePoint);
}
break;
}
if (lastOffset > 0) {
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
if (codePoint == ZERO_WIDTH_JOINER) {
isZwj = true;
codePoint = Character.codePointBefore(text, lastOffset);
lastOffset -= Character.charCount(codePoint);
if (lastOffset > 0 && isVariationSelector(codePoint)) {
codePoint = Character.codePointBefore(text, lastOffset);
lastSeenVariantSelectorCharCount = Character.charCount(codePoint);
lastOffset -= Character.charCount(codePoint);
}
}
}
if (lastOffset == 0) {
break;
}
} while (isZwj && isEmoji(codePoint));
}
return offset - deleteCharCount;
}
}
......@@ -16,7 +16,6 @@ import android.text.InputType;
import android.text.Layout;
import android.text.Selection;
import android.text.TextPaint;
import android.text.method.TextKeyListener;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
......@@ -27,6 +26,7 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
class InputConnectionAdaptor extends BaseInputConnection {
......@@ -38,6 +38,7 @@ class InputConnectionAdaptor extends BaseInputConnection {
private int mBatchCount;
private InputMethodManager mImm;
private final Layout mLayout;
private FlutterTextUtils flutterTextUtils;
// Used to determine if Samsung-specific hacks should be applied.
private final boolean isSamsung;
......@@ -96,7 +97,8 @@ class InputConnectionAdaptor extends BaseInputConnection {
int client,
TextInputChannel textInputChannel,
Editable editable,
EditorInfo editorInfo) {
EditorInfo editorInfo,
FlutterJNI flutterJNI) {
super(view, true);
mFlutterView = view;
mClient = client;
......@@ -104,6 +106,7 @@ class InputConnectionAdaptor extends BaseInputConnection {
mEditable = editable;
mEditorInfo = editorInfo;
mBatchCount = 0;
this.flutterTextUtils = new FlutterTextUtils(flutterJNI);
// We create a dummy Layout with max width so that the selection
// shifting acts as if all text were in one line.
mLayout =
......@@ -120,6 +123,15 @@ class InputConnectionAdaptor extends BaseInputConnection {
isSamsung = isSamsung();
}
public InputConnectionAdaptor(
View view,
int client,
TextInputChannel textInputChannel,
Editable editable,
EditorInfo editorInfo) {
this(view, client, textInputChannel, editable, editorInfo, new FlutterJNI());
}
// Send the current state of the editable to Flutter.
private void updateEditingState() {
// If the IME is in the middle of a batch edit, then wait until it completes.
......@@ -315,19 +327,18 @@ class InputConnectionAdaptor extends BaseInputConnection {
if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
int selStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable);
int selEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable);
if (selStart == selEnd && selStart > 0) {
// Extend selection to left of the last character
selStart = flutterTextUtils.getOffsetBefore(mEditable, selStart);
}
if (selEnd > selStart) {
// Delete the selection.
Selection.setSelection(mEditable, selStart);
mEditable.delete(selStart, selEnd);
updateEditingState();
return true;
} else if (selStart > 0) {
if (TextKeyListener.getInstance().onKeyDown(null, mEditable, event.getKeyCode(), event)) {
updateEditingState();
return true;
}
return false;
}
return false;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
int selStart = Selection.getSelectionStart(mEditable);
int selEnd = Selection.getSelectionEnd(mEditable);
......
......@@ -7,6 +7,7 @@
#include <android/native_window_jni.h>
#include <utility>
#include "unicode/uchar.h"
#include "flutter/assets/directory_asset_bundle.h"
#include "flutter/common/settings.h"
......@@ -484,6 +485,35 @@ static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env,
);
}
static jboolean FlutterTextUtilsIsEmoji(JNIEnv* env,
jobject obj,
jint codePoint) {
return u_hasBinaryProperty(codePoint, UProperty::UCHAR_EMOJI);
}
static jboolean FlutterTextUtilsIsEmojiModifier(JNIEnv* env,
jobject obj,
jint codePoint) {
return u_hasBinaryProperty(codePoint, UProperty::UCHAR_EMOJI_MODIFIER);
}
static jboolean FlutterTextUtilsIsEmojiModifierBase(JNIEnv* env,
jobject obj,
jint codePoint) {
return u_hasBinaryProperty(codePoint, UProperty::UCHAR_EMOJI_MODIFIER_BASE);
}
static jboolean FlutterTextUtilsIsVariationSelector(JNIEnv* env,
jobject obj,
jint codePoint) {
return u_hasBinaryProperty(codePoint, UProperty::UCHAR_VARIATION_SELECTOR);
}
static jboolean FlutterTextUtilsIsRegionalIndicator(JNIEnv* env,
jobject obj,
jint codePoint) {
return u_hasBinaryProperty(codePoint, UProperty::UCHAR_REGIONAL_INDICATOR);
}
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterJNI
......@@ -599,6 +629,36 @@ bool RegisterApi(JNIEnv* env) {
.signature = "(J)Lio/flutter/view/FlutterCallbackInformation;",
.fnPtr = reinterpret_cast<void*>(&LookupCallbackInformation),
},
// Start of methods for FlutterTextUtils
{
.name = "nativeFlutterTextUtilsIsEmoji",
.signature = "(I)Z",
.fnPtr = reinterpret_cast<void*>(&FlutterTextUtilsIsEmoji),
},
{
.name = "nativeFlutterTextUtilsIsEmojiModifier",
.signature = "(I)Z",
.fnPtr = reinterpret_cast<void*>(&FlutterTextUtilsIsEmojiModifier),
},
{
.name = "nativeFlutterTextUtilsIsEmojiModifierBase",
.signature = "(I)Z",
.fnPtr =
reinterpret_cast<void*>(&FlutterTextUtilsIsEmojiModifierBase),
},
{
.name = "nativeFlutterTextUtilsIsVariationSelector",
.signature = "(I)Z",
.fnPtr =
reinterpret_cast<void*>(&FlutterTextUtilsIsVariationSelector),
},
{
.name = "nativeFlutterTextUtilsIsRegionalIndicator",
.signature = "(I)Z",
.fnPtr =
reinterpret_cast<void*>(&FlutterTextUtilsIsRegionalIndicator),
},
};
if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods,
......
......@@ -3,16 +3,19 @@ package io.flutter.plugin.editing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipboardManager;
import android.content.res.AssetManager;
import android.text.Editable;
import android.text.Emoji;
import android.text.InputType;
import android.text.Selection;
import android.text.SpannableStringBuilder;
......@@ -316,7 +319,7 @@ public class InputConnectionAdaptorTest {
@Test
public void testSendKeyEvent_delKeyDeletesBackward() {
int selStart = 29;
Editable editable = sampleRtlEditable(selStart, selStart);
Editable editable = sampleEditable(selStart, selStart, SAMPLE_RTL_TEXT);
InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
......@@ -334,9 +337,171 @@ public class InputConnectionAdaptorTest {
assertEquals(Selection.getSelectionStart(editable), 10);
}
@Test
public void testSendKeyEvent_delKeyDeletesBackwardComplexEmojis() {
int selStart = 75;
Editable editable = sampleEditable(selStart, selStart, SAMPLE_EMOJI_TEXT);
InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
boolean didConsume;
// Normal Character
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 74);
// Non-Spacing Mark
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 73);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 72);
// Keycap
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 69);
// Keycap with invalid base
adaptor.setSelection(68, 68);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 66);
adaptor.setSelection(67, 67);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 66);
// Zero Width Joiner
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 55);
// Zero Width Joiner with invalid base
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 53);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 52);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 51);
// ----- Start Emoji Tag Sequence with invalid base testing ----
// Delete base tag
adaptor.setSelection(39, 39);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 37);
// Delete the sequence
adaptor.setSelection(49, 49);
for (int i = 0; i < 6; i++) {
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
}
assertEquals(Selection.getSelectionStart(editable), 37);
// ----- End Emoji Tag Sequence with invalid base testing ----
// Emoji Tag Sequence
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 23);
// Variation Selector with invalid base
adaptor.setSelection(22, 22);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 21);
adaptor.setSelection(22, 22);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 21);
// Variation Selector
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 19);
// Emoji Modifier
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 16);
// Emoji Modifier with invalid base
adaptor.setSelection(14, 14);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 13);
adaptor.setSelection(14, 14);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 13);
// Line Feed
adaptor.setSelection(12, 12);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 11);
// Carriage Return
adaptor.setSelection(12, 12);
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 11);
// Carriage Return and Line Feed
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 9);
// Regional Indicator Symbol odd
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 7);
// Regional Indicator Symbol even
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 3);
// Simple Emoji
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 1);
// First CodePoint
didConsume = adaptor.sendKeyEvent(downKeyDown);
assertTrue(didConsume);
assertEquals(Selection.getSelectionStart(editable), 0);
}
private static final String SAMPLE_TEXT =
"Lorem ipsum dolor sit amet," + "\nconsectetur adipiscing elit.";
private static final String SAMPLE_EMOJI_TEXT =
"a" // First CodePoint
+ "😂" // Simple Emoji
+ "🇮🇷" // Regional Indicator Symbol even
+ "🇷" // Regional Indicator Symbol odd
+ "\r\n" // Carriage Return and Line Feed
+ "\r\n"
+ "✋🏿" // Emoji Modifier
+ "✋🏿"
+ "⚠️" // Variant Selector
+ "⚠️"
+ "🏴󠁧󠁢󠁥󠁮󠁧󠁿" // Emoji Tag Sequence
+ "🏴󠁧󠁢󠁥󠁮󠁧󠁿"
+ "a‍👨" // Zero Width Joiner
+ "👨‍👩‍👧‍👦"
+ "5️⃣" // Keycap
+ "5️⃣"
+ "عَ" // Non-Spacing Mark
+ "a"; // Normal Character
private static final String SAMPLE_RTL_TEXT = "متن ساختگی" + "\nبرای تستfor test😊";
private static Editable sampleEditable(int selStart, int selEnd) {
......@@ -345,8 +510,8 @@ public class InputConnectionAdaptorTest {
return sample;
}
private static Editable sampleRtlEditable(int selStart, int selEnd) {
SpannableStringBuilder sample = new SpannableStringBuilder(SAMPLE_RTL_TEXT);
private static Editable sampleEditable(int selStart, int selEnd, String text) {
SpannableStringBuilder sample = new SpannableStringBuilder(text);
Selection.setSelection(sample, selStart, selEnd);
return sample;
}
......@@ -355,7 +520,24 @@ public class InputConnectionAdaptorTest {
View testView = new View(RuntimeEnvironment.application);
int client = 0;
TextInputChannel textInputChannel = mock(TextInputChannel.class);
return new InputConnectionAdaptor(testView, client, textInputChannel, editable, null);
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
when(mockFlutterJNI.nativeFlutterTextUtilsIsEmoji(anyInt()))
.thenAnswer((invocation) -> Emoji.isEmoji((int) invocation.getArguments()[0]));
when(mockFlutterJNI.nativeFlutterTextUtilsIsEmojiModifier(anyInt()))
.thenAnswer((invocation) -> Emoji.isEmojiModifier((int) invocation.getArguments()[0]));
when(mockFlutterJNI.nativeFlutterTextUtilsIsEmojiModifierBase(anyInt()))
.thenAnswer((invocation) -> Emoji.isEmojiModifierBase((int) invocation.getArguments()[0]));
when(mockFlutterJNI.nativeFlutterTextUtilsIsVariationSelector(anyInt()))
.thenAnswer(
(invocation) -> {
int codePoint = (int) invocation.getArguments()[0];
return 0xFE0E <= codePoint && codePoint <= 0xFE0F;
});
when(mockFlutterJNI.nativeFlutterTextUtilsIsRegionalIndicator(anyInt()))
.thenAnswer(
(invocation) -> Emoji.isRegionalIndicatorSymbol((int) invocation.getArguments()[0]));
return new InputConnectionAdaptor(
testView, client, textInputChannel, editable, null, mockFlutterJNI);
}
private class TestTextInputChannel extends TextInputChannel {
......
......@@ -65,6 +65,7 @@
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/common/MethodCall.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/common/EventChannel.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/common/MethodChannel.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/editing/FlutterTextUtils.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册