mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This commit is contained in:
parent
74ac6d25ec
commit
4003fbc3ed
@ -407,6 +407,7 @@ action("robolectric_tests") {
|
||||
"test/io/flutter/embedding/android/FlutterActivityTest.java",
|
||||
"test/io/flutter/embedding/android/FlutterFragmentTest.java",
|
||||
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
|
||||
"test/io/flutter/embedding/engine/systemchannels/TextInputChannelTest.java",
|
||||
"test/io/flutter/util/PreconditionsTest.java",
|
||||
]
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.flutter.Log;
|
||||
@ -222,6 +223,17 @@ public class TextInputChannel {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs Flutter to clear the current input client, which ends the text
|
||||
* input interaction with the given input control.
|
||||
*/
|
||||
public void onConnectionClosed(int inputClientId) {
|
||||
channel.invokeMethod(
|
||||
"TextInputClient.onConnectionClosed",
|
||||
Collections.singletonList(inputClientId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link TextInputMethodHandler} which receives all events and requests
|
||||
* that are parsed from the underlying platform channel.
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
package io.flutter.plugin.editing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.DynamicLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.Layout;
|
||||
import android.text.Layout.Directions;
|
||||
import android.text.Selection;
|
||||
import android.text.TextPaint;
|
||||
import android.view.KeyEvent;
|
||||
@ -19,30 +19,37 @@ import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.plugin.common.ErrorLogResult;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
class InputConnectionAdaptor extends BaseInputConnection {
|
||||
@NonNull
|
||||
private final View mFlutterView;
|
||||
private final int mClient;
|
||||
@NonNull
|
||||
private final TextInputChannel textInputChannel;
|
||||
@NonNull
|
||||
private final Editable mEditable;
|
||||
@NonNull
|
||||
private final Runnable onConnectionClosed;
|
||||
private int mBatchCount;
|
||||
@NonNull
|
||||
private InputMethodManager mImm;
|
||||
@NonNull
|
||||
private final Layout mLayout;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public InputConnectionAdaptor(
|
||||
View view,
|
||||
@NonNull View view,
|
||||
int client,
|
||||
TextInputChannel textInputChannel,
|
||||
Editable editable
|
||||
@NonNull TextInputChannel textInputChannel,
|
||||
@NonNull Editable editable,
|
||||
@NonNull Runnable onConnectionClosed
|
||||
) {
|
||||
super(view, true);
|
||||
mFlutterView = view;
|
||||
mClient = client;
|
||||
this.textInputChannel = textInputChannel;
|
||||
mEditable = editable;
|
||||
this.onConnectionClosed = onConnectionClosed;
|
||||
mBatchCount = 0;
|
||||
// We create a dummy Layout with max width so that the selection
|
||||
// shifting acts as if all text were in one line.
|
||||
@ -50,6 +57,13 @@ class InputConnectionAdaptor extends BaseInputConnection {
|
||||
mImm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnection() {
|
||||
super.closeConnection();
|
||||
textInputChannel.onConnectionClosed(mClient);
|
||||
onConnectionClosed.run();
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@ -46,6 +46,13 @@ public class TextInputPlugin {
|
||||
// target is a platform view. See the comments on lockPlatformViewInputConnection for more details.
|
||||
private boolean isInputConnectionLocked;
|
||||
|
||||
private final Runnable onInputConnectionClosed = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearTextInputClient();
|
||||
}
|
||||
};
|
||||
|
||||
public TextInputPlugin(View view, @NonNull DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController) {
|
||||
mView = view;
|
||||
mImm = (InputMethodManager) view.getContext().getSystemService(
|
||||
@ -219,7 +226,8 @@ public class TextInputPlugin {
|
||||
view,
|
||||
inputTarget.id,
|
||||
textInputChannel,
|
||||
mEditable
|
||||
mEditable,
|
||||
onInputConnectionClosed
|
||||
);
|
||||
outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
|
||||
outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
|
||||
|
||||
@ -12,6 +12,7 @@ import io.flutter.embedding.android.FlutterActivityAndFragmentDelegateTest;
|
||||
import io.flutter.embedding.android.FlutterActivityTest;
|
||||
import io.flutter.embedding.android.FlutterFragmentTest;
|
||||
import io.flutter.embedding.engine.FlutterEngineCacheTest;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannelTest;
|
||||
import io.flutter.util.PreconditionsTest;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@ -21,7 +22,8 @@ import io.flutter.util.PreconditionsTest;
|
||||
FlutterActivityTest.class,
|
||||
FlutterFragmentTest.class,
|
||||
// FlutterActivityAndFragmentDelegateTest.class, TODO(mklim): Fix and re-enable this
|
||||
FlutterEngineCacheTest.class
|
||||
FlutterEngineCacheTest.class,
|
||||
TextInputChannelTest.class
|
||||
})
|
||||
/** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */
|
||||
public class FlutterTestSuite {}
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
package io.flutter.embedding.engine.systemchannels;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.plugin.common.JSONMethodCodec;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@Config(manifest=Config.NONE)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class TextInputChannelTest {
|
||||
@Test
|
||||
public void itNotifiesFrameworkWhenPlatformClosesInputConnection() {
|
||||
// Setup test.
|
||||
final int INPUT_CLIENT_ID = 9; // Arbitrary integer.
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
|
||||
// Execute behavior under test.
|
||||
textInputChannel.onConnectionClosed(INPUT_CLIENT_ID);
|
||||
|
||||
// Verify results.
|
||||
verify(dartExecutor, times(1)).send(
|
||||
eq("flutter/textinput"),
|
||||
ByteBufferMatcher.eqByteBuffer(JSONMethodCodec.INSTANCE.encodeMethodCall(
|
||||
new MethodCall(
|
||||
"TextInputClient.onConnectionClosed",
|
||||
Collections.singletonList(INPUT_CLIENT_ID)
|
||||
)
|
||||
)),
|
||||
eq(null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mockito matcher that compares two {@link ByteBuffer}s by resetting both buffers and then
|
||||
* utilizing their standard {@code equals()} method.
|
||||
* <p>
|
||||
* This matcher will change the state of the expected and actual buffers. The exact change in
|
||||
* state depends on where the comparison fails or succeeds.
|
||||
*/
|
||||
static class ByteBufferMatcher extends ArgumentMatcher<ByteBuffer> {
|
||||
|
||||
static ByteBuffer eqByteBuffer(ByteBuffer expected) {
|
||||
return argThat(new ByteBufferMatcher(expected));
|
||||
}
|
||||
|
||||
private ByteBuffer expected;
|
||||
|
||||
ByteBufferMatcher(ByteBuffer expected) {
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object argument) {
|
||||
if (!(argument instanceof ByteBuffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset the buffers for content comparison.
|
||||
((ByteBuffer) argument).position(0);
|
||||
expected.position(0);
|
||||
|
||||
return expected.equals(argument);
|
||||
}
|
||||
|
||||
// Implemented so that during a failure the expected value is
|
||||
// shown in logs, rather than the name of this class.
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText(expected.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user