• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package io.flutter.plugin.common;
6 
7 import android.support.annotation.UiThread;
8 import android.util.Log;
9 
10 import io.flutter.BuildConfig;
11 import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
12 import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
13 
14 import java.nio.ByteBuffer;
15 import java.util.concurrent.atomic.AtomicBoolean;
16 import java.util.concurrent.atomic.AtomicReference;
17 
18 /**
19  * A named channel for communicating with the Flutter application using asynchronous
20  * event streams.
21  *
22  * <p>Incoming requests for event stream setup are decoded from binary on receipt, and
23  * Java responses and events are encoded into binary before being transmitted back
24  * to Flutter. The {@link MethodCodec} used must be compatible with the one used by
25  * the Flutter application. This can be achieved by creating an
26  * <a href="https://docs.flutter.io/flutter/services/EventChannel-class.html">EventChannel</a>
27  * counterpart of this channel on the Dart side. The Java type of stream configuration arguments,
28  * events, and error details is {@code Object}, but only values supported by the specified
29  * {@link MethodCodec} can be used.</p>
30  *
31  * <p>The logical identity of the channel is given by its name. Identically named channels will interfere
32  * with each other's communication.</p>
33  */
34 public final class EventChannel {
35     private static final String TAG = "EventChannel#";
36 
37     private final BinaryMessenger messenger;
38     private final String name;
39     private final MethodCodec codec;
40 
41     /**
42      * Creates a new channel associated with the specified {@link BinaryMessenger}
43      * and with the specified name and the standard {@link MethodCodec}.
44      *
45      * @param messenger a {@link BinaryMessenger}.
46      * @param name a channel name String.
47      */
EventChannel(BinaryMessenger messenger, String name)48     public EventChannel(BinaryMessenger messenger, String name) {
49         this(messenger, name, StandardMethodCodec.INSTANCE);
50     }
51 
52     /**
53      * Creates a new channel associated with the specified {@link BinaryMessenger}
54      * and with the specified name and {@link MethodCodec}.
55      *
56      * @param messenger a {@link BinaryMessenger}.
57      * @param name a channel name String.
58      * @param codec a {@link MessageCodec}.
59      */
EventChannel(BinaryMessenger messenger, String name, MethodCodec codec)60     public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
61         if (BuildConfig.DEBUG) {
62             if (messenger == null) {
63                 Log.e(TAG, "Parameter messenger must not be null.");
64             }
65             if (name == null) {
66                 Log.e(TAG, "Parameter name must not be null.");
67             }
68             if (codec == null) {
69                 Log.e(TAG, "Parameter codec must not be null.");
70             }
71         }
72         this.messenger = messenger;
73         this.name = name;
74         this.codec = codec;
75     }
76 
77     /**
78      * Registers a stream handler on this channel.
79      *
80      * <p>Overrides any existing handler registration for (the name of) this channel.</p>
81      *
82      * <p>If no handler has been registered, any incoming stream setup requests will be handled
83      * silently by providing an empty stream.</p>
84      *
85      * @param handler a {@link StreamHandler}, or null to deregister.
86      */
87     @UiThread
setStreamHandler(final StreamHandler handler)88     public void setStreamHandler(final StreamHandler handler) {
89         messenger.setMessageHandler(name, handler == null ? null : new IncomingStreamRequestHandler(handler));
90     }
91 
92     /**
93      * Handler of stream setup and tear-down requests.
94      *
95      * <p>Implementations must be prepared to accept sequences of alternating calls to
96      * {@link #onListen(Object, EventSink)} and {@link #onCancel(Object)}. Implementations
97      * should ideally consume no resources when the last such call is not {@code onListen}.
98      * In typical situations, this means that the implementation should register itself
99      * with platform-specific event sources {@code onListen} and deregister again
100      * {@code onCancel}.</p>
101      */
102     public interface StreamHandler {
103         /**
104          * Handles a request to set up an event stream.
105          *
106          * <p>Any uncaught exception thrown by this method will be caught by the channel
107          * implementation and logged. An error result message will be sent back to Flutter.</p>
108          *
109          * @param arguments stream configuration arguments, possibly null.
110          * @param events an {@link EventSink} for emitting events to the Flutter receiver.
111          */
onListen(Object arguments, EventSink events)112         void onListen(Object arguments, EventSink events);
113 
114         /**
115          * Handles a request to tear down the most recently created event stream.
116          *
117          * <p>Any uncaught exception thrown by this method will be caught by the channel
118          * implementation and logged. An error result message will be sent back to Flutter.</p>
119          *
120          * <p>The channel implementation may call this method with null arguments
121          * to separate a pair of two consecutive set up requests. Such request pairs
122          * may occur during Flutter hot restart. Any uncaught exception thrown
123          * in this situation will be logged without notifying Flutter.</p>
124          *
125          * @param arguments stream configuration arguments, possibly null.
126          */
onCancel(Object arguments)127         void onCancel(Object arguments);
128     }
129 
130     /**
131      * Event callback. Supports dual use: Producers of events to be sent to Flutter
132      * act as clients of this interface for sending events. Consumers of events sent
133      * from Flutter implement this interface for handling received events (the latter
134      * facility has not been implemented yet).
135      */
136     public interface EventSink {
137         /**
138          * Consumes a successful event.
139          *
140          * @param event the event, possibly null.
141          */
success(Object event)142         void success(Object event);
143 
144         /**
145          * Consumes an error event.
146          *
147          * @param errorCode an error code String.
148          * @param errorMessage a human-readable error message String, possibly null.
149          * @param errorDetails error details, possibly null
150          */
error(String errorCode, String errorMessage, Object errorDetails)151         void error(String errorCode, String errorMessage, Object errorDetails);
152 
153         /**
154          * Consumes end of stream. Ensuing calls to {@link #success(Object)} or
155          * {@link #error(String, String, Object)}, if any, are ignored.
156          */
endOfStream()157         void endOfStream();
158     }
159 
160     private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
161         private final StreamHandler handler;
162         private final AtomicReference<EventSink> activeSink = new AtomicReference<>(null);
163 
IncomingStreamRequestHandler(StreamHandler handler)164         IncomingStreamRequestHandler(StreamHandler handler) {
165             this.handler = handler;
166         }
167 
168         @Override
onMessage(ByteBuffer message, final BinaryReply reply)169         public void onMessage(ByteBuffer message, final BinaryReply reply) {
170             final MethodCall call = codec.decodeMethodCall(message);
171             if (call.method.equals("listen")) {
172                 onListen(call.arguments, reply);
173             } else if (call.method.equals("cancel")) {
174                 onCancel(call.arguments, reply);
175             } else {
176                 reply.reply(null);
177             }
178         }
179 
onListen(Object arguments, BinaryReply callback)180         private void onListen(Object arguments, BinaryReply callback) {
181             final EventSink eventSink = new EventSinkImplementation();
182             final EventSink oldSink = activeSink.getAndSet(eventSink);
183             if (oldSink != null) {
184               // Repeated calls to onListen may happen during hot restart.
185               // We separate them with a call to onCancel.
186               try {
187                   handler.onCancel(null);
188               } catch (RuntimeException e) {
189                   Log.e(TAG + name, "Failed to close existing event stream", e);
190               }
191             }
192             try {
193                 handler.onListen(arguments, eventSink);
194                 callback.reply(codec.encodeSuccessEnvelope(null));
195             } catch (RuntimeException e) {
196                 activeSink.set(null);
197                 Log.e(TAG + name, "Failed to open event stream", e);
198                 callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
199             }
200         }
201 
onCancel(Object arguments, BinaryReply callback)202         private void onCancel(Object arguments, BinaryReply callback) {
203             final EventSink oldSink = activeSink.getAndSet(null);
204             if (oldSink != null) {
205                 try {
206                     handler.onCancel(arguments);
207                     callback.reply(codec.encodeSuccessEnvelope(null));
208                 } catch (RuntimeException e) {
209                     Log.e(TAG + name, "Failed to close event stream", e);
210                     callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
211                 }
212             } else {
213                 callback.reply(codec.encodeErrorEnvelope("error", "No active stream to cancel", null));
214             }
215         }
216 
217         private final class EventSinkImplementation implements EventSink {
218              final AtomicBoolean hasEnded = new AtomicBoolean(false);
219 
220              @Override
221              @UiThread
success(Object event)222              public void success(Object event) {
223                  if (hasEnded.get() || activeSink.get() != this) {
224                      return;
225                  }
226                  EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
227              }
228 
229              @Override
230              @UiThread
error(String errorCode, String errorMessage, Object errorDetails)231              public void error(String errorCode, String errorMessage, Object errorDetails) {
232                  if (hasEnded.get() || activeSink.get() != this) {
233                      return;
234                  }
235                  EventChannel.this.messenger.send(
236                      name,
237                      codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
238              }
239 
240              @Override
241              @UiThread
endOfStream()242              public void endOfStream() {
243                  if (hasEnded.getAndSet(true) || activeSink.get() != this) {
244                      return;
245                  }
246                  EventChannel.this.messenger.send(name, null);
247              }
248          }
249     }
250 }
251