提交 c4edec74 编写于 作者: M Mikkel Nygaard Ravn 提交者: GitHub

Remove old flutter messaging API (#3482)

Breaking change: removed facilities for JSON and string messaging from FlutterView/FlutterViewController, leaving only binary messaging there. All other use of flutter communication now goes through FlutterMessageChannel and FlutterMethodChannels. Retained use of String and JSON codecs for now.

Companion flutter PR: flutter/flutter#8837
上级 4a5a3246
......@@ -82,18 +82,18 @@ android_library("java") {
"io/flutter/app/FlutterActivity.java",
"io/flutter/app/FlutterApplication.java",
"io/flutter/plugin/common/ActivityLifecycleListener.java",
"io/flutter/plugin/common/BinaryMessageCodec.java",
"io/flutter/plugin/common/BinaryCodec.java",
"io/flutter/plugin/common/FlutterException.java",
"io/flutter/plugin/common/FlutterMessageChannel.java",
"io/flutter/plugin/common/FlutterMethodChannel.java",
"io/flutter/plugin/common/JSONMessageCodec.java",
"io/flutter/plugin/common/JSONMessageListener.java",
"io/flutter/plugin/common/JSONMethodCodec.java",
"io/flutter/plugin/common/MessageCodec.java",
"io/flutter/plugin/common/MethodCodec.java",
"io/flutter/plugin/common/MethodCall.java",
"io/flutter/plugin/common/StandardMessageCodec.java",
"io/flutter/plugin/common/StandardMethodCodec.java",
"io/flutter/plugin/common/StringMessageCodec.java",
"io/flutter/plugin/common/StringCodec.java",
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
"io/flutter/plugin/editing/TextInputPlugin.java",
"io/flutter/plugin/platform/PlatformPlugin.java",
......
......@@ -9,11 +9,11 @@ import java.nio.ByteBuffer;
/**
* A {@link MessageCodec} using unencoded binary messages, represented as {@link ByteBuffer}s.
*/
public final class BinaryMessageCodec implements MessageCodec<ByteBuffer> {
public final class BinaryCodec implements MessageCodec<ByteBuffer> {
// This codec must match the Dart codec of the same name in package flutter/services.
public static final BinaryMessageCodec INSTANCE = new BinaryMessageCodec();
public static final BinaryCodec INSTANCE = new BinaryCodec();
private BinaryMessageCodec() {
private BinaryCodec() {
}
@Override
......
// Copyright 2017 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.common;
/**
* Thrown to indicate that a Flutter method invocation failed on the Flutter side.
*/
public class FlutterException extends RuntimeException {
public final String code;
public final Object details;
FlutterException(String code, String message, Object details) {
super(message);
assert code != null;
this.code = code;
this.details = details;
}
}
......@@ -6,6 +6,7 @@ package io.flutter.plugin.common;
import android.util.Log;
import io.flutter.view.FlutterView;
import io.flutter.view.FlutterView.BinaryMessageReplyCallback;
import io.flutter.view.FlutterView.BinaryMessageResponse;
import io.flutter.view.FlutterView.OnBinaryMessageListenerAsync;
import java.nio.ByteBuffer;
......@@ -60,6 +61,27 @@ public final class FlutterMethodChannel {
this.codec = codec;
}
/**
* Invokes a method on this channel, expecting no result.
*
* @param method the name String of the method.
* @param arguments the arguments for the invocation, possibly null.
*/
public void invokeMethod(String method, Object arguments) {
invokeMethod(method, arguments, null);
}
/**
* Invokes a method on this channel.
*
* @param call a {@link MethodCall}.
* @param handler a {@link Response} handler for the invocation result.
*/
public void invokeMethod(String method, Object arguments, Response handler) {
view.sendBinaryMessage(name, codec.encodeMethodCall(new MethodCall(method, arguments)),
handler == null ? null : new MethodCallResultCallback(handler));
}
/**
* Registers a method call handler on this channel.
*
......@@ -106,7 +128,8 @@ public final class FlutterMethodChannel {
* Handles a stream setup request.
*
* @param arguments Stream configuration arguments, possibly null.
* @param eventSink A {@link EventSink} used to emit events once the stream has been set up.
* @param eventSink An {@link EventSink} used to emit events once the stream has been set
* up.
*/
void listen(Object arguments, EventSink eventSink);
......@@ -150,6 +173,24 @@ public final class FlutterMethodChannel {
void done();
}
private final class MethodCallResultCallback implements BinaryMessageReplyCallback {
private final Response handler;
MethodCallResultCallback(Response handler) {
this.handler = handler;
}
@Override
public void onReply(ByteBuffer reply) {
try {
final Object result = codec.decodeEnvelope(reply);
handler.success(result);
} catch (FlutterException e) {
handler.error(e.code, e.getMessage(), e.details);
}
}
}
private final class MethodCallListener implements OnBinaryMessageListenerAsync {
private final MethodCallHandler handler;
......@@ -188,7 +229,7 @@ public final class FlutterMethodChannel {
});
} catch (Exception e) {
Log.e(TAG + name, "Failed to handle method call", e);
response.send(codec.encodeErrorEnvelope("error", e.getMessage(),null));
response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
}
}
......@@ -236,13 +277,13 @@ public final class FlutterMethodChannel {
if (cancelled.get()) {
return;
}
FlutterMethodChannel.this.view.sendToFlutter(name,null);
FlutterMethodChannel.this.view.sendBinaryMessage(name, null, null);
}
});
response.send(codec.encodeSuccessEnvelope(null));
} catch (Exception e) {
Log.e(TAG + name, "Failed to open event stream", e);
response.send(codec.encodeErrorEnvelope("error", e.getMessage(),null));
response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
} else if (call.method.equals("cancel")) {
cancelled.set(true);
......@@ -251,7 +292,7 @@ public final class FlutterMethodChannel {
response.send(codec.encodeSuccessEnvelope(null));
} catch (Exception e) {
Log.e(TAG + name, "Failed to close event stream", e);
response.send(codec.encodeErrorEnvelope("error", e.getMessage(),null));
response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
}
}
......
......@@ -26,7 +26,7 @@ public final class JSONMessageCodec implements MessageCodec<Object> {
if (message == null) {
return null;
}
return StringMessageCodec.INSTANCE.encodeMessage(JSONObject.wrap(message).toString());
return StringCodec.INSTANCE.encodeMessage(JSONObject.wrap(message).toString());
}
@Override
......@@ -35,7 +35,7 @@ public final class JSONMessageCodec implements MessageCodec<Object> {
return null;
}
try {
final String json = StringMessageCodec.INSTANCE.decodeMessage(message);
final String json = StringCodec.INSTANCE.decodeMessage(message);
final JSONTokener tokener = new JSONTokener(json);
final Object value = tokener.nextValue();
if (tokener.more()) {
......
// 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.common;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import io.flutter.view.FlutterView;
/** @deprecated Use {@link FlutterMessageChannel} and {@link JSONMessageCodec} instead. */
@Deprecated
public abstract class JSONMessageListener implements FlutterView.OnMessageListener {
static final String TAG = "FlutterView";
@Override
public String onMessage(FlutterView view, String message) {
try {
JSONObject response = onJSONMessage(view, new JSONObject(message));
if (response == null)
return null;
return response.toString();
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return null;
}
}
public abstract JSONObject onJSONMessage(FlutterView view, JSONObject message) throws JSONException;
public static String getStringOrNull(JSONObject object, String name) throws JSONException {
return object.isNull(name) ? null : object.getString(name);
}
}
......@@ -11,37 +11,77 @@ import org.json.JSONObject;
* {@link JSONMessageCodec}.
*/
public final class JSONMethodCodec implements MethodCodec {
public static final JSONMethodCodec INSTANCE = new JSONMethodCodec();
private JSONMethodCodec() {
}
@Override
public MethodCall decodeMethodCall(ByteBuffer message) {
try {
final Object json = JSONMessageCodec.INSTANCE.decodeMessage(message);
if (json instanceof JSONArray) {
final JSONArray pair = (JSONArray) json;
if (pair.length() == 2 && pair.get(0) instanceof String) {
return new MethodCall(pair.getString(0), pair.get(1));
public static final JSONMethodCodec INSTANCE = new JSONMethodCodec();
private JSONMethodCodec() {
}
@Override
public ByteBuffer encodeMethodCall(MethodCall methodCall) {
try {
final JSONObject map = new JSONObject();
map.put("method", methodCall.method);
map.put("args", JSONObject.wrap(methodCall.arguments));
return JSONMessageCodec.INSTANCE.encodeMessage(map);
} catch (JSONException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
@Override
public MethodCall decodeMethodCall(ByteBuffer message) {
try {
final Object json = JSONMessageCodec.INSTANCE.decodeMessage(message);
if (json instanceof JSONObject) {
final JSONObject map = (JSONObject) json;
final Object method = map.get("method");
final Object arguments = map.get("args");
if (method instanceof String) {
return new MethodCall((String) method, arguments);
}
}
throw new IllegalArgumentException("Invalid method call: " + json);
} catch (JSONException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
@Override
public ByteBuffer encodeSuccessEnvelope(Object result) {
return JSONMessageCodec.INSTANCE
.encodeMessage(new JSONArray().put(JSONObject.wrap(result)));
}
@Override
public ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage,
Object errorDetails) {
return JSONMessageCodec.INSTANCE.encodeMessage(new JSONArray()
.put(errorCode)
.put(errorMessage)
.put(JSONObject.wrap(errorDetails)));
}
@Override
public Object decodeEnvelope(ByteBuffer envelope) {
try {
final Object json = JSONMessageCodec.INSTANCE.decodeMessage(envelope);
if (json instanceof JSONArray) {
final JSONArray array = (JSONArray) json;
if (array.length() == 1) {
return array.get(0);
}
if (array.length() == 3) {
final Object code = array.get(0);
final Object message = array.get(1);
final Object details = array.get(2);
if (code instanceof String && (message == null || message instanceof String)) {
throw new FlutterException((String) code, (String) message, details);
}
}
}
throw new IllegalArgumentException("Invalid method call: " + json);
} catch (JSONException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
throw new IllegalArgumentException("Invalid method call: " + json);
} catch (JSONException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
@Override
public ByteBuffer encodeSuccessEnvelope(Object result) {
return JSONMessageCodec.INSTANCE.encodeMessage(new JSONArray().put(JSONObject.wrap(result)));
}
@Override
public ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails) {
return JSONMessageCodec.INSTANCE.encodeMessage(new JSONArray()
.put(errorCode)
.put(errorMessage)
.put(JSONObject.wrap(errorDetails)));
}
}
......@@ -4,8 +4,6 @@
package io.flutter.plugin.common;
import java.util.Objects;
/**
* Command object representing a method call on a {@link FlutterMessageChannel}.
*/
......
......@@ -16,6 +16,15 @@ import java.nio.ByteBuffer;
* All operations throw {@link IllegalArgumentException}, if conversion fails.
*/
public interface MethodCodec {
/**
* Encodes a message call into binary.
*
* @param methodCall a {@link MethodCall}.
* @return a {@link ByteBuffer} containing the encoding between position 0 and
* the current position.
*/
ByteBuffer encodeMethodCall(MethodCall methodCall);
/**
* Decodes a message call from binary.
*
......@@ -29,7 +38,7 @@ public interface MethodCodec {
* Encodes a successful result into a binary envelope message.
*
* @param result The result value, possibly null.
* @return a ByteBuffer containing the encoding between position 0 and
* @return a {@link ByteBuffer} containing the encoding between position 0 and
* the current position.
*/
ByteBuffer encodeSuccessEnvelope(Object result);
......@@ -40,8 +49,17 @@ public interface MethodCodec {
* @param errorCode An error code String.
* @param errorMessage An error message String, possibly null.
* @param errorDetails Error details, possibly null.
* @return a ByteBuffer containing the encoding between position 0 and
* @return a {@link ByteBuffer} containing the encoding between position 0 and
* the current position.
*/
ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails);
/**
* Decodes a result envelope from binary.
*
* @param envelope the binary encoding of a result envelope as a {@link ByteBuffer}.
* @return the enveloped result Object.
* @throws FlutterException if the envelope was an error envelope.
*/
Object decodeEnvelope(ByteBuffer envelope);
}
......@@ -25,12 +25,22 @@ public final class StandardMethodCodec implements MethodCodec {
private StandardMethodCodec() {
}
@Override
public ByteBuffer encodeMethodCall(MethodCall methodCall) {
final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
StandardMessageCodec.writeValue(stream, methodCall.method);
StandardMessageCodec.writeValue(stream, methodCall.arguments);
final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
buffer.put(stream.buffer(), 0, stream.size());
return buffer;
}
@Override
public MethodCall decodeMethodCall(ByteBuffer methodCall) {
methodCall.order(ByteOrder.nativeOrder());
final Object method = StandardMessageCodec.readValue(methodCall);
final Object arguments = StandardMessageCodec.readValue(methodCall);
if (method instanceof String) {
if (method instanceof String && !methodCall.hasRemaining()) {
return new MethodCall((String) method, arguments);
}
throw new IllegalArgumentException("Method call corrupted");
......@@ -58,4 +68,29 @@ public final class StandardMethodCodec implements MethodCodec {
buffer.put(stream.buffer(), 0, stream.size());
return buffer;
}
@Override
public Object decodeEnvelope(ByteBuffer envelope) {
envelope.order(ByteOrder.nativeOrder());
final byte flag = envelope.get();
switch (flag) {
case 0: {
final Object result = StandardMessageCodec.readValue(envelope);
if (!envelope.hasRemaining()) {
return result;
}
}
case 1: {
final Object code = StandardMessageCodec.readValue(envelope);
final Object message = StandardMessageCodec.readValue(envelope);
final Object details = StandardMessageCodec.readValue(envelope);
if (code instanceof String
&& (message == null || message instanceof String)
&& !envelope.hasRemaining()) {
throw new FlutterException((String) code, (String) message, details);
}
}
}
throw new IllegalArgumentException("Envelope corrupted");
}
}
......@@ -10,12 +10,12 @@ import java.nio.charset.Charset;
/**
* A {@link MessageCodec} using UTF-8 encoded String messages.
*/
public final class StringMessageCodec implements MessageCodec<String> {
public final class StringCodec implements MessageCodec<String> {
// This codec must match the Dart codec of the same name in package flutter/services.
private static final Charset UTF8 = Charset.forName("UTF8");
public static final StringMessageCodec INSTANCE = new StringMessageCodec();
public static final StringCodec INSTANCE = new StringCodec();
private StringMessageCodec() {
private StringCodec() {
}
@Override
......
......@@ -6,56 +6,41 @@ 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.plugin.common.FlutterMethodChannel;
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";
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
private FlutterView mView;
private int mClient;
private TextInputPlugin mPlugin;
private JSONObject mOutgoingState;
class InputConnectionAdaptor extends BaseInputConnection {
private final int mClient;
private final TextInputPlugin mPlugin;
private final FlutterMethodChannel mFlutterChannel;
private final Map<String, Object> mOutgoingState;
public InputConnectionAdaptor(FlutterView view, int client, TextInputPlugin plugin) {
public InputConnectionAdaptor(FlutterView view, int client,
TextInputPlugin plugin, FlutterMethodChannel flutterChannel) {
super(view, true);
mView = view;
mClient = client;
mPlugin = plugin;
mOutgoingState = new JSONObject();
mFlutterChannel = flutterChannel;
mOutgoingState = new HashMap<>();
}
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);
mPlugin.setLatestEditingState(mOutgoingState);
} catch (JSONException e) {
Log.e(TAG, "Unexpected error serializing editing state", e);
}
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));
mFlutterChannel.invokeMethod("TextInputClient.updateEditingState", Arrays
.asList(mClient, mOutgoingState));
mPlugin.setLatestEditingState(mOutgoingState);
}
@Override
......@@ -103,7 +88,7 @@ class InputConnectionAdaptor extends BaseInputConnection {
// 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)) {
optInt("selectionBase", -1) == optInt("selectionExtent", -1)) {
deleteSurroundingText(1, 0);
} else {
String text = event.getNumber() == 0 ? "" : String.valueOf(event.getNumber());
......@@ -113,21 +98,15 @@ class InputConnectionAdaptor extends BaseInputConnection {
return result;
}
private int optInt(String key, int defaultValue) {
return mOutgoingState.containsKey(key) ? (Integer) mOutgoingState.get(key) : defaultValue;
}
@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;
}
// TODO(abarth): Support more actions.
mFlutterChannel.invokeMethod("TextInputClient.performAction",
Arrays.asList(mClient, "TextInputAction.done"));
return true;
}
}
......@@ -6,17 +6,19 @@ 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.plugin.common.FlutterMethodChannel;
import io.flutter.plugin.common.FlutterMethodChannel.MethodCallHandler;
import io.flutter.plugin.common.FlutterMethodChannel.Response;
import io.flutter.plugin.common.JSONMethodCodec;
import io.flutter.plugin.common.MethodCall;
import io.flutter.view.FlutterView;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -24,37 +26,50 @@ import org.json.JSONObject;
/**
* Android implementation of the text input plugin.
*/
public class TextInputPlugin extends JSONMessageListener {
private static final String TAG = "FlutterView";
public class TextInputPlugin implements MethodCallHandler {
private final Activity mActivity;
private final FlutterView mView;
private final FlutterMethodChannel mFlutterChannel;
private int mClient = 0;
private JSONObject mConfiguration;
private JSONObject mLatestState;
public TextInputPlugin(Activity activity) {
public TextInputPlugin(Activity activity, FlutterView view) {
mActivity = activity;
mView = view;
mFlutterChannel = new FlutterMethodChannel(view, "flutter/textinput",
JSONMethodCodec.INSTANCE);
mFlutterChannel.setMethodCallHandler(this);
}
@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(1));
} 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.
public void onMethodCall(MethodCall call, Response response) {
String method = call.method;
Object args = call.arguments;
try {
if (method.equals("TextInput.show")) {
showTextInput(mView);
response.success(null);
} else if (method.equals("TextInput.hide")) {
hideTextInput(mView);
response.success(null);
} else if (method.equals("TextInput.setClient")) {
final JSONArray argumentList = (JSONArray) args;
setTextInputClient(mView, argumentList.getInt(0), argumentList.getJSONObject(1));
response.success(null);
} else if (method.equals("TextInput.setEditingState")) {
setTextInputEditingState(mView, (JSONObject) args);
response.success(null);
} else if (method.equals("TextInput.clearClient")) {
clearTextInputClient();
response.success(null);
} else {
response.error("unknown", "Unknown method: " + call.method, null);
}
} catch (JSONException e) {
response.error("error", "JSON error: " + e.getMessage(), null);
}
return null;
}
private static int inputTypeFromTextInputType(String inputType) {
......@@ -67,67 +82,64 @@ public class TextInputPlugin extends JSONMessageListener {
return InputType.TYPE_CLASS_TEXT;
}
public InputConnection createInputConnection(FlutterView view, EditorInfo outAttrs) {
public InputConnection createInputConnection(FlutterView view, EditorInfo outAttrs)
throws JSONException {
if (mClient == 0)
return null;
try {
outAttrs.inputType = inputTypeFromTextInputType(mConfiguration.getString("inputType"));
outAttrs.actionLabel = getStringOrNull(mConfiguration, "actionLabel");
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN;
InputConnectionAdaptor connection = new InputConnectionAdaptor(view, mClient, this);
if (mLatestState != null) {
int selectionBase = mLatestState.getInt("selectionBase");
int selectionExtent = mLatestState.getInt("selectionExtent");
outAttrs.initialSelStart = selectionBase;
outAttrs.initialSelEnd = selectionExtent;
connection.getEditable().append(mLatestState.getString("text"));
connection.setSelection(Math.max(selectionBase, 0),
Math.max(selectionExtent, 0));
connection.setComposingRegion(mLatestState.getInt("composingBase"),
mLatestState.getInt("composingExtent"));
} else {
outAttrs.initialSelStart = 0;
outAttrs.initialSelEnd = 0;
}
return connection;
} catch (JSONException e) {
Log.e(TAG, "Failed to create input connection", e);
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, this,
mFlutterChannel);
if (mLatestState != null) {
int selectionBase = (Integer) mLatestState.get("selectionBase");
int selectionExtent = (Integer) mLatestState.get("selectionExtent");
outAttrs.initialSelStart = selectionBase;
outAttrs.initialSelEnd = selectionExtent;
connection.getEditable().append((String) mLatestState.get("text"));
connection.setSelection(Math.max(selectionBase, 0),
Math.max(selectionExtent, 0));
connection.setComposingRegion((Integer) mLatestState.get("composingBase"),
(Integer) mLatestState.get("composingExtent"));
} else {
outAttrs.initialSelStart = 0;
outAttrs.initialSelEnd = 0;
}
return null;
return connection;
}
private void showTextInput(FlutterView view) {
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
(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);
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
}
private void setTextInputClient(FlutterView view, int client, JSONObject configuration) throws JSONException {
private void setTextInputClient(FlutterView view, int client, JSONObject configuration) {
mLatestState = null;
mClient = client;
mConfiguration = configuration;
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.restartInput(view);
}
private void setTextInputEditingState(FlutterView view, JSONObject state) throws JSONException {
private void setTextInputEditingState(FlutterView view, JSONObject state) {
mLatestState = state;
InputMethodManager imm =
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.restartInput(view);
}
void setLatestEditingState(JSONObject state) {
mLatestState = state;
void setLatestEditingState(Map<String, Object> state) {
mLatestState = (JSONObject) JSONObject.wrap(state);
}
private void clearTextInputClient() {
mClient = 0;
}
......
......@@ -16,9 +16,12 @@ import android.os.Build;
import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.JSONMessageListener;
import io.flutter.view.FlutterView;
import io.flutter.plugin.common.FlutterMethodChannel.MethodCallHandler;
import io.flutter.plugin.common.FlutterMethodChannel.Response;
import io.flutter.plugin.common.MethodCall;
import org.chromium.base.PathUtils;
import org.json.JSONArray;
import org.json.JSONException;
......@@ -27,7 +30,7 @@ import org.json.JSONObject;
/**
* Android implementation of the platform plugin.
*/
public class PlatformPlugin extends JSONMessageListener implements ActivityLifecycleListener {
public class PlatformPlugin implements MethodCallHandler, ActivityLifecycleListener {
private final Activity mActivity;
public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
......@@ -39,38 +42,49 @@ public class PlatformPlugin extends JSONMessageListener implements ActivityLifec
}
@Override
public JSONObject onJSONMessage(FlutterView view, JSONObject message) throws JSONException {
String method = message.getString("method");
JSONArray args = message.getJSONArray("args");
if (method.equals("SystemSound.play")) {
playSystemSound(args.getString(0));
} else if (method.equals("HapticFeedback.vibrate")) {
vibrateHapticFeedback();
} else if (method.equals("UrlLauncher.launch")) {
launchURL(args.getString(0));
} else if (method.equals("SystemChrome.setPreferredOrientations")) {
setSystemChromePreferredOrientatations(args.getJSONArray(0));
} else if (method.equals("SystemChrome.setApplicationSwitcherDescription")) {
setSystemChromeApplicationSwitcherDescription(args.getJSONObject(0));
} else if (method.equals("SystemChrome.setEnabledSystemUIOverlays")) {
setSystemChromeEnabledSystemUIOverlays(args.getJSONArray(0));
} else if (method.equals("SystemChrome.setSystemUIOverlayStyle")) {
setSystemChromeSystemUIOverlayStyle(args.getString(0));
} else if (method.equals("SystemNavigator.pop")) {
popSystemNavigator();
} else if (method.equals("Clipboard.getData")) {
return getClipboardData(args.getString(0));
} else if (method.equals("Clipboard.setData")) {
setClipboardData(args.getJSONObject(0));
} else if (method.equals("PathProvider.getTemporaryDirectory")) {
return getPathProviderTemporaryDirectory();
} else if (method.equals("PathProvider.getApplicationDocumentsDirectory")) {
return getPathProviderApplicationDocumentsDirectory();
} else {
// TODO(abarth): We should throw an exception here that gets
// transmitted back to Dart.
public void onMethodCall(MethodCall call, Response response) {
String method = call.method;
Object arguments = call.arguments;
try {
if (method.equals("SystemSound.play")) {
playSystemSound((String) arguments);
response.success(null);
} else if (method.equals("HapticFeedback.vibrate")) {
vibrateHapticFeedback();
response.success(null);
} else if (method.equals("UrlLauncher.launch")) {
launchURL((String) arguments);
response.success(null);
} else if (method.equals("SystemChrome.setPreferredOrientations")) {
setSystemChromePreferredOrientations((JSONArray) arguments);
response.success(null);
} else if (method.equals("SystemChrome.setApplicationSwitcherDescription")) {
setSystemChromeApplicationSwitcherDescription((JSONObject) arguments);
response.success(null);
} else if (method.equals("SystemChrome.setEnabledSystemUIOverlays")) {
setSystemChromeEnabledSystemUIOverlays((JSONArray) arguments);
response.success(null);
} else if (method.equals("SystemChrome.setSystemUIOverlayStyle")) {
setSystemChromeSystemUIOverlayStyle((String) arguments);
response.success(null);
} else if (method.equals("SystemNavigator.pop")) {
popSystemNavigator();
response.success(null);
} else if (method.equals("Clipboard.getData")) {
response.success(getClipboardData((String) arguments));
} else if (method.equals("Clipboard.setData")) {
setClipboardData((JSONObject) arguments);
response.success(null);
} else if (method.equals("PathProvider.getTemporaryDirectory")) {
response.success(getPathProviderTemporaryDirectory());
} else if (method.equals("PathProvider.getApplicationDocumentsDirectory")) {
response.success(getPathProviderApplicationDocumentsDirectory());
} else {
response.error("unknown", "Unknown method: " + method, null);
}
} catch (JSONException e) {
response.error("error", "JSON error: " + e.getMessage(), null);
}
return null;
}
private void playSystemSound(String soundType) {
......@@ -95,19 +109,19 @@ public class PlatformPlugin extends JSONMessageListener implements ActivityLifec
}
}
private void setSystemChromePreferredOrientatations(JSONArray orientatations) throws JSONException {
private void setSystemChromePreferredOrientations(JSONArray orientations) throws JSONException {
// Currently the Android implementation only supports masks with zero or one
// selected device orientations.
int androidOrientation;
if (orientatations.length() == 0) {
if (orientations.length() == 0) {
androidOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
} else if (orientatations.getString(0).equals("DeviceOrientation.portraitUp")) {
} else if (orientations.getString(0).equals("DeviceOrientation.portraitUp")) {
androidOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else if (orientatations.getString(0).equals("DeviceOrientation.landscapeLeft")) {
} else if (orientations.getString(0).equals("DeviceOrientation.landscapeLeft")) {
androidOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
} else if (orientatations.getString(0).equals("DeviceOrientation.portraitDown")) {
} else if (orientations.getString(0).equals("DeviceOrientation.portraitDown")) {
androidOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
} else if (orientatations.getString(0).equals("DeviceOrientation.landscapeRight")) {
} else if (orientations.getString(0).equals("DeviceOrientation.landscapeRight")) {
androidOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
} else {
return;
......@@ -127,11 +141,11 @@ public class PlatformPlugin extends JSONMessageListener implements ActivityLifec
}
mActivity.setTaskDescription(
new android.app.ActivityManager.TaskDescription(
description.getString("label"),
null,
color
)
new android.app.ActivityManager.TaskDescription(
description.getString("label"),
null,
color
)
);
}
......@@ -143,9 +157,9 @@ public class PlatformPlugin extends JSONMessageListener implements ActivityLifec
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (overlays.length() == 0) {
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
if (overlays.length() == 0) {
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
for (int i = 0; i < overlays.length(); ++i) {
String overlay = overlays.getString(i);
......@@ -183,9 +197,9 @@ public class PlatformPlugin extends JSONMessageListener implements ActivityLifec
if ((format == null || format.equals(kTextPlainFormat)) &&
clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
JSONObject result = new JSONObject();
result.put("text", clip.getItemAt(0).getText().toString());
return result;
JSONObject result = new JSONObject();
result.put("text", clip.getItemAt(0).getText().toString());
return result;
}
return null;
......@@ -197,16 +211,12 @@ public class PlatformPlugin extends JSONMessageListener implements ActivityLifec
clipboard.setPrimaryClip(clip);
}
private JSONObject getPathProviderTemporaryDirectory() throws JSONException {
JSONObject result = new JSONObject();
result.put("path", mActivity.getCacheDir().getPath());
return result;
private String getPathProviderTemporaryDirectory() {
return mActivity.getCacheDir().getPath();
}
private JSONObject getPathProviderApplicationDocumentsDirectory() throws JSONException {
JSONObject result = new JSONObject();
result.put("path", PathUtils.getDataDirectory(mActivity));
return result;
private String getPathProviderApplicationDocumentsDirectory() {
return PathUtils.getDataDirectory(mActivity);
}
@Override
......
......@@ -10,12 +10,10 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
......
......@@ -10,10 +10,8 @@ import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.SystemClock;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -21,9 +19,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.chromium.base.JNINamespace;
import org.chromium.base.PathUtils;
......
......@@ -11,7 +11,6 @@ import android.util.Log;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
/**
* A class to clean up orphaned resource directories after unclean shutdowns.
......
......@@ -17,14 +17,11 @@ shared_library("flutter_framework_dylib") {
sources = [
"framework/Headers/Flutter.h",
"framework/Headers/FlutterAppDelegate.h",
"framework/Headers/FlutterAsyncMessageListener.h",
"framework/Headers/FlutterChannels.h",
"framework/Headers/FlutterCodecs.h",
"framework/Headers/FlutterBinaryMessenger.h",
"framework/Headers/FlutterDartProject.h",
"framework/Headers/FlutterJSONMessageListener.h",
"framework/Headers/FlutterMacros.h",
"framework/Headers/FlutterMessageListener.h",
"framework/Headers/FlutterViewController.h",
"framework/Source/FlutterAppDelegate.mm",
"framework/Source/FlutterChannels.mm",
......@@ -33,7 +30,6 @@ shared_library("flutter_framework_dylib") {
"framework/Source/FlutterDartProject_Internal.h",
"framework/Source/FlutterDartSource.h",
"framework/Source/FlutterDartSource.mm",
"framework/Source/FlutterJSONMessageListener.mm",
"framework/Source/FlutterPlatformPlugin.h",
"framework/Source/FlutterPlatformPlugin.mm",
"framework/Source/FlutterStandardCodec_Internal.h",
......@@ -158,14 +154,11 @@ copy("framework_headers") {
sources = [
"framework/Headers/Flutter.h",
"framework/Headers/FlutterAppDelegate.h",
"framework/Headers/FlutterAsyncMessageListener.h",
"framework/Headers/FlutterBinaryMessenger.h",
"framework/Headers/FlutterChannels.h",
"framework/Headers/FlutterCodecs.h",
"framework/Headers/FlutterDartProject.h",
"framework/Headers/FlutterJSONMessageListener.h",
"framework/Headers/FlutterMacros.h",
"framework/Headers/FlutterMessageListener.h",
"framework/Headers/FlutterViewController.h",
]
outputs = [
......
......@@ -6,14 +6,11 @@
#define FLUTTER_FLUTTER_H_
#include "FlutterAppDelegate.h"
#include "FlutterAsyncMessageListener.h"
#include "FlutterBinaryMessenger.h"
#include "FlutterChannels.h"
#include "FlutterCodecs.h"
#include "FlutterDartProject.h"
#include "FlutterJSONMessageListener.h"
#include "FlutterMacros.h"
#include "FlutterMessageListener.h"
#include "FlutterViewController.h"
#endif // FLUTTER_FLUTTER_H_
// 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.
#ifndef FLUTTER_FLUTTERASYNCMESSAGELISTENER_H_
#define FLUTTER_FLUTTERASYNCMESSAGELISTENER_H_
#import <Foundation/Foundation.h>
#include "FlutterMacros.h"
FLUTTER_EXPORT
@protocol FlutterAsyncMessageListener<NSObject>
- (void)didReceiveString:(NSString*)message
callback:(void(^)(NSString*))sendResponse;
@property(readonly, strong, nonatomic) NSString* messageName;
@end
#endif // FLUTTER_FLUTTERASYNCMESSAGELISTENER_H_
......@@ -17,6 +17,10 @@ FLUTTER_EXPORT
+ (instancetype)messageChannelNamed:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMessageCodec>*)codec;
- (instancetype)initWithName:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMessageCodec>*)codec;
- (void)sendMessage:(id)message;
- (void)sendMessage:(id)message replyHandler:(FlutterReplyHandler)handler;
- (void)setMessageHandler:(FlutterMessageHandler)handler;
@end
......@@ -37,6 +41,13 @@ FLUTTER_EXPORT
+ (instancetype)methodChannelNamed:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMethodCodec>*)codec;
- (instancetype)initWithName:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMethodCodec>*)codec;
- (void)invokeMethod:(NSString*)method arguments:(id)arguments;
- (void)invokeMethod:(NSString*)method
arguments:(id)arguments
resultReceiver:(FlutterResultReceiver)resultReceiver;
- (void)setMethodCallHandler:(FlutterMethodCallHandler)handler;
- (void)setStreamHandler:(FlutterStreamHandler)handler;
@end
......
......@@ -35,8 +35,8 @@ FLUTTER_EXPORT
@interface FlutterMethodCall : NSObject
+ (instancetype)methodCallWithMethodName:(NSString*)method
arguments:(id)arguments;
@property(readonly) NSString* method;
@property(readonly) id arguments;
@property(readonly, nonatomic) NSString* method;
@property(readonly, nonatomic) id arguments;
@end
FLUTTER_EXPORT
......@@ -44,9 +44,9 @@ FLUTTER_EXPORT
+ (instancetype)errorWithCode:(NSString*)code
message:(NSString*)message
details:(id)details;
@property(readonly) NSString* code;
@property(readonly) NSString* message;
@property(readonly) id details;
@property(readonly, nonatomic) NSString* code;
@property(readonly, nonatomic) NSString* message;
@property(readonly, nonatomic) id details;
@end
typedef NS_ENUM(NSInteger, FlutterStandardDataType) {
......@@ -62,24 +62,26 @@ FLUTTER_EXPORT
+ (instancetype)typedDataWithInt32:(NSData*)data;
+ (instancetype)typedDataWithInt64:(NSData*)data;
+ (instancetype)typedDataWithFloat64:(NSData*)data;
@property(readonly) NSData* data;
@property(readonly) FlutterStandardDataType type;
@property(readonly) UInt32 elementCount;
@property(readonly) UInt8 elementSize;
@property(readonly, nonatomic) NSData* data;
@property(readonly, nonatomic) FlutterStandardDataType type;
@property(readonly, nonatomic) UInt32 elementCount;
@property(readonly, nonatomic) UInt8 elementSize;
@end
FLUTTER_EXPORT
@interface FlutterStandardBigInteger : NSObject
+ (instancetype)bigIntegerWithHex:(NSString*)hex;
@property(readonly) NSString* hex;
@property(readonly, nonatomic) NSString* hex;
@end
FLUTTER_EXPORT
@protocol FlutterMethodCodec
+ (instancetype)sharedInstance;
- (FlutterMethodCall*)decodeMethodCall:(NSData*)message;
- (NSData*)encodeMethodCall:(FlutterMethodCall*)methodCall;
- (FlutterMethodCall*)decodeMethodCall:(NSData*)methodCall;
- (NSData*)encodeSuccessEnvelope:(id)result;
- (NSData*)encodeErrorEnvelope:(FlutterError*)error;
- (id)decodeEnvelope:(NSData*)envelope error:(FlutterError**)error;
@end
FLUTTER_EXPORT
......
// 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.
#ifndef FLUTTER_FLUTTERJSONMESSAGELISTENER_H_
#define FLUTTER_FLUTTERJSONMESSAGELISTENER_H_
#include "FlutterMessageListener.h"
FLUTTER_EXPORT
@interface FlutterJSONMessageListener : NSObject<FlutterMessageListener>
- (NSDictionary*)didReceiveJSON:(NSDictionary*)message;
@end
#endif // FLUTTER_FLUTTERJSONMESSAGELISTENER_H_
// 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.
#ifndef FLUTTER_FLUTTERMESSAGELISTENER_H_
#define FLUTTER_FLUTTERMESSAGELISTENER_H_
#import <Foundation/Foundation.h>
#include "FlutterMacros.h"
FLUTTER_EXPORT
@protocol FlutterMessageListener<NSObject>
- (NSString*)didReceiveString:(NSString*)message;
@property(readonly, strong, nonatomic) NSString* messageName;
@end
#endif // FLUTTER_FLUTTERMESSAGELISTENER_H_
......@@ -8,11 +8,9 @@
#import <UIKit/UIKit.h>
#include <sys/cdefs.h>
#include "FlutterAsyncMessageListener.h"
#include "FlutterBinaryMessenger.h"
#include "FlutterDartProject.h"
#include "FlutterMacros.h"
#include "FlutterMessageListener.h"
FLUTTER_EXPORT
@interface FlutterViewController : UIViewController<FlutterBinaryMessenger>
......@@ -22,22 +20,6 @@ FLUTTER_EXPORT
bundle:(NSBundle*)nibBundleOrNil
NS_DESIGNATED_INITIALIZER;
- (void)sendString:(NSString*)message withMessageName:(NSString*)messageName;
- (void)sendString:(NSString*)message
withMessageName:(NSString*)messageName
callback:(void (^)(NSString*))callback;
- (void)addMessageListener:(NSObject<FlutterMessageListener>*)listener;
- (void)removeMessageListener:(NSObject<FlutterMessageListener>*)listener;
- (void)addAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener;
- (void)removeAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener;
- (void)handleStatusBarTouches:(UIEvent*)event;
@end
......
......@@ -37,6 +37,11 @@
[super dealloc];
}
- (void)sendMessage:(id)message {
[_messenger sendBinaryMessage:[_codec encode:message]
channelName:_name];
}
- (void)sendMessage:(id)message replyHandler:(FlutterReplyHandler)handler {
[_messenger sendBinaryMessage:[_codec encode:message]
channelName:_name
......@@ -147,6 +152,25 @@
[super dealloc];
}
- (void)invokeMethod:(NSString*)method arguments:(id)arguments {
[_messenger sendBinaryMessage:[_codec encodeMethodCall:[FlutterMethodCall methodCallWithMethodName:method arguments:arguments]]
channelName:_name];
}
- (void)invokeMethod:(NSString*)method
arguments:(id)arguments
resultReceiver:(FlutterResultReceiver)resultReceiver {
[_messenger sendBinaryMessage:[_codec encodeMethodCall:[FlutterMethodCall methodCallWithMethodName:method arguments:arguments]]
channelName:_name
binaryReplyHandler:^(NSData* reply) {
if (resultReceiver) {
FlutterError* flutterError = nil;
id result = [_codec decodeEnvelope:reply error:&flutterError];
resultReceiver(result, flutterError);
}
}];
}
- (void)setMethodCallHandler:(FlutterMethodCallHandler)handler {
[_messenger
setBinaryMessageHandlerOnChannel:_name
......
......@@ -78,20 +78,43 @@
return _sharedInstance;
}
- (NSData*)encodeMethodCall:(FlutterMethodCall*)call {
return [[FlutterJSONMessageCodec sharedInstance] encode:@{
@"method": call.method,
@"args": (call.arguments == nil ? [NSNull null] : call.arguments),
}];
}
- (NSData*)encodeSuccessEnvelope:(id)result {
return [[FlutterJSONMessageCodec sharedInstance] encode:@[ result ]];
return [[FlutterJSONMessageCodec sharedInstance] encode:@[
result == nil ? [NSNull null] : result
]];
}
- (NSData*)encodeErrorEnvelope:(FlutterError*)error {
return [[FlutterJSONMessageCodec sharedInstance]
encode:@[ error.code, error.message, error.details ]];
return [[FlutterJSONMessageCodec sharedInstance] encode:@[
error.code,
error.message == nil ? [NSNull null] : error.message,
error.details == nil ? [NSNull null] : error.details,
]];
}
- (FlutterMethodCall*)decodeMethodCall:(NSData*)message {
NSArray* call = [[FlutterJSONMessageCodec sharedInstance] decode:message];
NSAssert(call.count == 2, @"Invalid JSON method call");
NSAssert([call[0] isKindOfClass:[NSString class]],
@"Invalid JSON method call");
return [FlutterMethodCall methodCallWithMethodName:call[0] arguments:call[1]];
NSDictionary* dictionary = [[FlutterJSONMessageCodec sharedInstance] decode:message];
id method = dictionary[@"method"];
id arguments = dictionary[@"args"];
NSAssert([method isKindOfClass:[NSString class]], @"Invalid JSON method call");
return [FlutterMethodCall methodCallWithMethodName:method arguments:arguments];
}
- (id)decodeEnvelope:(NSData*)envelope error:(FlutterError**)error {
NSArray* array = [[FlutterJSONMessageCodec sharedInstance] decode:envelope];
if (array.count == 1)
return array[0];
NSAssert(array.count == 3, @"Invalid JSON envelope");
NSAssert([array[0] isKindOfClass:[NSString class]], @"Invalid JSON envelope");
NSAssert(array[1] == nil || [array[1] isKindOfClass:[NSString class]], @"Invalid JSON envelope");
*error = [FlutterError errorWithCode:array[0] message:array[1] details:array[2]];
return nil;
}
@end
// 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.
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h"
@implementation FlutterJSONMessageListener
- (NSString*)didReceiveString:(NSString*)message {
if (!message)
return nil;
NSError *error = nil;
NSData* data = [message dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error)
return nil;
NSDictionary* response = [self didReceiveJSON:jsonObject];
if (!response)
return nil;
NSData* responseData = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
if (!responseData)
return nil;
return [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
}
- (NSDictionary*)didReceiveJSON:(NSDictionary*)message {
return nil;
}
- (NSString *)messageName {
return nil;
}
@end
......@@ -5,9 +5,11 @@
#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMPLUGIN_H_
#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMPLUGIN_H_
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
@interface FlutterPlatformPlugin : FlutterJSONMessageListener
@interface FlutterPlatformPlugin : NSObject
-(void)handleMethodCall:(FlutterMethodCall*)call resultReceiver:(FlutterResultReceiver)resultReceiver;
@end
......
......@@ -41,42 +41,45 @@ using namespace shell;
@implementation FlutterPlatformPlugin
- (NSString *)messageName {
return @"flutter/platform";
}
- (NSDictionary*)didReceiveJSON:(NSDictionary*)message {
NSString* method = message[@"method"];
NSArray* args = message[@"args"];
- (void)handleMethodCall:(FlutterMethodCall*)call resultReceiver:(FlutterResultReceiver)resultReceiver {
NSString* method = call.method;
id args = call.arguments;
if ([method isEqualToString:@"SystemSound.play"]) {
[self playSystemSound:args.firstObject];
[self playSystemSound:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"HapticFeedback.vibrate"]) {
[self vibrateHapticFeedback];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"UrlLauncher.launch"]) {
[self launchURL:args.firstObject];
[self launchURL:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"SystemChrome.setPreferredOrientations"]) {
[self setSystemChromePreferredOrientatations:args.firstObject];
[self setSystemChromePreferredOrientations:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"SystemChrome.setApplicationSwitcherDescription"]) {
[self setSystemChromeApplicationSwitcherDescription:args.firstObject];
[self setSystemChromeApplicationSwitcherDescription:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"SystemChrome.setEnabledSystemUIOverlays"]) {
[self setSystemChromeEnabledSystemUIOverlays:args.firstObject];
[self setSystemChromeEnabledSystemUIOverlays:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"SystemChrome.setSystemUIOverlayStyle"]) {
[self setSystemChromeSystemUIOverlayStyle:args.firstObject];
[self setSystemChromeSystemUIOverlayStyle:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"SystemNavigator.pop"]) {
[self popSystemNavigator];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"Clipboard.getData"]) {
return [self getClipboardData:args.firstObject];
resultReceiver([self getClipboardData:args], nil);
} else if ([method isEqualToString:@"Clipboard.setData"]) {
[self setClipboardData:args.firstObject];
[self setClipboardData:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"PathProvider.getTemporaryDirectory"]) {
return [self getPathProviderTemporaryDirectory];
resultReceiver([self getPathProviderTemporaryDirectory], nil);
} else if ([method isEqualToString:@"PathProvider.getApplicationDocumentsDirectory"]) {
return [self getPathProviderApplicationDocumentsDirectory];
resultReceiver([self getPathProviderApplicationDocumentsDirectory], nil);
} else {
// TODO(abarth): We should signal an error here that gets reported back to
// Dart.
resultReceiver(nil, [FlutterError errorWithCode:@"UNKNOWN" message:@"Unknown method" details: nil]);
}
return nil;
}
- (void)playSystemSound:(NSString*)soundType {
......@@ -99,7 +102,7 @@ using namespace shell;
return @{ @"succes": @(success) };
}
- (void)setSystemChromePreferredOrientatations:(NSArray*)orientations {
- (void)setSystemChromePreferredOrientations:(NSArray*)orientations {
UIInterfaceOrientationMask mask = 0;
if (orientations.count == 0) {
......
......@@ -42,6 +42,14 @@
return _sharedInstance;
}
- (NSData*)encodeMethodCall:(FlutterMethodCall*)call {
NSMutableData* data = [NSMutableData dataWithCapacity:32];
FlutterStandardWriter* writer = [FlutterStandardWriter writerWithData:data];
[writer writeValue:call.method];
[writer writeValue:call.arguments];
return data;
}
- (NSData*)encodeSuccessEnvelope:(id)result {
NSMutableData* data = [NSMutableData dataWithCapacity:32];
FlutterStandardWriter* writer = [FlutterStandardWriter writerWithData:data];
......@@ -70,6 +78,33 @@
@"Corrupted standard method call");
return [FlutterMethodCall methodCallWithMethodName:value1 arguments:value2];
}
- (id)decodeEnvelope:(NSData*)envelope error:(FlutterError**)error {
FlutterStandardReader* reader =
[FlutterStandardReader readerWithData:envelope];
UInt8 flag = [reader readByte];
NSAssert(flag <= 1, @"Corrupted standard envelope");
id result;
switch (flag) {
case 0: {
result = [reader readValue];
NSAssert(![reader hasMore], @"Corrupted standard envelope");
}
break;
case 1: {
id code = [reader readValue];
id message = [reader readValue];
id details = [reader readValue];
NSAssert(![reader hasMore], @"Corrupted standard envelope");
NSAssert([code isKindOfClass:[NSString class]], @"Invalid standard envelope");
NSAssert(message == nil || [message isKindOfClass:[NSString class]], @"Invalid standard envelope");
*error = [FlutterError errorWithCode:code message:message details:details];
result = nil;
}
break;
}
return result;
}
@end
using namespace shell;
......
......@@ -56,6 +56,7 @@ UInt8 elementSizeForFlutterStandardDataType(FlutterStandardDataType type) {
@interface FlutterStandardReader : NSObject
+ (instancetype)readerWithData:(NSData*)data;
- (BOOL)hasMore;
- (UInt8)readByte;
- (id)readValue;
@end
......
......@@ -5,12 +5,13 @@
#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_
#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
@interface FlutterTextInputPlugin : FlutterJSONMessageListener
@interface FlutterTextInputPlugin : NSObject
@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;
-(void)handleMethodCall:(FlutterMethodCall*)call resultReceiver:(FlutterResultReceiver)resultReceiver;
@end
......
......@@ -154,30 +154,27 @@ static UIKeyboardType ToUIKeyboardType(NSString* inputType) {
[super dealloc];
}
- (NSString *)messageName {
return @"flutter/textinput";
}
- (NSDictionary*)didReceiveJSON:(NSDictionary*)message {
NSString* method = message[@"method"];
NSArray* args = message[@"args"];
if (!args)
return nil;
- (void)handleMethodCall:(FlutterMethodCall*)call resultReceiver:(FlutterResultReceiver)resultReceiver {
NSString* method = call.method;
id args = call.arguments;
if ([method isEqualToString:@"TextInput.show"]) {
[self showTextInput];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"TextInput.hide"]) {
[self hideTextInput];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"TextInput.setClient"]) {
[self setTextInputClient:[args[0] intValue] withConfiguration:args[1]];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"TextInput.setEditingState"]) {
[self setTextInputEditingState:args.firstObject];
[self setTextInputEditingState:args];
resultReceiver(nil, nil);
} else if ([method isEqualToString:@"TextInput.clearClient"]) {
[self clearTextInputClient];
resultReceiver(nil, nil);
} else {
// TODO(abarth): We should signal an error here that gets reported back to
// Dart.
resultReceiver(nil, [FlutterError errorWithCode:@"UNKNOWN" message:@"Unknown method" details: nil]);
}
return nil;
}
- (void)showTextInput {
......
......@@ -68,7 +68,12 @@ void FlutterInit(int argc, const char* argv[]) {
std::unique_ptr<shell::PlatformViewIOS> _platformView;
base::scoped_nsprotocol<FlutterPlatformPlugin*> _platformPlugin;
base::scoped_nsprotocol<FlutterTextInputPlugin*> _textInputPlugin;
base::scoped_nsprotocol<FlutterMethodChannel*> _localizationChannel;
base::scoped_nsprotocol<FlutterMethodChannel*> _navigationChannel;
base::scoped_nsprotocol<FlutterMethodChannel*> _platformChannel;
base::scoped_nsprotocol<FlutterMethodChannel*> _textInputChannel;
base::scoped_nsprotocol<FlutterMessageChannel*> _lifecycleChannel;
base::scoped_nsprotocol<FlutterMessageChannel*> _systemChannel;
BOOL _initialized;
}
......@@ -118,15 +123,49 @@ void FlutterInit(int argc, const char* argv[]) {
_orientationPreferences = UIInterfaceOrientationMaskAll;
_statusBarStyle = UIStatusBarStyleDefault;
_platformView = std::make_unique<shell::PlatformViewIOS>(
reinterpret_cast<CAEAGLLayer*>(self.view.layer));
reinterpret_cast<CAEAGLLayer*>(self.view.layer));
_platformView->SetupResourceContextOnIOThread();
_localizationChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/localization"
binaryMessenger:self
codec:[FlutterJSONMethodCodec sharedInstance]]);
_navigationChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/navigation"
binaryMessenger:self
codec:[FlutterJSONMethodCodec sharedInstance]]);
_platformChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/platform"
binaryMessenger:self
codec:[FlutterJSONMethodCodec sharedInstance]]);
_textInputChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/textinput"
binaryMessenger:self
codec:[FlutterJSONMethodCodec sharedInstance]]);
_lifecycleChannel.reset([[FlutterMessageChannel alloc]
initWithName:@"flutter/lifecycle"
binaryMessenger:self
codec:[FlutterStringCodec sharedInstance]]);
_systemChannel.reset([[FlutterMessageChannel alloc]
initWithName:@"flutter/system"
binaryMessenger:self
codec:[FlutterJSONMessageCodec sharedInstance]]);
_platformPlugin.reset([[FlutterPlatformPlugin alloc] init]);
[self addMessageListener:_platformPlugin.get()];
[_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResultReceiver resultReceiver) {
[_platformPlugin.get() handleMethodCall:call resultReceiver:resultReceiver];
}];
_textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
_textInputPlugin.get().textInputDelegate = self;
[self addMessageListener:_textInputPlugin.get()];
[_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResultReceiver resultReceiver) {
[_textInputPlugin.get() handleMethodCall:call resultReceiver:resultReceiver];
}];
[self setupNotificationCenterObservers];
......@@ -227,13 +266,11 @@ void FlutterInit(int argc, const char* argv[]) {
#pragma mark - Application lifecycle notifications
- (void)applicationBecameActive:(NSNotification*)notification {
[self sendString:@"AppLifecycleState.resumed"
withMessageName:@"flutter/lifecycle"];
[_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];
}
- (void)applicationWillResignActive:(NSNotification*)notification {
[self sendString:@"AppLifecycleState.paused"
withMessageName:@"flutter/lifecycle"];
[_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
}
#pragma mark - Touch event handling
......@@ -392,11 +429,7 @@ static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
#pragma mark - Text input delegate
- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
NSDictionary* message = @{
@"method" : @"TextInputClient.updateEditingState",
@"args" : @[ @(client), state ],
};
[self sendJSON:message withMessageName:@"flutter/textinputclient"];
[_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState" arguments:@[ @(client), state ]];
}
#pragma mark - Orientation updates
......@@ -446,8 +479,7 @@ static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
#pragma mark - Memory Notifications
- (void)onMemoryWarning:(NSNotification*)notification {
NSDictionary* message = @{ @"type" : @"memoryPressure" };
[self sendJSON:message withMessageName:@"flutter/system"];
[_systemChannel.get() sendMessage:@{ @"type" : @"memoryPressure" }];
}
#pragma mark - Locale updates
......@@ -456,10 +488,7 @@ static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
NSLocale* currentLocale = [NSLocale currentLocale];
NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
NSDictionary* message =
@{ @"method" : @"setLocale",
@"args" : @[ languageCode, countryCode ] };
[self sendJSON:message withMessageName:@"flutter/localization"];
[_localizationChannel.get() invokeMethod:@"setLocale" arguments: @[ languageCode, countryCode ]];
}
#pragma mark - Surface creation and teardown updates
......@@ -549,79 +578,6 @@ constexpr CGFloat kStandardStatusBarHeight = 20.0;
#pragma mark - Application Messages
- (void)sendString:(NSString*)message withMessageName:(NSString*)channel {
NSAssert(message, @"The message must not be null");
NSAssert(channel, @"The channel must not be null");
FlutterStringCodec* codec = [FlutterStringCodec sharedInstance];
[self sendBinaryMessage:[codec encode:message] channelName:channel];
}
- (void)sendString:(NSString*)message
withMessageName:(NSString*)channel
callback:(void (^)(NSString*))callback {
NSAssert(message, @"The message must not be null");
NSAssert(channel, @"The channel must not be null");
NSAssert(callback, @"The callback must not be null");
FlutterStringCodec* codec = [FlutterStringCodec sharedInstance];
[self sendBinaryMessage:[codec encode:message]
channelName:channel
binaryReplyHandler:^(NSData* data) {
callback([codec decode:data]);
}];
}
- (void)sendJSON:(NSDictionary*)message withMessageName:(NSString*)channel {
NSData* data = [[FlutterJSONMessageCodec sharedInstance] encode:message];
[self sendBinaryMessage:data channelName:channel];
}
- (void)addMessageListener:(NSObject<FlutterMessageListener>*)listener {
NSAssert(listener, @"The listener must not be null");
NSString* messageName = listener.messageName;
NSAssert(messageName, @"The messageName must not be null");
FlutterStringCodec* codec = [FlutterStringCodec sharedInstance];
[self
setBinaryMessageHandlerOnChannel:messageName
binaryMessageHandler:^(
NSData* message, FlutterBinaryReplyHandler replyHandler) {
NSString* reply =
[listener didReceiveString:[codec decode:message]];
replyHandler([codec encode:reply]);
}];
}
- (void)removeMessageListener:(NSObject<FlutterMessageListener>*)listener {
NSAssert(listener, @"The listener must not be null");
NSString* messageName = listener.messageName;
NSAssert(messageName, @"The messageName must not be null");
[self setBinaryMessageHandlerOnChannel:messageName binaryMessageHandler:nil];
}
- (void)addAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener {
NSAssert(listener, @"The listener must not be null");
NSString* messageName = listener.messageName;
NSAssert(messageName, @"The messageName must not be null");
FlutterStringCodec* codec = [FlutterStringCodec sharedInstance];
[self
setBinaryMessageHandlerOnChannel:messageName
binaryMessageHandler:^(
NSData* message, FlutterBinaryReplyHandler replyHandler) {
[listener didReceiveString:[codec decode:message]
callback:^(NSString* reply) {
replyHandler([codec encode:reply]);
}];
}];
}
- (void)removeAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener {
NSAssert(listener, @"The listener must not be null");
NSString* messageName = listener.messageName;
NSAssert(messageName, @"The messageName must not be null");
[self setBinaryMessageHandlerOnChannel:messageName binaryMessageHandler:nil];
}
- (void)sendBinaryMessage:(NSData*)message channelName:(NSString*)channel {
NSAssert(message, @"The message must not be null");
NSAssert(channel, @"The channel must not be null");
......
......@@ -1781,7 +1781,6 @@ FILE: ../../../flutter/shell/platform/android/android_surface_gl.h
FILE: ../../../flutter/shell/platform/android/android_surface_vulkan.cc
FILE: ../../../flutter/shell/platform/android/android_surface_vulkan.h
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/JSONMessageListener.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/PlatformPlugin.java
......@@ -1794,17 +1793,13 @@ FILE: ../../../flutter/shell/platform/darwin/common/process_info_mac.h
FILE: ../../../flutter/shell/platform/darwin/desktop/vsync_waiter_mac.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterAsyncMessageListener.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterDartProject.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterMessageListener.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartSource.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartSource.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h
......@@ -1915,7 +1910,8 @@ FILE: ../../../flutter/fml/platform/darwin/cf_utils.cc
FILE: ../../../flutter/fml/platform/darwin/cf_utils.h
FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_software.h
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryMessageCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterException.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterMessageChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/JSONMessageCodec.java
......@@ -1924,7 +1920,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/MethodCal
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/MethodCodec.java
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/StringMessageCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StringCodec.java
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册