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