提交 8449f778 编写于 作者: A Adam Barth 提交者: GitHub

Implement TextInputPlugin on Android (#3144)

This plugin will eventually replace keyboard.mojom.
上级 d185552c
......@@ -66,6 +66,8 @@ android_library("java") {
java_files = [
"io/flutter/plugin/common/ActivityLifecycleListener.java",
"io/flutter/plugin/common/JSONMessageListener.java",
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
"io/flutter/plugin/editing/TextInputPlugin.java",
"io/flutter/plugin/platform/PlatformPlugin.java",
"io/flutter/view/AccessibilityBridge.java",
"io/flutter/view/FlutterMain.java",
......
// Copyright 2016 The Chromium 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 android.text.Editable;
import android.text.Selection;
import android.util.Log;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.KeyEvent;
import android.view.View;
import io.flutter.view.FlutterView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
class InputConnectionAdaptor extends BaseInputConnection {
static final String TAG = "FlutterView";
static final String MESSAGE_NAME = "flutter/textinputclient";
private FlutterView mView;
private int mClient;
private JSONObject mOutgoingState;
public InputConnectionAdaptor(FlutterView view, int client) {
super(view, true);
mView = view;
mClient = client;
mOutgoingState = new JSONObject();
}
private void updateEditingState() {
try {
final Editable content = getEditable();
mOutgoingState.put("text", content.toString());
mOutgoingState.put("selectionBase", Selection.getSelectionStart(content));
mOutgoingState.put("selectionExtent", Selection.getSelectionEnd(content));
mOutgoingState.put("composingBase", BaseInputConnection.getComposingSpanStart(content));
mOutgoingState.put("composingExtent", BaseInputConnection.getComposingSpanEnd(content));
final JSONArray args = new JSONArray();
args.put(0, mClient);
args.put(1, mOutgoingState);
final JSONObject message = new JSONObject();
message.put("method", "TextInputClient.updateEditingState");
message.put("args", args);
mView.sendPlatformMessage(MESSAGE_NAME, message.toString(), null);
} catch (JSONException e) {
Log.e(TAG, "Unexpected error serializing editing state", e);
}
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
final boolean result = super.commitText(text, newCursorPosition);
updateEditingState();
return result;
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
final boolean result = super.deleteSurroundingText(beforeLength, afterLength);
updateEditingState();
return result;
}
@Override
public boolean setComposingRegion(int start, int end) {
final boolean result = super.setComposingRegion(start, end);
updateEditingState();
return result;
}
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
final boolean result = super.setComposingText(text, newCursorPosition);
updateEditingState();
return result;
}
@Override
public boolean setSelection(int start, int end) {
final boolean result = super.setSelection(start, end);
updateEditingState();
return result;
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
final boolean result = super.sendKeyEvent(event);
if (event.getAction() == KeyEvent.ACTION_UP) {
// Weird special case. This method is (sometimes) called for the backspace key in 2
// situations:
// 1. There is no selection. In that case, we want to delete the previous character.
// 2. There is a selection. In that case, we want to delete the selection.
// event.getNumber() is 0, and commitText("", 1) will do what we want.
if (event.getKeyCode() == KeyEvent.KEYCODE_DEL &&
mOutgoingState.optInt("selectionBase", -1) == mOutgoingState.optInt("selectionExtent", -1)) {
deleteSurroundingText(1, 0);
} else {
String text = event.getNumber() == 0 ? "" : String.valueOf(event.getNumber());
commitText(text, 1);
}
}
return result;
}
@Override
public boolean performEditorAction(int actionCode) {
try {
// TODO(abarth): Support more actions.
final JSONArray args = new JSONArray();
args.put(0, mClient);
args.put(1, "TextInputAction.done");
final JSONObject message = new JSONObject();
message.put("method", "TextInputClient.performAction");
message.put("args", args);
mView.sendPlatformMessage(MESSAGE_NAME, message.toString(), null);
return true;
} catch (JSONException e) {
Log.e(TAG, "Unexpected error serializing editor action", e);
return false;
}
}
}
// Copyright 2016 The Chromium 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 android.app.Activity;
import android.content.Context;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.View;
import io.flutter.plugin.common.JSONMessageListener;
import io.flutter.view.FlutterView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Android implementation of the text input plugin.
*/
public class TextInputPlugin extends JSONMessageListener {
private static final String TAG = "FlutterView";
private final Activity mActivity;
private int mClient = 0;
private JSONObject mConfiguration;
private JSONObject mIncomingState;
public TextInputPlugin(Activity activity) {
mActivity = activity;
}
@Override
public JSONObject onJSONMessage(FlutterView view, JSONObject message) throws JSONException {
String method = message.getString("method");
JSONArray args = message.getJSONArray("args");
if (method.equals("TextInput.show")) {
showTextInput(view);
} else if (method.equals("TextInput.hide")) {
hideTextInput(view);
} else if (method.equals("TextInput.setClient")) {
setTextInputClient(view, args.getInt(0), args.getJSONObject(0));
} else if (method.equals("TextInput.setEditingState")) {
setTextInputEditingState(view, args.getJSONObject(0));
} else if (method.equals("TextInput.clearClient")) {
clearTextInputClient();
} else {
// TODO(abarth): We should throw an exception here that gets
// transmitted back to Dart.
}
return null;
}
private static int inputTypeFromTextInputType(String inputType) {
if (inputType.equals("TextInputType.datetime"))
return InputType.TYPE_CLASS_DATETIME;
if (inputType.equals("TextInputType.datetime"))
return InputType.TYPE_CLASS_NUMBER;
if (inputType.equals("TextInputType.datetime"))
return InputType.TYPE_CLASS_PHONE;
return InputType.TYPE_CLASS_TEXT;
}
public InputConnection createInputConnection(FlutterView view, EditorInfo outAttrs) {
if (mClient == 0)
return null;
try {
outAttrs.inputType = inputTypeFromTextInputType(mConfiguration.getString("inputType"));
outAttrs.actionLabel = mConfiguration.getString("actionLabel");
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN;
InputConnectionAdaptor connection = new InputConnectionAdaptor(view, mClient);
if (mIncomingState != null) {
outAttrs.initialSelStart = mIncomingState.getInt("selectionBase");
outAttrs.initialSelEnd = mIncomingState.getInt("selectionExtent");
connection.getEditable().append(mIncomingState.getString("text"));
connection.setSelection(mIncomingState.getInt("selectionBase"),
mIncomingState.getInt("selectionExtent"));
connection.setComposingRegion(mIncomingState.getInt("composingBase"),
mIncomingState.getInt("composingExtent"));
} else {
outAttrs.initialSelStart = 0;
outAttrs.initialSelEnd = 0;
}
return connection;
} catch (JSONException e) {
Log.e(TAG, "Failed to create input connection", e);
}
return null;
}
private void showTextInput(FlutterView view) {
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, 0);
}
private void hideTextInput(FlutterView view) {
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
}
private void setTextInputClient(FlutterView view, int client, JSONObject configuration) throws JSONException {
mIncomingState = null;
mClient = client;
mConfiguration = configuration;
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.restartInput(view);
}
private void setTextInputEditingState(FlutterView view, JSONObject state) throws JSONException {
mIncomingState = state;
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.restartInput(view);
}
private void clearTextInputClient() {
mClient = 0;
}
}
......@@ -60,6 +60,7 @@ import java.util.Locale;
import java.util.Map;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformPlugin;
import org.domokit.editing.KeyboardImpl;
......@@ -77,6 +78,8 @@ public class FlutterView extends SurfaceView
private static final String ACTION_DISCOVER = "io.flutter.view.DISCOVER";
private long mNativePlatformView;
private TextInputPlugin mTextInputPlugin;
private SkyEngine.Proxy mSkyEngine;
private ServiceProviderImpl mPlatformServiceProvider;
private Binding mPlatformServiceProviderBinding;
......@@ -159,6 +162,8 @@ public class FlutterView extends SurfaceView
PlatformPlugin platformPlugin = new PlatformPlugin((Activity)getContext());
addOnMessageListener("flutter/platform", platformPlugin);
addActivityLifecycleListener(platformPlugin);
mTextInputPlugin = new TextInputPlugin((Activity)getContext());
addOnMessageListener("flutter/textinput", mTextInputPlugin);
if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
discoveryReceiver = new DiscoveryReceiver();
......@@ -181,7 +186,7 @@ public class FlutterView extends SurfaceView
message.put("type", "keyup");
message.put("keymap", "android");
encodeKeyEvent(event, message);
dispatchPlatformMessage("flutter/keyevent", message.toString(), null);
sendPlatformMessage("flutter/keyevent", message.toString(), null);
} catch (JSONException e) {
Log.e(TAG, "Failed to serialize key event", e);
}
......@@ -195,7 +200,7 @@ public class FlutterView extends SurfaceView
message.put("type", "keydown");
message.put("keymap", "android");
encodeKeyEvent(event, message);
dispatchPlatformMessage("flutter/keyevent", message.toString(), null);
sendPlatformMessage("flutter/keyevent", message.toString(), null);
} catch (JSONException e) {
Log.e(TAG, "Failed to serialize key event", e);
}
......@@ -269,7 +274,10 @@ public class FlutterView extends SurfaceView
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return mKeyboardState.createInputConnection(outAttrs);
InputConnection connection = mKeyboardState.createInputConnection(outAttrs);
if (connection == null)
connection = mTextInputPlugin.createInputConnection(this, outAttrs);
return connection;
}
// Must match the PointerChange enum in pointer.dart.
......@@ -738,7 +746,12 @@ public class FlutterView extends SurfaceView
return true;
}
private void dispatchPlatformMessage(String name, String message, MessageReplyCallback callback) {
/**
* Send a message to the Flutter application. The Flutter application can
* register a platform message handler that will receive these messages with
* the PlatformMessages object.
*/
public void sendPlatformMessage(String name, String message, MessageReplyCallback callback) {
int responseId = 0;
if (callback != null) {
responseId = mNextResponseId++;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册