diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 8dc90c946..9a786a602 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -690,6 +690,17 @@ public synchronized void unsubscribe() { eventListeners.clear(); } + /** + *
+ * Checks if {@link io.ably.lib.types.ChannelOptions#attachOnSubscribe} is true. + *
+ * Defaults to {@code true} when {@link io.ably.lib.realtime.ChannelBase#options} is null. + *Spec: TB4, RTL7g, RTL7gh, RTP6d, RTP6e
+ */ + protected boolean attachOnSubscribeEnabled() { + return options == null || options.attachOnSubscribe; + } + /** * Registers a listener for messages on this channel. * The caller supplies a listener function, which is called each time one or more messages arrives on the channel. @@ -704,7 +715,9 @@ public synchronized void unsubscribe() { public synchronized void subscribe(MessageListener listener) throws AblyException { Log.v(TAG, "subscribe(); channel = " + this.name); listeners.add(listener); - attach(); + if (attachOnSubscribeEnabled()) { + attach(); + } } /** @@ -739,7 +752,9 @@ public synchronized void unsubscribe(MessageListener listener) { public synchronized void subscribe(String name, MessageListener listener) throws AblyException { Log.v(TAG, "subscribe(); channel = " + this.name + "; event = " + name); subscribeImpl(name, listener); - attach(); + if (attachOnSubscribeEnabled()) { + attach(); + } } /** @@ -773,7 +788,9 @@ public synchronized void subscribe(String[] names, MessageListener listener) thr Log.v(TAG, "subscribe(); channel = " + this.name + "; (multiple events)"); for(String name : names) subscribeImpl(name, listener); - attach(); + if (attachOnSubscribeEnabled()) { + attach(); + } } /** diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 1ada6d092..504985e98 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -308,6 +308,12 @@ public void unsubscribe() { * @throws AblyException */ private void implicitAttachOnSubscribe(CompletionListener completionListener) throws AblyException { + if (!channel.attachOnSubscribeEnabled()) { + if (completionListener != null) { + completionListener.onSuccess(); + } + return; + } if (channel.state == ChannelState.failed) { String errorString = String.format(Locale.ROOT, "Channel %s: subscribe in FAILED channel state", channel.name); Log.v(TAG, errorString); diff --git a/lib/src/main/java/io/ably/lib/types/ChannelOptions.java b/lib/src/main/java/io/ably/lib/types/ChannelOptions.java index 29186eee9..8ee10faf3 100644 --- a/lib/src/main/java/io/ably/lib/types/ChannelOptions.java +++ b/lib/src/main/java/io/ably/lib/types/ChannelOptions.java @@ -40,6 +40,17 @@ public class ChannelOptions { */ public boolean encrypted; + /** + *+ * Determines whether calling {@link io.ably.lib.realtime.Channel#subscribe Channel.subscribe} or + * {@link io.ably.lib.realtime.Presence#subscribe Presence.subscribe} method + * should trigger an implicit attach. + *
+ *Defaults to {@code true}.
+ *Spec: TB4, RTL7g, RTL7gh, RTP6d, RTP6e
+ */ + public boolean attachOnSubscribe = true; + public boolean hasModes() { return null != modes && 0 != modes.length; } diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index f58fe832f..bdeb11921 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -393,6 +393,71 @@ public void onMessage(Message message) { } } + /** + *+ * Validates a client can subscribe to messages without implicit channel attach + * Refer Spec TB4, RTL7g, RTL7gh + *
+ * @throws AblyException + */ + @Test + public void subscribe_without_implicit_attach() { + String channelName = "subscribe_" + testParams.name; + AblyRealtime ably = null; + try { + ClientOptions opts = createOptions(testVars.keys[0].keyStr); + ably = new AblyRealtime(opts); + + /* create a channel and set attachOnSubscribe to false */ + final Channel channel = ably.channels.get(channelName); + ChannelOptions chOpts = new ChannelOptions(); + chOpts.attachOnSubscribe = false; + channel.setOptions(chOpts); + + List* Verifies that unsubscribe call with no argument removes all listeners, diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java index 9fe903675..a13fe235f 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java @@ -1624,6 +1624,68 @@ public void onPresenceMessage(PresenceMessage message) { } } + /** + *
+ * Validates a client can subscribe to presence without implicit channel attach + * Refer Spec TB4, RTP6d, RTP6e + *
+ * @throws AblyException + */ + @Test + public void presence_subscribe_without_implicit_attach() { + String ablyChannel = "subscribe_" + testParams.name; + AblyRealtime ably = null; + try { + ClientOptions option1 = createOptions(testVars.keys[0].keyStr); + option1.clientId = "client1"; + ably = new AblyRealtime(option1); + + /* create a channel and set attachOnSubscribe to false */ + final Channel channel = ably.channels.get(ablyChannel); + ChannelOptions chOpts = new ChannelOptions(); + chOpts.attachOnSubscribe = false; + channel.setOptions(chOpts); + + List* Validates a client sending multiple presence updates when the channel is in the attaching