2018-11-07 12:24:35 -08:00
|
|
|
// Copyright 2013 The Flutter Authors. All rights reserved.
|
2017-03-29 13:43:54 +02:00
|
|
|
// 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;
|
|
|
|
|
|
2019-05-13 13:26:31 -07:00
|
|
|
import android.support.annotation.UiThread;
|
2017-03-29 13:43:54 +02:00
|
|
|
import android.util.Log;
|
2019-05-02 17:30:19 -07:00
|
|
|
|
|
|
|
|
import io.flutter.BuildConfig;
|
2017-04-18 14:30:31 +02:00
|
|
|
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
|
|
|
|
|
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
|
2017-03-29 13:43:54 +02:00
|
|
|
|
|
|
|
|
import java.nio.ByteBuffer;
|
2017-04-19 21:53:46 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
2017-03-29 13:43:54 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A named channel for communicating with the Flutter application using asynchronous
|
|
|
|
|
* event streams.
|
|
|
|
|
*
|
2017-04-19 21:53:46 +02:00
|
|
|
* <p>Incoming requests for event stream setup are decoded from binary on receipt, and
|
2017-03-29 13:43:54 +02:00
|
|
|
* Java responses and events are encoded into binary before being transmitted back
|
|
|
|
|
* to Flutter. The {@link MethodCodec} used must be compatible with the one used by
|
2017-04-19 21:53:46 +02:00
|
|
|
* the Flutter application. This can be achieved by creating an
|
|
|
|
|
* <a href="https://docs.flutter.io/flutter/services/EventChannel-class.html">EventChannel</a>
|
|
|
|
|
* counterpart of this channel on the Dart side. The Java type of stream configuration arguments,
|
|
|
|
|
* events, and error details is {@code Object}, but only values supported by the specified
|
|
|
|
|
* {@link MethodCodec} can be used.</p>
|
2017-03-29 13:43:54 +02:00
|
|
|
*
|
2017-04-19 21:53:46 +02:00
|
|
|
* <p>The logical identity of the channel is given by its name. Identically named channels will interfere
|
|
|
|
|
* with each other's communication.</p>
|
2017-03-29 13:43:54 +02:00
|
|
|
*/
|
2017-04-18 14:30:31 +02:00
|
|
|
public final class EventChannel {
|
|
|
|
|
private static final String TAG = "EventChannel#";
|
2017-03-29 13:43:54 +02:00
|
|
|
|
2017-04-18 14:30:31 +02:00
|
|
|
private final BinaryMessenger messenger;
|
2017-03-29 13:43:54 +02:00
|
|
|
private final String name;
|
|
|
|
|
private final MethodCodec codec;
|
|
|
|
|
|
|
|
|
|
/**
|
2017-04-18 14:30:31 +02:00
|
|
|
* Creates a new channel associated with the specified {@link BinaryMessenger}
|
|
|
|
|
* and with the specified name and the standard {@link MethodCodec}.
|
2017-03-29 13:43:54 +02:00
|
|
|
*
|
2017-04-18 14:30:31 +02:00
|
|
|
* @param messenger a {@link BinaryMessenger}.
|
2017-03-29 13:43:54 +02:00
|
|
|
* @param name a channel name String.
|
|
|
|
|
*/
|
2017-04-18 14:30:31 +02:00
|
|
|
public EventChannel(BinaryMessenger messenger, String name) {
|
|
|
|
|
this(messenger, name, StandardMethodCodec.INSTANCE);
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2017-04-18 14:30:31 +02:00
|
|
|
* Creates a new channel associated with the specified {@link BinaryMessenger}
|
|
|
|
|
* and with the specified name and {@link MethodCodec}.
|
2017-03-29 13:43:54 +02:00
|
|
|
*
|
2017-04-18 14:30:31 +02:00
|
|
|
* @param messenger a {@link BinaryMessenger}.
|
2017-03-29 13:43:54 +02:00
|
|
|
* @param name a channel name String.
|
|
|
|
|
* @param codec a {@link MessageCodec}.
|
|
|
|
|
*/
|
2017-04-18 14:30:31 +02:00
|
|
|
public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
|
2019-05-02 17:30:19 -07:00
|
|
|
if (BuildConfig.DEBUG) {
|
|
|
|
|
if (messenger == null) {
|
2019-05-08 01:19:24 -07:00
|
|
|
Log.e(TAG, "Parameter messenger must not be null.");
|
2019-05-02 17:30:19 -07:00
|
|
|
}
|
|
|
|
|
if (name == null) {
|
2019-05-08 01:19:24 -07:00
|
|
|
Log.e(TAG, "Parameter name must not be null.");
|
2019-05-02 17:30:19 -07:00
|
|
|
}
|
|
|
|
|
if (codec == null) {
|
2019-05-08 01:19:24 -07:00
|
|
|
Log.e(TAG, "Parameter codec must not be null.");
|
2019-05-02 17:30:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-04-18 14:30:31 +02:00
|
|
|
this.messenger = messenger;
|
2017-03-29 13:43:54 +02:00
|
|
|
this.name = name;
|
|
|
|
|
this.codec = codec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Registers a stream handler on this channel.
|
|
|
|
|
*
|
2017-04-19 21:53:46 +02:00
|
|
|
* <p>Overrides any existing handler registration for (the name of) this channel.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>If no handler has been registered, any incoming stream setup requests will be handled
|
|
|
|
|
* silently by providing an empty stream.</p>
|
2017-03-29 13:43:54 +02:00
|
|
|
*
|
|
|
|
|
* @param handler a {@link StreamHandler}, or null to deregister.
|
|
|
|
|
*/
|
2019-05-13 13:26:31 -07:00
|
|
|
@UiThread
|
2017-03-29 13:43:54 +02:00
|
|
|
public void setStreamHandler(final StreamHandler handler) {
|
2017-04-18 14:30:31 +02:00
|
|
|
messenger.setMessageHandler(name, handler == null ? null : new IncomingStreamRequestHandler(handler));
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2017-04-18 14:30:31 +02:00
|
|
|
* Handler of stream setup and tear-down requests.
|
2017-04-19 21:53:46 +02:00
|
|
|
*
|
|
|
|
|
* <p>Implementations must be prepared to accept sequences of alternating calls to
|
|
|
|
|
* {@link #onListen(Object, EventSink)} and {@link #onCancel(Object)}. Implementations
|
|
|
|
|
* should ideally consume no resources when the last such call is not {@code onListen}.
|
|
|
|
|
* In typical situations, this means that the implementation should register itself
|
|
|
|
|
* with platform-specific event sources {@code onListen} and deregister again
|
|
|
|
|
* {@code onCancel}.</p>
|
2017-03-29 13:43:54 +02:00
|
|
|
*/
|
|
|
|
|
public interface StreamHandler {
|
|
|
|
|
/**
|
|
|
|
|
* Handles a request to set up an event stream.
|
|
|
|
|
*
|
2017-04-27 07:52:56 +02:00
|
|
|
* <p>Any uncaught exception thrown by this method will be caught by the channel
|
|
|
|
|
* implementation and logged. An error result message will be sent back to Flutter.</p>
|
2017-04-19 21:53:46 +02:00
|
|
|
*
|
|
|
|
|
* @param arguments stream configuration arguments, possibly null.
|
|
|
|
|
* @param events an {@link EventSink} for emitting events to the Flutter receiver.
|
2017-03-29 13:43:54 +02:00
|
|
|
*/
|
2017-04-18 14:30:31 +02:00
|
|
|
void onListen(Object arguments, EventSink events);
|
2017-03-29 13:43:54 +02:00
|
|
|
|
|
|
|
|
/**
|
2017-11-13 07:47:36 +01:00
|
|
|
* Handles a request to tear down the most recently created event stream.
|
2017-03-29 13:43:54 +02:00
|
|
|
*
|
2017-04-27 07:52:56 +02:00
|
|
|
* <p>Any uncaught exception thrown by this method will be caught by the channel
|
|
|
|
|
* implementation and logged. An error result message will be sent back to Flutter.</p>
|
2017-04-19 21:53:46 +02:00
|
|
|
*
|
2017-11-13 07:47:36 +01:00
|
|
|
* <p>The channel implementation may call this method with null arguments
|
|
|
|
|
* to separate a pair of two consecutive set up requests. Such request pairs
|
|
|
|
|
* may occur during Flutter hot restart. Any uncaught exception thrown
|
|
|
|
|
* in this situation will be logged without notifying Flutter.</p>
|
|
|
|
|
*
|
2017-04-19 21:53:46 +02:00
|
|
|
* @param arguments stream configuration arguments, possibly null.
|
2017-03-29 13:43:54 +02:00
|
|
|
*/
|
|
|
|
|
void onCancel(Object arguments);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 21:53:46 +02:00
|
|
|
/**
|
|
|
|
|
* Event callback. Supports dual use: Producers of events to be sent to Flutter
|
|
|
|
|
* act as clients of this interface for sending events. Consumers of events sent
|
|
|
|
|
* from Flutter implement this interface for handling received events (the latter
|
|
|
|
|
* facility has not been implemented yet).
|
|
|
|
|
*/
|
|
|
|
|
public interface EventSink {
|
|
|
|
|
/**
|
|
|
|
|
* Consumes a successful event.
|
|
|
|
|
*
|
|
|
|
|
* @param event the event, possibly null.
|
|
|
|
|
*/
|
|
|
|
|
void success(Object event);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Consumes an error event.
|
|
|
|
|
*
|
|
|
|
|
* @param errorCode an error code String.
|
|
|
|
|
* @param errorMessage a human-readable error message String, possibly null.
|
|
|
|
|
* @param errorDetails error details, possibly null
|
|
|
|
|
*/
|
|
|
|
|
void error(String errorCode, String errorMessage, Object errorDetails);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Consumes end of stream. Ensuing calls to {@link #success(Object)} or
|
|
|
|
|
* {@link #error(String, String, Object)}, if any, are ignored.
|
|
|
|
|
*/
|
|
|
|
|
void endOfStream();
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-18 14:30:31 +02:00
|
|
|
private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
|
2017-03-29 13:43:54 +02:00
|
|
|
private final StreamHandler handler;
|
|
|
|
|
private final AtomicReference<EventSink> activeSink = new AtomicReference<>(null);
|
|
|
|
|
|
2017-04-18 14:30:31 +02:00
|
|
|
IncomingStreamRequestHandler(StreamHandler handler) {
|
2017-03-29 13:43:54 +02:00
|
|
|
this.handler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2017-04-18 14:30:31 +02:00
|
|
|
public void onMessage(ByteBuffer message, final BinaryReply reply) {
|
2017-04-27 07:52:56 +02:00
|
|
|
final MethodCall call = codec.decodeMethodCall(message);
|
|
|
|
|
if (call.method.equals("listen")) {
|
|
|
|
|
onListen(call.arguments, reply);
|
|
|
|
|
} else if (call.method.equals("cancel")) {
|
|
|
|
|
onCancel(call.arguments, reply);
|
|
|
|
|
} else {
|
|
|
|
|
reply.reply(null);
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-18 14:30:31 +02:00
|
|
|
private void onListen(Object arguments, BinaryReply callback) {
|
2017-03-29 13:43:54 +02:00
|
|
|
final EventSink eventSink = new EventSinkImplementation();
|
2017-11-13 07:47:36 +01:00
|
|
|
final EventSink oldSink = activeSink.getAndSet(eventSink);
|
|
|
|
|
if (oldSink != null) {
|
|
|
|
|
// Repeated calls to onListen may happen during hot restart.
|
|
|
|
|
// We separate them with a call to onCancel.
|
|
|
|
|
try {
|
|
|
|
|
handler.onCancel(null);
|
|
|
|
|
} catch (RuntimeException e) {
|
|
|
|
|
Log.e(TAG + name, "Failed to close existing event stream", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
handler.onListen(arguments, eventSink);
|
|
|
|
|
callback.reply(codec.encodeSuccessEnvelope(null));
|
|
|
|
|
} catch (RuntimeException e) {
|
|
|
|
|
activeSink.set(null);
|
|
|
|
|
Log.e(TAG + name, "Failed to open event stream", e);
|
|
|
|
|
callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-18 14:30:31 +02:00
|
|
|
private void onCancel(Object arguments, BinaryReply callback) {
|
2017-03-29 13:43:54 +02:00
|
|
|
final EventSink oldSink = activeSink.getAndSet(null);
|
|
|
|
|
if (oldSink != null) {
|
|
|
|
|
try {
|
|
|
|
|
handler.onCancel(arguments);
|
2017-04-18 14:30:31 +02:00
|
|
|
callback.reply(codec.encodeSuccessEnvelope(null));
|
2017-04-19 21:53:46 +02:00
|
|
|
} catch (RuntimeException e) {
|
2017-03-29 13:43:54 +02:00
|
|
|
Log.e(TAG + name, "Failed to close event stream", e);
|
2017-04-27 07:52:56 +02:00
|
|
|
callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2017-04-18 14:30:31 +02:00
|
|
|
callback.reply(codec.encodeErrorEnvelope("error", "No active stream to cancel", null));
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final class EventSinkImplementation implements EventSink {
|
2017-04-19 21:53:46 +02:00
|
|
|
final AtomicBoolean hasEnded = new AtomicBoolean(false);
|
|
|
|
|
|
2017-03-29 13:43:54 +02:00
|
|
|
@Override
|
2019-05-13 13:26:31 -07:00
|
|
|
@UiThread
|
2017-03-29 13:43:54 +02:00
|
|
|
public void success(Object event) {
|
2017-04-19 21:53:46 +02:00
|
|
|
if (hasEnded.get() || activeSink.get() != this) {
|
2017-03-29 13:43:54 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2017-04-27 07:52:56 +02:00
|
|
|
EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2019-05-13 13:26:31 -07:00
|
|
|
@UiThread
|
2017-04-19 21:53:46 +02:00
|
|
|
public void error(String errorCode, String errorMessage, Object errorDetails) {
|
|
|
|
|
if (hasEnded.get() || activeSink.get() != this) {
|
2017-03-29 13:43:54 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2017-04-27 07:52:56 +02:00
|
|
|
EventChannel.this.messenger.send(
|
|
|
|
|
name,
|
|
|
|
|
codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2019-05-13 13:26:31 -07:00
|
|
|
@UiThread
|
2017-03-29 13:43:54 +02:00
|
|
|
public void endOfStream() {
|
2017-04-19 21:53:46 +02:00
|
|
|
if (hasEnded.getAndSet(true) || activeSink.get() != this) {
|
2017-03-29 13:43:54 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2017-04-18 14:30:31 +02:00
|
|
|
EventChannel.this.messenger.send(name, null);
|
2017-03-29 13:43:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|