未验证 提交 4003fbc3 编写于 作者: M Matt Carroll 提交者: GitHub

Notify framework to clear input connection when app is backgrounded (#35054) (#9498)

上级 74ac6d25
......@@ -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 {}
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());
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册