diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java index 7b0acfae887..c1a9a51c005 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/JSONMethodCodec.java @@ -70,6 +70,17 @@ public final class JSONMethodCodec implements MethodCodec { .put(JSONUtil.wrap(errorDetails))); } + @Override + public ByteBuffer encodeErrorEnvelopeWithStacktrace( + String errorCode, String errorMessage, Object errorDetails, String errorStacktrace) { + return JSONMessageCodec.INSTANCE.encodeMessage( + new JSONArray() + .put(errorCode) + .put(JSONUtil.wrap(errorMessage)) + .put(JSONUtil.wrap(errorDetails)) + .put(JSONUtil.wrap(errorStacktrace))); + } + @Override public Object decodeEnvelope(ByteBuffer envelope) { try { diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodChannel.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodChannel.java index 288ae2f0849..81e50e3b938 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodChannel.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodChannel.java @@ -11,6 +11,9 @@ import androidx.annotation.UiThread; import io.flutter.BuildConfig; import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; import io.flutter.plugin.common.BinaryMessenger.BinaryReply; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; import java.nio.ByteBuffer; /** @@ -247,8 +250,16 @@ public class MethodChannel { }); } catch (RuntimeException e) { Log.e(TAG + name, "Failed to handle method call", e); - reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null)); + reply.reply( + codec.encodeErrorEnvelopeWithStacktrace( + "error", e.getMessage(), null, getStackTrace(e))); } } + + private String getStackTrace(Exception e) { + Writer result = new StringWriter(); + e.printStackTrace(new PrintWriter(result)); + return result.toString(); + } } } diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodCodec.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodCodec.java index fba950f9a49..f958a5307ae 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodCodec.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/MethodCodec.java @@ -55,6 +55,20 @@ public interface MethodCodec { */ ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails); + /** + * Encodes an error result into a binary envelope message with the native stacktrace. + * + * @param errorCode An error code String. + * @param errorMessage An error message String, possibly null. + * @param errorDetails Error details, possibly null. Consider supporting {@link Throwable} in your + * codec. This is the most common value passed to this field. + * @param errorStacktrace Platform stacktrace for the error. possibly null. + * @return a {@link ByteBuffer} containing the encoding between position 0 and the current + * position. + */ + ByteBuffer encodeErrorEnvelopeWithStacktrace( + String errorCode, String errorMessage, Object errorDetails, String errorStacktrace); + /** * Decodes a result envelope from binary. * diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java index 913001f5b2b..942bd0e99bf 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java @@ -79,6 +79,24 @@ public final class StandardMethodCodec implements MethodCodec { return buffer; } + @Override + public ByteBuffer encodeErrorEnvelopeWithStacktrace( + String errorCode, String errorMessage, Object errorDetails, String errorStacktrace) { + final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream(); + stream.write(1); + messageCodec.writeValue(stream, errorCode); + messageCodec.writeValue(stream, errorMessage); + if (errorDetails instanceof Throwable) { + messageCodec.writeValue(stream, getStackTrace((Throwable) errorDetails)); + } else { + messageCodec.writeValue(stream, errorDetails); + } + messageCodec.writeValue(stream, errorStacktrace); + final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size()); + buffer.put(stream.buffer(), 0, stream.size()); + return buffer; + } + @Override public Object decodeEnvelope(ByteBuffer envelope) { envelope.order(ByteOrder.nativeOrder()); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java index c99a30f5d7f..f87815f401f 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.HashMap; import java.util.Map; import org.junit.Test; @@ -89,4 +90,27 @@ public class StandardMethodCodecTest { "at io.flutter.plugin.common.StandardMethodCodecTest.encodeErrorEnvelopeWithThrowableTest(StandardMethodCodecTest.java:")); } } + + @Test + public void encodeErrorEnvelopeWithStacktraceTest() { + final Exception e = new IllegalArgumentException("foo"); + final ByteBuffer buffer = + StandardMethodCodec.INSTANCE.encodeErrorEnvelopeWithStacktrace( + "code", e.getMessage(), e, "error stacktrace"); + assertNotNull(buffer); + buffer.flip(); + buffer.order(ByteOrder.nativeOrder()); + final byte flag = buffer.get(); + final Object code = StandardMessageCodec.INSTANCE.readValue(buffer); + final Object message = StandardMessageCodec.INSTANCE.readValue(buffer); + final Object details = StandardMessageCodec.INSTANCE.readValue(buffer); + final Object stacktrace = StandardMessageCodec.INSTANCE.readValue(buffer); + assertEquals("code", (String) code); + assertEquals("foo", (String) message); + String stack = (String) details; + assertTrue( + stack.contains( + "at io.flutter.plugin.common.StandardMethodCodecTest.encodeErrorEnvelopeWithStacktraceTest(StandardMethodCodecTest.java:")); + assertEquals("error stacktrace", (String) stacktrace); + } }