diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 230ccba489892dec17e72dd054cd57f6acc2e791..d51bc62b13a2ab4fa6b40f870f5489f28f27fdd2 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -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", diff --git a/shell/platform/android/io/flutter/plugin/common/BinaryMessageCodec.java b/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java similarity index 76% rename from shell/platform/android/io/flutter/plugin/common/BinaryMessageCodec.java rename to shell/platform/android/io/flutter/plugin/common/BinaryCodec.java index d4d192691638de775d46365176d4d314f565224f..563f91c4347b1c2285e0d26c3238202ef2f2f352 100644 --- a/shell/platform/android/io/flutter/plugin/common/BinaryMessageCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/BinaryCodec.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 { +public final class BinaryCodec implements MessageCodec { // 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 diff --git a/shell/platform/android/io/flutter/plugin/common/FlutterException.java b/shell/platform/android/io/flutter/plugin/common/FlutterException.java new file mode 100644 index 0000000000000000000000000000000000000000..72d1b9029d9010350c0bd82b5a38bfc1931d57f0 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/common/FlutterException.java @@ -0,0 +1,20 @@ +// 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; + } +} diff --git a/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java b/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java index 5cadc0f2ef67a9aa530400054ec968f69b745ac2..6928f0c00e91a55df985055ad90b389436f9176a 100644 --- a/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java @@ -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)); } } } diff --git a/shell/platform/android/io/flutter/plugin/common/JSONMessageCodec.java b/shell/platform/android/io/flutter/plugin/common/JSONMessageCodec.java index ba29f411167d8c64e5da12f0ba6bfed8fdf9eff4..d3f1d0566b4b1d1ea99b808400a161cd2d36b3f7 100644 --- a/shell/platform/android/io/flutter/plugin/common/JSONMessageCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/JSONMessageCodec.java @@ -26,7 +26,7 @@ public final class JSONMessageCodec implements MessageCodec { 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 { 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()) { diff --git a/shell/platform/android/io/flutter/plugin/common/JSONMessageListener.java b/shell/platform/android/io/flutter/plugin/common/JSONMessageListener.java deleted file mode 100644 index 7dcddf09d02fc9d782bdb902354ccde6946e33a1..0000000000000000000000000000000000000000 --- a/shell/platform/android/io/flutter/plugin/common/JSONMessageListener.java +++ /dev/null @@ -1,37 +0,0 @@ -// 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); - } -} diff --git a/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java b/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java index 0cb88836db5f329e17ecbce6a3d55455f4942465..bdc1ce7d4b70c0b7cc725246ebce3cb4717c9772 100644 --- a/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java @@ -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))); - } } diff --git a/shell/platform/android/io/flutter/plugin/common/MethodCall.java b/shell/platform/android/io/flutter/plugin/common/MethodCall.java index 3643bb5e4553176bd8486dc8931dfcc329c86e6a..6bf49135cc637dacffbcd1476d9fff5408ad3b16 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodCall.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodCall.java @@ -4,8 +4,6 @@ package io.flutter.plugin.common; -import java.util.Objects; - /** * Command object representing a method call on a {@link FlutterMessageChannel}. */ diff --git a/shell/platform/android/io/flutter/plugin/common/MethodCodec.java b/shell/platform/android/io/flutter/plugin/common/MethodCodec.java index fcb295dc38e051cafafc07f36bd214335b6dc2d1..d4e7307230913a4ef7958af25a581217377376a7 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodCodec.java @@ -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); } diff --git a/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java b/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java index ea4277a7e6fce64c7664dc0d0654e5b513102fd9..a6a1029510ceb363ff59f8004041d8256907249e 100644 --- a/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java @@ -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"); + } } diff --git a/shell/platform/android/io/flutter/plugin/common/StringMessageCodec.java b/shell/platform/android/io/flutter/plugin/common/StringCodec.java similarity index 88% rename from shell/platform/android/io/flutter/plugin/common/StringMessageCodec.java rename to shell/platform/android/io/flutter/plugin/common/StringCodec.java index 0ae415c8a3b78cd37fa8e527a846345e53bc50d6..dac376ba0d43462b2549f5bf8ede268bf9c82dea 100644 --- a/shell/platform/android/io/flutter/plugin/common/StringMessageCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/StringCodec.java @@ -10,12 +10,12 @@ import java.nio.charset.Charset; /** * A {@link MessageCodec} using UTF-8 encoded String messages. */ -public final class StringMessageCodec implements MessageCodec { +public final class StringCodec implements MessageCodec { // 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 diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 932f965ccd2f67f5711f403ba639228f24e4c174..e2adba328f6ff92d8897769af32974fbb6db0a19 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -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 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; } } diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 62c8861f2cc7a3395e4e0aaa1bda6c183a185974..69fb3a9ad20bf9b1749c915ace36a9e9d71157ad 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -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 state) { + mLatestState = (JSONObject) JSONObject.wrap(state); } - + private void clearTextInputClient() { mClient = 0; } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index 3417c36486cfe21ec68826cf35381a12d5f1e281..a1a12aa1b929e3ecbc9d21719d714295fb4aa262 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -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 diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index fdb21aaaf51e1d2c1eccf3dbec55cbe22d70cb60..35bb760572d74bb7f0da916d262ce40e868e6083 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -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; diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index 0939d3b039d9c274cf74618d2b9acdea990b627e..e32fdc093628d5eedf964c6c0526a70394b9fc1f 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -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; diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 82821f8df2fa08bd720a74aeac719ea8ed0f28a7..ce63c200bc11ee5cf3c4efeed2d967e6a4de193d 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -27,38 +27,44 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeProvider; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; -import io.flutter.plugin.common.StringMessageCodec; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; + +import io.flutter.plugin.common.ActivityLifecycleListener; +import io.flutter.plugin.common.FlutterMessageChannel; +import io.flutter.plugin.common.FlutterMethodChannel; +import io.flutter.plugin.common.JSONMessageCodec; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.StringCodec; +import io.flutter.plugin.editing.TextInputPlugin; +import io.flutter.plugin.platform.PlatformPlugin; import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; +import org.json.JSONException; +import org.json.JSONObject; import java.net.URI; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; 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; - /** * An Android view containing a Flutter app. */ @JNINamespace("shell") public class FlutterView extends SurfaceView - implements AccessibilityManager.AccessibilityStateChangeListener { + implements AccessibilityManager.AccessibilityStateChangeListener { + private static final String TAG = "FlutterView"; private static final String ACTION_DISCOVER = "io.flutter.view.DISCOVER"; - class ViewportMetrics { + static final class ViewportMetrics { + float devicePixelRatio = 1.0f; int physicalWidth = 0; int physicalHeight = 0; @@ -68,15 +74,19 @@ public class FlutterView extends SurfaceView int physicalPaddingLeft = 0; } - private long mNativePlatformView; - private TextInputPlugin mTextInputPlugin; - - private HashMap mMessageListeners; + private final TextInputPlugin mTextInputPlugin; + private final Map mMessageListeners; private final SurfaceHolder.Callback mSurfaceCallback; private final ViewportMetrics mMetrics; private final AccessibilityManager mAccessibilityManager; - private BroadcastReceiver discoveryReceiver; - private List mActivityLifecycleListeners; + private final FlutterMethodChannel mFlutterLocalizationChannel; + private final FlutterMethodChannel mFlutterNavigationChannel; + private final FlutterMessageChannel mFlutterKeyEventChannel; + private final FlutterMessageChannel mFlutterLifecycleChannel; + private final FlutterMessageChannel mFlutterSystemChannel; + private final BroadcastReceiver mDiscoveryReceiver; + private final List mActivityLifecycleListeners; + private long mNativePlatformView; public FlutterView(Context context) { this(context, null); @@ -96,8 +106,10 @@ public class FlutterView extends SurfaceView int color = 0xFF000000; TypedValue typedValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true); - if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) - color = typedValue.data; + if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT + && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { + color = typedValue.data; + } // TODO(abarth): Consider letting the developer override this color. final int backgroundColor = color; @@ -122,27 +134,41 @@ public class FlutterView extends SurfaceView }; getHolder().addCallback(mSurfaceCallback); - mAccessibilityManager = (AccessibilityManager)getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - - mMessageListeners = new HashMap(); - mActivityLifecycleListeners = new ArrayList(); + mAccessibilityManager = (AccessibilityManager) getContext() + .getSystemService(Context.ACCESSIBILITY_SERVICE); + + mMessageListeners = new HashMap<>(); + mActivityLifecycleListeners = new ArrayList<>(); + + // Configure the platform plugins and flutter channels. + mFlutterLocalizationChannel = new FlutterMethodChannel(this, "flutter/localization", + JSONMethodCodec.INSTANCE); + mFlutterNavigationChannel = new FlutterMethodChannel(this, "flutter/navigation", + JSONMethodCodec.INSTANCE); + mFlutterKeyEventChannel = new FlutterMessageChannel<>(this, "flutter/keyevent", + JSONMessageCodec.INSTANCE); + mFlutterLifecycleChannel = new FlutterMessageChannel<>(this, "flutter/lifecycle", + StringCodec.INSTANCE); + mFlutterSystemChannel = new FlutterMessageChannel<>(this, "flutter/system", + JSONMessageCodec.INSTANCE); + PlatformPlugin platformPlugin = new PlatformPlugin((Activity) getContext()); + FlutterMethodChannel flutterPlatformChannel = new FlutterMethodChannel(this, + "flutter/platform", JSONMethodCodec.INSTANCE); + flutterPlatformChannel.setMethodCallHandler(platformPlugin); + addActivityLifecycleListener(platformPlugin); + mTextInputPlugin = new TextInputPlugin((Activity) getContext(), this); setLocale(getResources().getConfiguration().locale); - // Configure the platform plugin. - 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(); - context.registerReceiver(discoveryReceiver, new IntentFilter(ACTION_DISCOVER)); + mDiscoveryReceiver = new DiscoveryReceiver(); + context.registerReceiver(mDiscoveryReceiver, new IntentFilter(ACTION_DISCOVER)); + } else { + mDiscoveryReceiver = null; } } - private void encodeKeyEvent(KeyEvent event, JSONObject message) throws JSONException { + private void encodeKeyEvent(KeyEvent event, Map message) { message.put("flags", event.getFlags()); message.put("codePoint", event.getUnicodeChar()); message.put("keyCode", event.getKeyCode()); @@ -152,35 +178,29 @@ public class FlutterView extends SurfaceView @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (!isAttached()) + if (!isAttached()) { return super.onKeyUp(keyCode, event); - - try { - JSONObject message = new JSONObject(); - message.put("type", "keyup"); - message.put("keymap", "android"); - encodeKeyEvent(event, message); - sendPlatformMessage("flutter/keyevent", message.toString(), null); - } catch (JSONException e) { - Log.e(TAG, "Failed to serialize key event", e); } + + Map message = new HashMap<>(); + message.put("type", "keyup"); + message.put("keymap", "android"); + encodeKeyEvent(event, message); + mFlutterKeyEventChannel.send(message); return super.onKeyUp(keyCode, event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (!isAttached()) + if (!isAttached()) { return super.onKeyDown(keyCode, event); - - try { - JSONObject message = new JSONObject(); - message.put("type", "keydown"); - message.put("keymap", "android"); - encodeKeyEvent(event, message); - sendPlatformMessage("flutter/keyevent", message.toString(), null); - } catch (JSONException e) { - Log.e(TAG, "Failed to serialize key event", e); } + + Map message = new HashMap<>(); + message.put("type", "keydown"); + message.put("keymap", "android"); + encodeKeyEvent(event, message); + mFlutterKeyEventChannel.send(message); return super.onKeyDown(keyCode, event); } @@ -189,62 +209,33 @@ public class FlutterView extends SurfaceView } public void onPause() { - sendPlatformMessage("flutter/lifecycle", "AppLifecycleState.paused", null); + mFlutterLifecycleChannel.send("AppLifecycleState.paused"); } public void onPostResume() { - for (ActivityLifecycleListener listener : mActivityLifecycleListeners) + for (ActivityLifecycleListener listener : mActivityLifecycleListeners) { listener.onPostResume(); - - sendPlatformMessage("flutter/lifecycle", "AppLifecycleState.resumed", null); + } + mFlutterLifecycleChannel.send("AppLifecycleState.resumed"); } public void onMemoryPressure() { - try { - JSONObject message = new JSONObject(); - message.put("type", "memoryPressure"); - sendPlatformMessage("flutter/system", message.toString(), null); - } catch (JSONException e) { - Log.e(TAG, "Failed to serialize system event", e); - } + Map message = new HashMap<>(1); + message.put("type", "memoryPressure"); + mFlutterSystemChannel.send(message); } public void pushRoute(String route) { - try { - final JSONArray args = new JSONArray(); - args.put(0, route); - final JSONObject message = new JSONObject(); - message.put("method", "pushRoute"); - message.put("args", args); - sendPlatformMessage("flutter/navigation", message.toString(), null); - } catch (JSONException e) { - Log.e(TAG, "Unexpected JSONException pushing route", e); - } + mFlutterNavigationChannel.invokeMethod("pushRoute", route); } public void popRoute() { - try { - final JSONObject message = new JSONObject(); - message.put("method", "popRoute"); - message.put("args", new JSONArray()); - sendPlatformMessage("flutter/navigation", message.toString(), null); - } catch (JSONException e) { - Log.e(TAG, "Unexpected JSONException pushing route", e); - } + mFlutterNavigationChannel.invokeMethod("popRoute", null); } private void setLocale(Locale locale) { - try { - final JSONArray args = new JSONArray(); - args.put(0, locale.getLanguage()); - args.put(1, locale.getCountry()); - final JSONObject message = new JSONObject(); - message.put("method", "setLocale"); - message.put("args", args); - sendPlatformMessage("flutter/localization", message.toString(), null); - } catch (JSONException e) { - Log.e(TAG, "Unexpected JSONException pushing route", e); - } + mFlutterLocalizationChannel.invokeMethod("setLocale", + Arrays.asList(locale.getLanguage(), locale.getCountry())); } @Override @@ -258,8 +249,8 @@ public class FlutterView extends SurfaceView } public void destroy() { - if (discoveryReceiver != null) { - getContext().unregisterReceiver(discoveryReceiver); + if (mDiscoveryReceiver != null) { + getContext().unregisterReceiver(mDiscoveryReceiver); } getHolder().removeCallback(mSurfaceCallback); @@ -269,7 +260,12 @@ public class FlutterView extends SurfaceView @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - return mTextInputPlugin.createInputConnection(this, outAttrs); + try { + return mTextInputPlugin.createInputConnection(this, outAttrs); + } catch (JSONException e) { + Log.e(TAG, "Failed to create input connection", e); + return null; + } } // Must match the PointerChange enum in pointer.dart. @@ -327,7 +323,7 @@ public class FlutterView extends SurfaceView } private void addPointerForIndex(MotionEvent event, int pointerIndex, - ByteBuffer packet) { + ByteBuffer packet) { int pointerChange = getPointerChangeForAction(event.getActionMasked()); if (pointerChange == -1) { return; @@ -348,27 +344,28 @@ public class FlutterView extends SurfaceView packet.putDouble(event.getY(pointerIndex)); // physical_y if (pointerKind == kPointerDeviceKindMouse) { - packet.putLong(event.getButtonState() & 0x1F); // buttons + packet.putLong(event.getButtonState() & 0x1F); // buttons } else if (pointerKind == kPointerDeviceKindStylus) { - packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons + packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons } else { - packet.putLong(0); // buttons + packet.putLong(0); // buttons } packet.putLong(0); // obscured // TODO(eseidel): Could get the calibrated range if necessary: // event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE) - packet.putDouble(event.getPressure(pointerIndex)); // presure + packet.putDouble(event.getPressure(pointerIndex)); // pressure packet.putDouble(0.0); // pressure_min packet.putDouble(1.0); // pressure_max if (pointerKind == kPointerDeviceKindStylus) { - packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance - packet.putDouble(0.0); // distance_max + packet + .putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance + packet.putDouble(0.0); // distance_max } else { - packet.putDouble(0.0); // distance - packet.putDouble(0.0); // distance_max + packet.putDouble(0.0); // distance + packet.putDouble(0.0); // distance_max } packet.putDouble(event.getToolMajor(pointerIndex)); // radius_major @@ -377,19 +374,21 @@ public class FlutterView extends SurfaceView packet.putDouble(0.0); // radius_min packet.putDouble(0.0); // radius_max - packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation + packet.putDouble( + event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation if (pointerKind == kPointerDeviceKindStylus) { - packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt + packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt } else { - packet.putDouble(0.0); // tilt + packet.putDouble(0.0); // tilt } } @Override public boolean onTouchEvent(MotionEvent event) { - if (!isAttached()) + if (!isAttached()) { return false; + } // TODO(abarth): This version check might not be effective in some // versions of Android that statically compile code and will be upset @@ -406,16 +405,17 @@ public class FlutterView extends SurfaceView int pointerCount = event.getPointerCount(); - ByteBuffer packet = ByteBuffer.allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField); + ByteBuffer packet = ByteBuffer + .allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField); packet.order(ByteOrder.LITTLE_ENDIAN); int maskedAction = event.getActionMasked(); // ACTION_UP, ACTION_POINTER_UP, ACTION_DOWN, and ACTION_POINTER_DOWN // only apply to a single pointer, other events apply to all pointers. if (maskedAction == MotionEvent.ACTION_UP - || maskedAction == MotionEvent.ACTION_POINTER_UP - || maskedAction == MotionEvent.ACTION_DOWN - || maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + || maskedAction == MotionEvent.ACTION_POINTER_UP + || maskedAction == MotionEvent.ACTION_DOWN + || maskedAction == MotionEvent.ACTION_POINTER_DOWN) { addPointerForIndex(event, event.getActionIndex(), packet); } else { // ACTION_MOVE may not actually mean all pointers have moved @@ -433,8 +433,9 @@ public class FlutterView extends SurfaceView @Override public boolean onHoverEvent(MotionEvent event) { - if (!isAttached()) + if (!isAttached()) { return false; + } boolean handled = handleAccessibilityHoverEvent(event); if (!handled) { @@ -499,8 +500,8 @@ public class FlutterView extends SurfaceView } private void runFromSource(final String assetsDirectory, - final String main, - final String packages) { + final String main, + final String packages) { Runnable runnable = new Runnable() { public void run() { preRun(); @@ -520,12 +521,13 @@ public class FlutterView extends SurfaceView } } catch (InterruptedException e) { Log.e(TAG, "Thread got interrupted waiting for " + - "RunFromSourceRunnable to finish", e); + "RunFromSourceRunnable to finish", e); } } /** * Return the most recent frame as a bitmap. + * * @return A bitmap. */ public Bitmap getBitmap() { @@ -533,52 +535,67 @@ public class FlutterView extends SurfaceView } private static native long nativeAttach(FlutterView view); + private static native String nativeGetObservatoryUri(); + private static native void nativeDetach(long nativePlatformViewAndroid); + private static native void nativeSurfaceCreated(long nativePlatformViewAndroid, - Surface surface, - int backgroundColor); + Surface surface, + int backgroundColor); + private static native void nativeSurfaceChanged(long nativePlatformViewAndroid, - int width, - int height); + int width, + int height); + private static native void nativeSurfaceDestroyed(long nativePlatformViewAndroid); private static native void nativeRunBundleAndSnapshot(long nativePlatformViewAndroid, - String bundlePath, - String snapshotOverride); + String bundlePath, + String snapshotOverride); + private static native void nativeRunBundleAndSource(long nativePlatformViewAndroid, - String bundlePath, - String main, - String packages); - - private static native void nativeSetViewportMetrics(long nativePlatformViewAndroid, - float devicePixelRatio, - int physicalWidth, - int physicalHeight, - int physicalPaddingTop, - int physicalPaddingRight, - int physicalPaddingBottom, - int physicalPaddingLeft); + String bundlePath, + String main, + String packages); + + private static native void nativeSetViewportMetrics(long nativePlatformViewAndroid, + float devicePixelRatio, + int physicalWidth, + int physicalHeight, + int physicalPaddingTop, + int physicalPaddingRight, + int physicalPaddingBottom, + int physicalPaddingLeft); + private static native Bitmap nativeGetBitmap(long nativePlatformViewAndroid); // Send a platform message to Dart. - private static native void nativeDispatchPlatformMessage(long nativePlatformViewAndroid, String channel, ByteBuffer message, int position, int responseId); - private static native void nativeDispatchPointerDataPacket(long nativePlatformViewAndroid, ByteBuffer buffer, int position); - private static native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, int id, int action); - private static native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, boolean enabled); + private static native void nativeDispatchPlatformMessage(long nativePlatformViewAndroid, + String channel, ByteBuffer message, int position, int responseId); + + private static native void nativeDispatchPointerDataPacket(long nativePlatformViewAndroid, + ByteBuffer buffer, int position); + + private static native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, int id, + int action); + + private static native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, + boolean enabled); // Send a response to a platform message received from Dart. - private static native void nativeInvokePlatformMessageResponseCallback(long nativePlatformViewAndroid, int responseId, ByteBuffer message, int position); + private static native void nativeInvokePlatformMessageResponseCallback( + long nativePlatformViewAndroid, int responseId, ByteBuffer message, int position); private void updateViewportMetrics() { nativeSetViewportMetrics(mNativePlatformView, - mMetrics.devicePixelRatio, - mMetrics.physicalWidth, - mMetrics.physicalHeight, - mMetrics.physicalPaddingTop, - mMetrics.physicalPaddingRight, - mMetrics.physicalPaddingBottom, - mMetrics.physicalPaddingLeft); + mMetrics.devicePixelRatio, + mMetrics.physicalWidth, + mMetrics.physicalHeight, + mMetrics.physicalPaddingTop, + mMetrics.physicalPaddingRight, + mMetrics.physicalPaddingBottom, + mMetrics.physicalPaddingLeft); } // Called by native to send us a platform message. @@ -606,8 +623,7 @@ public class FlutterView extends SurfaceView } private int mNextResponseId = 1; - private final Map mPendingResponses - = new HashMap(); + private final Map mPendingResponses = new HashMap<>(); // Called by native to respond to a platform message that we sent. @CalledByNative @@ -645,13 +661,15 @@ public class FlutterView extends SurfaceView super.onAttachedToWindow(); mAccessibilityEnabled = mAccessibilityManager.isEnabled(); mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); - if (mAccessibilityEnabled || mTouchExplorationEnabled) - ensureAccessibilityEnabled(); + if (mAccessibilityEnabled || mTouchExplorationEnabled) { + ensureAccessibilityEnabled(); + } resetWillNotDraw(); mAccessibilityManager.addAccessibilityStateChangeListener(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (mTouchExplorationListener == null) + if (mTouchExplorationListener == null) { mTouchExplorationListener = new TouchExplorationListener(); + } mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationListener); } } @@ -660,8 +678,10 @@ public class FlutterView extends SurfaceView protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAccessibilityManager.removeAccessibilityStateChangeListener(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) - mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mAccessibilityManager + .removeTouchExplorationStateChangeListener(mTouchExplorationListener); + } } private void resetWillNotDraw() { @@ -683,7 +703,8 @@ public class FlutterView extends SurfaceView } class TouchExplorationListener - implements AccessibilityManager.TouchExplorationStateChangeListener { + implements AccessibilityManager.TouchExplorationStateChangeListener { + @Override public void onTouchExplorationStateChanged(boolean enabled) { if (enabled) { @@ -721,10 +742,11 @@ public class FlutterView extends SurfaceView } private boolean handleAccessibilityHoverEvent(MotionEvent event) { - if (!mTouchExplorationEnabled) + if (!mTouchExplorationEnabled) { return false; + } if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER || - event.getAction() == MotionEvent.ACTION_HOVER_MOVE) { + event.getAction() == MotionEvent.ACTION_HOVER_MOVE) { mAccessibilityNodeProvider.handleTouchExploration(event.getX(), event.getY()); } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { mAccessibilityNodeProvider.handleTouchExplorationExit(); @@ -735,58 +757,17 @@ public class FlutterView extends SurfaceView return true; } - /** - * 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. - * @param channel Name of the channel that will receive this message. - * @param message Message payload. - * @param callback Callback that receives a reply from the application. - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public void sendPlatformMessage(String channel, String message, MessageReplyCallback callback) { - sendToFlutter(channel, message, callback); - } - - /** - * Send a message to the Flutter application. The Flutter Dart code can register a - * host message handler that will receive these messages. - * @param channel Name of the channel that will receive this message. - * @param message Message payload. - * @param callback Callback that receives a reply from the application. - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public void sendToFlutter(String channel, String message, final MessageReplyCallback callback) { - sendBinaryMessage(channel, StringMessageCodec.INSTANCE.encodeMessage(message), - callback == null ? null : new BinaryMessageReplyCallback() { - @Override - public void onReply(ByteBuffer reply) { - callback.onReply(StringMessageCodec.INSTANCE.decodeMessage(reply)); - } - }); - } - - /** @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public void sendToFlutter(String channel, String message) { - sendToFlutter(channel, message, null); - } - /** * Send a binary message to the Flutter application. The Flutter Dart code can register a * platform message handler that will receive these messages. + * * @param channel Name of the channel that will receive this message. * @param message Message payload, a {@link ByteBuffer} with the message bytes between position * zero and current position, or null. * @param callback Callback that receives a reply from the Flutter application. */ - public void sendBinaryMessage(String channel, ByteBuffer message, BinaryMessageReplyCallback callback) { + public void sendBinaryMessage(String channel, ByteBuffer message, + BinaryMessageReplyCallback callback) { int responseId = 0; if (callback != null) { responseId = mNextResponseId++; @@ -796,76 +777,23 @@ public class FlutterView extends SurfaceView message == null ? 0 : message.position(), responseId); } - /** Callback invoked when the app replies to a String message sent with sendToFlutter. - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - public interface MessageReplyCallback { - void onReply(String reply); - } - - /** Callback invoked when the app replies to a binary message sent with sendBinaryMessage. */ - public interface BinaryMessageReplyCallback { - void onReply(ByteBuffer reply); - } - /** - * Register a callback to be invoked when the Flutter application sends a message - * to its host. - * @param channel Name of the channel used by the application. - * @param listener Called when messages arrive. - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. + * Callback invoked when the app replies to a binary message sent with sendBinaryMessage. */ - @Deprecated - public void addOnMessageListener(String channel, final OnMessageListener listener) { - addOnBinaryMessageListenerAsync(channel, - listener == null ? null : new OnBinaryMessageListenerAsync() { - @Override - public void onMessage(FlutterView view, ByteBuffer message, - BinaryMessageResponse response) { - response.send(StringMessageCodec.INSTANCE.encodeMessage(listener.onMessage( - view, StringMessageCodec.INSTANCE.decodeMessage(message)))); - } - } - ); - } + public interface BinaryMessageReplyCallback { - /** - * Register a callback to be invoked when the Flutter application sends a message - * to its host. The reply to the message can be provided asynchronously. - * @param channel Name of the channel used by the application. - * @param listener Called when messages arrive. - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public void addOnMessageListenerAsync(String channel, final OnMessageListenerAsync listener) { - addOnBinaryMessageListenerAsync(channel, - listener == null ? null : new OnBinaryMessageListenerAsync() { - @Override - public void onMessage(FlutterView view, ByteBuffer message, - final BinaryMessageResponse response) { - listener.onMessage(view, StringMessageCodec.INSTANCE.decodeMessage(message), - new MessageResponse() { - @Override - public void send(String reply) { - response.send(StringMessageCodec.INSTANCE.encodeMessage(reply)); - } - } - ); - } - } - ); + void onReply(ByteBuffer reply); } /** * Register a callback to be invoked when the Flutter application sends a message * to its host. The reply to the message can be provided asynchronously. + * * @param channel Name of the channel used by the application. * @param listener Called when messages arrive. */ - public void addOnBinaryMessageListenerAsync(String channel, OnBinaryMessageListenerAsync listener) { + public void addOnBinaryMessageListenerAsync(String channel, + OnBinaryMessageListenerAsync listener) { if (listener == null) { mMessageListeners.remove(channel); } else { @@ -873,39 +801,11 @@ public class FlutterView extends SurfaceView } } - /** - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public interface OnMessageListener { - /** - * Called when a message is received from the Flutter app. - * @param view The Flutter view hosting the app. - * @param message Message payload. - * @return the reply to the message (can be null) - */ - String onMessage(FlutterView view, String message); - } - - /** - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public interface OnMessageListenerAsync { - /** - * Called when a message is received from the Flutter app. - * @param view The Flutter view hosting the app. - * @param message Message payload. - * @param response Used to send a reply back to the app. - */ - void onMessage(FlutterView view, String message, MessageResponse response); - } - public interface OnBinaryMessageListenerAsync { + /** * Called when a message is received from the Flutter app. + * * @param view The Flutter view hosting the app. * @param message Message payload. * @param response Used to send a reply back to the app. @@ -913,21 +813,16 @@ public class FlutterView extends SurfaceView void onMessage(FlutterView view, ByteBuffer message, BinaryMessageResponse response); } - /** - * @deprecated Use {@link io.flutter.plugin.common.FlutterMessageChannel} - * with {@link StringMessageCodec} instead. - */ - @Deprecated - public interface MessageResponse { - void send(String reply); - } - public interface BinaryMessageResponse { + void send(ByteBuffer reply); } - /** Broadcast receiver used to discover active Flutter instances. */ + /** + * Broadcast receiver used to discover active Flutter instances. + */ private class DiscoveryReceiver extends BroadcastReceiver { + @Override public void onReceive(Context context, Intent intent) { URI observatoryUri = URI.create(nativeGetObservatoryUri()); @@ -936,7 +831,8 @@ public class FlutterView extends SurfaceView discover.put("id", getContext().getPackageName()); discover.put("observatoryPort", observatoryUri.getPort()); Log.i(TAG, "DISCOVER: " + discover); - } catch (JSONException e) {} + } catch (JSONException e) { + } } } } diff --git a/shell/platform/android/io/flutter/view/ResourceCleaner.java b/shell/platform/android/io/flutter/view/ResourceCleaner.java index c7d8ff40889367f3acef03168cddb011c0607456..7378a04d2d71926c617e2566b561e0e33703fe93 100644 --- a/shell/platform/android/io/flutter/view/ResourceCleaner.java +++ b/shell/platform/android/io/flutter/view/ResourceCleaner.java @@ -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. diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 1d5bbc07730bf2278772eb6eea7c24d5f3170ec4..2b410767159daaf6b662b55e03ef7a380937350e 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -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 = [ diff --git a/shell/platform/darwin/ios/framework/Headers/Flutter.h b/shell/platform/darwin/ios/framework/Headers/Flutter.h index 14874622012a56fab02143d1d7e8e980287e1e03..e75c38f27f863fb95ee21c2593feeb8291cfbd0c 100644 --- a/shell/platform/darwin/ios/framework/Headers/Flutter.h +++ b/shell/platform/darwin/ios/framework/Headers/Flutter.h @@ -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_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterAsyncMessageListener.h b/shell/platform/darwin/ios/framework/Headers/FlutterAsyncMessageListener.h deleted file mode 100644 index dd3cc146622f8966b223dd49df931769f87272f7..0000000000000000000000000000000000000000 --- a/shell/platform/darwin/ios/framework/Headers/FlutterAsyncMessageListener.h +++ /dev/null @@ -1,22 +0,0 @@ -// 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 - -#include "FlutterMacros.h" - -FLUTTER_EXPORT -@protocol FlutterAsyncMessageListener - -- (void)didReceiveString:(NSString*)message - callback:(void(^)(NSString*))sendResponse; - -@property(readonly, strong, nonatomic) NSString* messageName; - -@end - -#endif // FLUTTER_FLUTTERASYNCMESSAGELISTENER_H_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h b/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h index 4398be4ff8d479bd6e727371c195f048c9b107f0..22e3c3261de02bb8bf1f9cf70055cb11f9103beb 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h @@ -17,6 +17,10 @@ FLUTTER_EXPORT + (instancetype)messageChannelNamed:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec; +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)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*)messenger codec:(NSObject*)codec; +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)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 diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h b/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h index d5f2c461f0c1c7d5642432ebf62f0bc1ba7b2582..2ac6359a581fa1eaacffe4d660e269fef2cbfd32 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h @@ -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 diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h b/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h deleted file mode 100644 index 557ed89e850fd98eb26c31cb2a8a75ce347dc8b6..0000000000000000000000000000000000000000 --- a/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h +++ /dev/null @@ -1,17 +0,0 @@ -// 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 - -- (NSDictionary*)didReceiveJSON:(NSDictionary*)message; - -@end - -#endif // FLUTTER_FLUTTERJSONMESSAGELISTENER_H_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterMessageListener.h b/shell/platform/darwin/ios/framework/Headers/FlutterMessageListener.h deleted file mode 100644 index bbf344f2294c4c7b1d1bb6986e50d1a040afd4ae..0000000000000000000000000000000000000000 --- a/shell/platform/darwin/ios/framework/Headers/FlutterMessageListener.h +++ /dev/null @@ -1,21 +0,0 @@ -// 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 - -#include "FlutterMacros.h" - -FLUTTER_EXPORT -@protocol FlutterMessageListener - -- (NSString*)didReceiveString:(NSString*)message; - -@property(readonly, strong, nonatomic) NSString* messageName; - -@end - -#endif // FLUTTER_FLUTTERMESSAGELISTENER_H_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index c539da86f2d8be6d0ed636479eb59c49527a012e..996c9078ce83b28efa4ef0ea58f1aa4655cedc01 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -8,11 +8,9 @@ #import #include -#include "FlutterAsyncMessageListener.h" #include "FlutterBinaryMessenger.h" #include "FlutterDartProject.h" #include "FlutterMacros.h" -#include "FlutterMessageListener.h" FLUTTER_EXPORT @interface FlutterViewController : UIViewController @@ -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*)listener; - -- (void)removeMessageListener:(NSObject*)listener; - -- (void)addAsyncMessageListener: - (NSObject*)listener; - -- (void)removeAsyncMessageListener: - (NSObject*)listener; - - (void)handleStatusBarTouches:(UIEvent*)event; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm b/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm index ba90cd162a3cc1a4603946e29e75179bc2907a37..0f6362861e23de84694e94b756a3a4d2c7116355 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm @@ -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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm b/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm index 0026e58af5dc0f072247b071ba1b9bbf2fa5bbee..6e0af54ee3149ac07fe845075f7511aed16e0bd3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm @@ -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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm b/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm deleted file mode 100644 index 431aeb8e1fd2f1ca30c2dacf6383ae35f5522761..0000000000000000000000000000000000000000 --- a/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm +++ /dev/null @@ -1,34 +0,0 @@ -// 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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h index 9af3eccc4309d0c721bab84386681d6c512db4cb..72c93ab1f37d70dc0bd08a46dce560a1e5039214 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h @@ -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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 9e968c7ac117c20687fc39baca6a274561707a4e..187cbf7c4ee5947dd95eb0228b3c508085af83d8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -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) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm b/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm index 0e1eb93bcc35620f3021a40e8cc3d2398a1aa2ee..d110251cea2ee0fb13b2088fca2bd7424e82f2a6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm @@ -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; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec_Internal.h index d4d56b8992253f720dc52a85c26eeca5a49507b2..a49aec5a400e79444d51331b04c0a9f36b1cd100 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec_Internal.h @@ -56,6 +56,7 @@ UInt8 elementSizeForFlutterStandardDataType(FlutterStandardDataType type) { @interface FlutterStandardReader : NSObject + (instancetype)readerWithData:(NSData*)data; - (BOOL)hasMore; +- (UInt8)readByte; - (id)readValue; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 337c28d95242ad78136822e5ee0482a5fc4f93ac..5c1096eb1be07a85ad81712700e3641efde3372d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -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 textInputDelegate; +-(void)handleMethodCall:(FlutterMethodCall*)call resultReceiver:(FlutterResultReceiver)resultReceiver; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 55fadf58ce2dc5c3eac39acea2105cf34ecd5814..76d7fa5e00ca984aa4e4f2d7f1e25b322b7358c5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -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 { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 55751db9e49249ecb78cb710ad49c084f00b0ef2..9e163da5895f41482c32937b717ba9bb0550c061 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -68,7 +68,12 @@ void FlutterInit(int argc, const char* argv[]) { std::unique_ptr _platformView; base::scoped_nsprotocol _platformPlugin; base::scoped_nsprotocol _textInputPlugin; - + base::scoped_nsprotocol _localizationChannel; + base::scoped_nsprotocol _navigationChannel; + base::scoped_nsprotocol _platformChannel; + base::scoped_nsprotocol _textInputChannel; + base::scoped_nsprotocol _lifecycleChannel; + base::scoped_nsprotocol _systemChannel; BOOL _initialized; } @@ -118,15 +123,49 @@ void FlutterInit(int argc, const char* argv[]) { _orientationPreferences = UIInterfaceOrientationMaskAll; _statusBarStyle = UIStatusBarStyleDefault; _platformView = std::make_unique( - reinterpret_cast(self.view.layer)); + reinterpret_cast(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*)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*)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*)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*)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"); diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 6b8764f5d816befb329106dfe22f32e4c4202a65..bfdcece5b15db0138648ea2e128723a9e047c266 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -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