Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Android] Expose channel buffer resize and overflow calls #44434

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Arrays;

/**
* A named channel for communicating with the Flutter application using basic, asynchronous message
Expand Down Expand Up @@ -143,13 +142,56 @@ public void resizeChannelBuffer(int newSize) {
resizeChannelBuffer(messenger, name, newSize);
}

static void resizeChannelBuffer(
@NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) {
Charset charset = Charset.forName("UTF-8");
String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize);
final byte[] bytes = messageString.getBytes(charset);
/**
* Toggles whether the channel should show warning messages when discarding messages due to
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
* not be shown.
*/
public void allowChannelBufferOverflow(boolean allowed) {
allowChannelBufferOverflow(messenger, name, allowed);
}

private static ByteBuffer packetFromEncodedMessage(ByteBuffer message) {
// Create a bytes array using the buffer content (messages.array() can not be used here).
message.flip();
final byte[] bytes = new byte[message.remaining()];
message.get(bytes);

// The current Android Java/JNI platform message implementation assumes
// that all buffers passed to native are direct buffers.
ByteBuffer packet = ByteBuffer.allocateDirect(bytes.length);
packet.put(bytes);

return packet;
}

/**
* Adjusts the number of messages that will get buffered when sending messages to channels that
* aren't fully set up yet. For example, the engine isn't running yet or the channel's message
* handler isn't set up on the Dart side yet.
*/
public static void resizeChannelBuffer(
@NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) {
final StandardMethodCodec codec = StandardMethodCodec.INSTANCE;
Object[] arguments = {channel, newSize};
MethodCall methodCall = new MethodCall("resize", Arrays.asList(arguments));
ByteBuffer message = codec.encodeMethodCall(methodCall);
ByteBuffer packet = packetFromEncodedMessage(message);
messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, packet);
}

/**
* Toggles whether the channel should show warning messages when discarding messages due to
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
* not be shown.
*/
public static void allowChannelBufferOverflow(
@NonNull BinaryMessenger messenger, @NonNull String channel, boolean allowed) {
final StandardMethodCodec codec = StandardMethodCodec.INSTANCE;
Object[] arguments = {channel, allowed};
MethodCall methodCall = new MethodCall("overflow", Arrays.asList(arguments));
ByteBuffer message = codec.encodeMethodCall(methodCall);
ByteBuffer packet = packetFromEncodedMessage(message);
messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, packet);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ public void resizeChannelBuffer(int newSize) {
BasicMessageChannel.resizeChannelBuffer(messenger, name, newSize);
}

/**
* Toggles whether the channel should show warning messages when discarding messages due to
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
* not be shown.
*/
public void allowChannelBufferOverflow(boolean allowed) {
BasicMessageChannel.allowChannelBufferOverflow(messenger, name, allowed);
}

/** A handler of incoming method calls. */
public interface MethodCallHandler {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.flutter.plugin.common;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
Expand All @@ -11,17 +12,16 @@
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.robolectric.annotation.Config;

@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4.class)
public class MethodChannelTest {
@Test
public void methodChannel_resizeChannelBuffer() {
public void resizeChannelBufferMessageIsWellformed() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
String channel = "flutter/test";
Expand All @@ -30,15 +30,68 @@ public void methodChannel_resizeChannelBuffer() {
int newSize = 3;
rawChannel.resizeChannelBuffer(newSize);

Charset charset = Charset.forName("UTF-8");
String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize);
final byte[] bytes = messageString.getBytes(charset);
ByteBuffer packet = ByteBuffer.allocateDirect(bytes.length);
packet.put(bytes);
// Created from the following Dart code:
// MethodCall methodCall = const MethodCall('resize', ['flutter/test', 3]);
// const StandardMethodCodec().encodeMethodCall(methodCall).buffer.asUint8List();
final byte[] expected = {
7, 6, 114, 101, 115, 105, 122, 101, 12, 2, 7, 12, 102, 108, 117, 116, 116, 101, 114, 47, 116,
101, 115, 116, 3, 3, 0, 0, 0
};

// Verify that DartExecutor sent the correct message to FlutterJNI.
// Verify that the correct message was sent to FlutterJNI.
ArgumentMatcher<ByteBuffer> packetMatcher =
new ByteBufferContentMatcher(ByteBuffer.wrap(expected));
verify(mockFlutterJNI, times(1))
.dispatchPlatformMessage(
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL), eq(packet), anyInt(), anyInt());
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL),
argThat(packetMatcher),
anyInt(),
anyInt());
}

@Test
public void overflowChannelBufferMessageIsWellformed() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
String channel = "flutter/test";
MethodChannel rawChannel = new MethodChannel(dartExecutor, channel);

rawChannel.allowChannelBufferOverflow(true);

// Created from the following Dart code:
// MethodCall methodCall = const MethodCall('overflow', ['flutter/test', true]);
// const StandardMethodCodec().encodeMethodCall(methodCall).buffer.asUint8List();
final byte[] expected = {
7, 8, 111, 118, 101, 114, 102, 108, 111, 119, 12, 2, 7, 12, 102, 108, 117, 116, 116, 101, 114,
47, 116, 101, 115, 116, 1
};

// Verify that the correct message was sent to FlutterJNI.
ArgumentMatcher<ByteBuffer> packetMatcher =
new ByteBufferContentMatcher(ByteBuffer.wrap(expected));
verify(mockFlutterJNI, times(1))
.dispatchPlatformMessage(
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL),
argThat(packetMatcher),
anyInt(),
anyInt());
}
}

// Custom ByteBuffer matcher which calls rewind on both buffers before calling equals.
// ByteBuffer.equals might return true when comparing byte buffers with different content if
// both have no remaining elements.
class ByteBufferContentMatcher implements ArgumentMatcher<ByteBuffer> {
private ByteBuffer expected;

public ByteBufferContentMatcher(ByteBuffer expected) {
this.expected = expected;
}

@Override
public boolean matches(ByteBuffer received) {
expected.rewind();
received.rewind();
return received.equals(expected);
}
}