Feedback

AndroidAndroid V4 Presence Tutorial for Realtime Apps

 

These docs are for PubNub 4.0 for Android which is our latest and greatest! For the docs of the older versions of the SDK, please check PubNub 3.0 for Java and PubNub 3.0 for Android.

If you have questions about the PubNub for Android SDK, please contact us at support@pubnub.com.

Requires that the Presence add-on is enabled for your key. How do I enable add-on features for my keys? - see http://www.pubnub.com/knowledge-base/discussion/644/how-do-i-enable-add-on-features-for-my-keys
PubNub's Channel Presence empowers your applications to Track online and offline status of users and devices in realtime. Before using this feature it must be enabled in the PubNub Admin Console.

Presence provides authoritative information on:
  • When a user has joined or left a channel
  • Who, and how many, users are subscribed to a particular channel
  • Which channel(s) an individual user is subscribed to
  • Associated state information for these users (lat/long, typing status, etc)
By default, a user may trigger the following presence events against the channels he is subscribed to:
  • join - A user subscribes to channel.
  • leave - A user unsubscribes from channel.
  • timeout - A timeout event is fired when a connection to a channel is severed and the subscriber has not been seen in 320 seconds (just over 5 minutes). This timeout interval can be customized using the heartbeat and heartbeat interval settings (SDK v3.6 or later).
  • state-change - A state-change event will be fired anytime the state is changed using the state API (function signature varies by SDK). A state-change event is not fired when a user subscribes with state being passed in as a parameter of the subscribe call, rather, the state is passed along as part of the join event payload.
  • interval - An occupancy count is sent every 10 seconds (the default setting for the Presence Interval property, which is also configurable in the Presence add-on panel in your account dashboard).
Presence events are always tied to a specific device, and that specific device is identified via the PubNub UUID. More info on setting UUIDs is available in the API Reference.

In addition to the default presence events of join, leave and timeout, the developer may add custom state attributes, which when modified, emit state-change events.

For example, assuming we have a device set to UUID = Stephen, when a user begins typing, you could locally set an isTyping attribute to true. This would result in a presence event of isTyping : true for user Stephen. Upon pausing, you could set the same attribute to false, resulting in a presence event of isTyping : false for user Stephen.

In addition to receiving default and custom presence events in realtime, PubNub provides the following APIs to pull specific presence details on-demand:
  • Here Now - who, and how many, users are subscribed to a particular channel
  • Where Now - which channel(s) an individual user is subscribed to
To add context to which device (connected to which user) is doing what, we'll need to assign a UUID to it. To do this, be sure you initialize your client with a UUID:
PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.setSubscribeKey("my_subkey");
pnConfiguration.setPublishKey("my_pubkey");
pnConfiguration.setUuid("Stephen");  
PubNub pubnub = new PubNub(pnConfiguration);
Now that we've set our UUID, we can enable realtime presence events along with our subscribe call:
pubnub.subscribe()
    .channels(Arrays.asList("my_channel")) // subscribe to channels
    .withPresence() // also subscribe to related presence information
    .execute();
In the above example, we subscribe to the presence channel for my_channel (under the covers, its a regular channel, called my_channel-pnpres), sending presence messages to the callback.
The resulting data received on presence channels will follow this format:
  • action - A string, which is either join, leave, timeout, or state-change
  • timestamp - A number, which is the timestamp of when the event occurred
  • uuid - a string, which correlates with the UUID set on the device
  • occupancy - a number, which represents the current occupancy of the channel
  • data - a JSON Object, which contains any custom state information
  • channel - a string, which depicts the name of the channel the event occurred on
{
	"action": "join",
	"timestamp": 1345546797,
	"uuid": "175c2c67-b2a9-470d-8f4b-1db94f90e39e",
	"occupancy": 2
}
{
    "action" : "leave",
    "timestamp" : 1345549797,
    "uuid" : "175c2c67-b2a9-470d-8f4b-1db94f90e39e",
    "occupancy" : 1
}
{
	"action": "timeout",
	"timestamp": 1345549797,
	"uuid": "76c2c571-9a2b-d074-b4f8-e93e09f49bd",
	"occupancy": 0
}
{
	"action": "state-change",
	"uuid": "76c2c571-9a2b-d074-b4f8-e93e09f49bd",
	"timestamp": 1345549797,
	"data": {
		"isTyping": true
	}
}
{
	"action":"interval",
	"timestamp":1474396578,
	"occupancy":2
}

When a channel is in interval mode with presence_deltas pnconfig flag enabled, the interval message may also include the following fields which contain an array of changed UUIDs since the last interval message.

  • joined
  • left
  • timedout

For example, this interval message indicates there were 2 new UUIDs that joined and 1 timed out UUID since the last interval:

{
    "action" : "interval",
    "occupancy" : <# users in channel>,
    "timestamp" : <unix timestamp>,
    "joined" : ["uuid2", "uuid3"],
    "timedout" : ["uuid1"]
}

If the full interval message is greater than 30KB (since the max publish payload is ∼32KB), none of the extra fields will be present. Instead there will be a here_now_refresh boolean field set to true. This indicates to the user that they should do a hereNow request to get the complete list of users present in the channel.

{
    "action" : "interval",
    "occupancy" : <# users in channel>,
    "timestamp" : <unix timestamp>,
    "here_now_refresh" : true
}
Whereas the Join,Leave,Timeout,State-Change events occur in realtime when subscribed to the presence channel, PubNub provides the ability to audit on-demand for presence-specifics with the Here Now, and Where Now commands.
To get a list of "who is here now" for a given channel, use the hereNow() method:
pubnub.hereNow()
	// tailor the next two lines to example
	.channels(Arrays.asList("coolChannel", "coolChannel2"))
	.includeUUIDs(true)
	.async(new PNCallback<PNHereNowResult>() {
		@Override
		public void onResponse(PNHereNowResult result, PNStatus status) {
			if (status.isError()) {
				// handle error
				return;
			}
			
			for (PNHereNowChannelData channelData : result.getChannels().values()) {
				System.out.println("---");
				System.out.println("channel:" + channelData.getChannelName());
				System.out.println("occupancy: " + channelData.getOccupancy());
				System.out.println("occupants:");
				for (PNHereNowOccupantData occupant : channelData.getOccupants()) {
					System.out.println("uuid: " + occupant.getUuid() + " state: " + occupant.getState());
				}
			}
		}
	});
The resulting data received from hereNow() will follow this format:
  • uuids - Array of Strings - List of UUIDs currently subscribed to the channel.
  • occupancy - Number - Total current occupancy of the channel.
{
	occupancy : 4,
	uuids : ['123123234t234f34fq3dq', '143r34f34t34fq34q34q3', '23f34d3f4rq34r34rq23q', 'w34tcw45t45tcw435tww3']
}
To get a list of everyone on every channel, just omit the channel parameter:
pubnub.hereNow()
    .includeState(true) // include state with request (false by default)
    .includeUUIDs(true) // if false, only shows occupancy count
    .async(new PNCallback<HereNowData>() {
        @Override
        public void onResponse(HereNowData result, PNStatus status) {

        }
    });
The response groups the data by channel:
{
	"status": 200,
	"message": "OK",
	"payload": {
		"channels": {
			"81d8d989-b95f-443c-a726-04fac323b331": {
				"uuids": [
					"70fc1140-22b5-4abc-85b2-ff8c17b24d59"
				],
				"occupancy": 1
			},
			"81b7a746-d153-49e2-ab70-3037a75cec7e": {
				"uuids": [
					"91363e7f-584b-49cc-822c-52c2c01e5928"
				],
				"occupancy": 1
			},
			"c068d15e-772a-4bcd-aa27-f920b7a4eaa8": {
				"uuids": [
					"ccfac1dd-b3a5-4afd-9fd9-db11aeb65395"
				],
				"occupancy": 1
			}
		},
		"total_channels": 3,
		"total_occupancy": 3
	},
	"service": "Presence"
}
To get a list of "which channel(s) is UUID on right now", use the whereNow() method:
pubnub.whereNow()
    .async(new PNCallback<PNWhereNowResult>() {
        @Override
        public void onResponse(PNWhereNowResult result, PNStatus status) {
            // returns a pojo with channels // channel groups which I am part of.  
        }
    });
The resulting data received from whereNow() will follow this format:
  • channels - Array of Strings - List of channels a uuid is subscribed to.
{
	"channels": [
		"lobby",
		"game01",
		"chat"
	]
}
The state API is used to get or set custom presence key/value pairs for a specific UUID.
To retrieve state (getter) for jbonham on channel my_channel, we'd call:
pubnub.getPresenceState()
	.channels(Arrays.asList("my_channel")) // channels to fetch state for
	.uuid("jbonham") // uuid of user to fetch, or omit own uuid
	.async(new PNCallback<PNGetStateResult>() {
		@Override
		public void onResponse(PNGetStateResult result, PNStatus status) {
			// handle me
		}
	});
To set state (setter) for jbonham on channel my_channel, we'd use the same call, but with a state attribute:
JsonObject jso = new JsonObject();
jso.put("full_name","James Patrick Page");

pubnub.setPresenceState()
	.channelGroups(Arrays.asList("my_channel")) // apply on those channel groups
	.state(jso) // the new state
	.async(new PNCallback<PNSetStateResult>() {
		@Override
		public void onResponse(PNSetStateResult result, PNStatus status) {
			// on new state for those channels
		}
	});
After setting state, if you were subscribed to presence for the channel my_channel, you would receive a state-change event in realtime similar to:
{
	"action" : "state-change",
	"uuid" : "jbonham",
	"timestamp" : 1345549797,
	"data" : {
		"full_name" : "James Patrick Page"
	}
}
Now that state is set, you could also pull it (again) via the getPresenceState() getter method.
In addition to setting state via the setPresenceState() setter method, you can subscribe to a channel and set state in a single call with subscribe():
PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.setSubscribeKey("demo");
pnConfiguration.setPublishKey("demo");


class complexData {
	String fieldA;
	int fieldB;
}

PubNub pubnub= new PubNub(pnConfiguration);

pubnub.addListener(new SubscribeCallback() {
	@Override
	public void status(PubNub pubnub, PNStatus status) {
		if (status.getCategory() == PNStatusCategory.PNConnectedCategory){
			complexData data = new complexData();
			data.fieldA = "Awesome";
			data.fieldB = 10;
			pubnub.setPresenceState()
				.channels(Arrays.asList("awesomeChannel"))
				.channelGroups(Arrays.asList("awesomeChannelGroup"))
				.state(data).async(new PNCallback<PNSetStateResult>() {
					@Override
					public void onResponse(PNSetStateResult result, PNStatus status) {
						// handle set state response
					}
				});
		}
	}

	@Override
	public void message(PubNub pubnub, PNMessageResult message) {

	}

	@Override
	public void presence(PubNub pubnub, PNPresenceEventResult presence) {

	}
});

pubnub.subscribe().channels(Arrays.asList("awesomeChannel"));
In this case, the join event would also include the state information, similar to:
{
	"action" : "join",
	"timestamp" : 1345546797,
	"uuid" : "175c2c67-b2a9-470d-8f4b-1db94f90e39e",
	"occupancy" : 2,
	"data" : {
		"age" : 67,
		"full" : "RobertPlant",
		"country" : "UK",
		"appstate" : "foreground",
		"latlong" : "51.5072°N,0.1275°W"
	}
}
hereNow() and whereNow() calls can be optimized to include or exclude UUID and Custom Presence (State) information. This may be useful when you wish to optimize response data sizes when you know you'll be receiving a lot of data in a response.
Lets take the example of calling hereNow() on a channel with state as true and uuid as true. We'll get back UUID and State info (if set):
{
	"status" : 200,
	"message" : "OK",
	"service" : "Presence",
	"uuids" : [
		{
			"uuid" : "myUUID0"
		},
		{
			"state" : {
				"abcd" : {
					"age" : 15
				}
			},
			"uuid" : "myUUID1"
		},
		{
			"uuid" : "b9eb408c-bcec-4d34-b4c4-fabec057ad0d"
		},
		{
			"state" : {
				"abcd" : {
					"age" : 15
				}
			},
			"uuid" : "myUUID2"
		},
		{
			"state" : {
				"abcd" : {
					"age" : 24
				}
			},
			"uuid" : "myUUID9"
		}
	],
	"occupancy" : 5
}
Calling with state as false returns the UUIDs, but not the state data:
{
    "status" : 200,
    "message" : "OK",
    "service" : "Presence",
    "uuids" : [
        "myUUID0",
        "myUUID1",
        "myUUID2",
        "myUUID9",
        "b9eb408c-bcec-4d34-b4c4-fabec057ad0d"
    ],
    "occupancy" : 5
}
and calling with uuid as false omits all UUID and state data (since state is a child of uuid):
{
    "status": 200,
    "message": "OK",
    "service": "Presence",
    "occupancy": 5
}
 
The minimum recommended value of Heartbeat is 60s. Setting it to a lower value like 30, 15 or 10s, especially for mobile apps, uses more battery and data and more likely to cause timeout events for presence.
Timeout events are triggered when the server does not hear a heartbeat from the client within a default timeout time of 280 seconds. This default timeout time can be adjusted, as well as the frequency in which the client sends heartbeats to the server.
By default, the system will use a default presence server timeout of 300s, with the client pinging back to the server every 280s.
PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.setSubscribeKey("my_subkey");
pnConfiguration.setPublishKey("my_pubkey");
pnConfiguration.setUuid("Stephen");  
PubNub pubnub = new PubNub(pnConfiguration);
At the expense of additional network bandwidth to send additional heartbeat pings, you could decrease this default presence server timeout to lets say, 120 seconds:
PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.setSubscribeKey("my_subkey");
pnConfiguration.setPublishKey("my_pubkey");
pnConfiguration.setPresenceTimeout(120);
 
PubNub pubnub = new PubNub(pnConfiguration);
By default, the client will send a heartbeat ping every HEARTBEAT / 2 - 1 seconds (aka Heartbeat Interval), or in this case, every 59s. To tune this further, we could override this heartbeat interval of 59s to every 30s. For example:
PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.setSubscribeKey("my_subkey");
pnConfiguration.setPublishKey("my_pubkey");
pnConfiguration.setPresenceTimeoutWithCustomInterval(120,59);
PubNub pubnub = new PubNub(pnConfiguration);
In the above example, the server knows to emit a TIMEOUT event if it does not receive a ping from it within 120s. The device will ping back to the server to tell its alive every 30s.
Adjust heartbeat and heartbeat_intervals carefully -- optimize base on expected network conditions, and keep in mind timeout detection will be a balance between the need to know and how much network/battery resources you're willing to spare to get to the know.
The easiest (and normally most convenient) method for PubNub users to consume Presence data is via the client SDK's Presence APIs, e.g., subscribe().
However, when under very high traffic loads or very specific customer architectures, it may be more efficient to have PubNub push to the customer's webhook endpoint.
In this document, we'll cover PubNub's Presence Webhook implementation for backend monitoring of Presence across all channels under the subscribeKey.
 
Contact our support team via support@pubnub.com to configure the URL-endpoints for Presence Webhooks. They are not self-service configurable.

Presence monitoring provides 4 events in relation to channel activity. For any channel, the events that occur are broadcast/published to a child channel with a channel suffix of -pnpres.

ChannelPresence Events child Channel
mychannel
mychannel-pnpres
To monitor for realtime Presence events, you need to subscribe to the Presence channel.
Our mainstream popular SDKs provide convenience methods (such as subscribe()) that take the main channel name as argument, and automatically append -pnpres to the actual subscribing channel name.
On our lightweight / embedded SDKs, in some cases we require the end user to just manually append the -pnpres suffix manually.
Regardless of which SDK you are using, you can always subscribe to the Presence channel directly, without a prerequisite of first subscribing the parent channel.

If you have a large number of channels in your implementation, subscribing to every single Presence channel in some cases may be hard to manage. Especially in environments where the actual channel names may be dynamic and ever-changing.

Without Presence Webhooks, your server would have to subscribe to all the channels' -pnpres channels, but this can be a tedious task to stay on top of if your app has thousands of channels or more. There are network, memory and CPU limitations to your server and so you will have to scale this across multiple server instances without duplicating presence channel subscriptions. This is commonly referred to as a fan-in (many publishers to one/few subscribers) design pattern.

This can be done and has been done by some PubNub customers but it is not a solution we will be laying out in this post. If you require a fan-in architecture, please contact PubNub Support to talk to your Customer Success Manager and Solutions Architect about fan-in best practices.

So if you are going to have thousands of channels and you would rather not go down the fan-in implementation road, then you will find Webhooks to be easier to implement and scale with traditional, well-known web infrastructure (load balancers, web and app servers provided by your application service providers like Heroku, Rackspace, Azure, Amazon and others).

PubNub created Webhooks in order to solve these use cases. In addition to publishing Presence events on the -pnpres channels, PubNub servers will HTTP POST the same data to specified endpoints on the customer's backend. When Webhook functionality is enabled, all Presence events, across all channels for a given enabled keyset will be tee-ed to a Webhooks endpoint. This puts the load balancer/web server/app server layers to work to handle the load in a distributed way rather than making you do the work or distributing the channel subscriptions across your server instances.

There are four user level presence events: join, leave, timeout and state-change, and two channel level events: active and inactive. You are likely familiar with the four user level presence events but perhaps the channel level events are new to you. When a channel occupancy goes from 0 to 1, it is active and when it goes from 1 to 0, it is inactive.

Each event has it's own Webhook for which you can provide a REST endpoint to your server to handle the event. Or you can provide one REST endpoint for all of them and just implement conditional logic on the action attribute on your server to handle each individual event.

Whatever you choose, you need to provide the sub-key and the REST URIs to PubNub Support to configure this for you. You likely will have more than one sub-key with different endpoints for different server environments (dev, test, production, for example).

Once your server's REST endpoints are implemented and the PubNub key configuration is in place, you are ready to go.

Since Presence tracks events based on the assigned client UUID, the client UUID should be set by developers when initializing the PubNub client in the SDK. Every SDK will generate a random UUID if one is not supplied, but that makes it harder to associate Presence activity with a known user (or userID). The format of the UUID can be any string format; the only requirement is that it's unique.

Feel free to encode useful information into your UUIDs as well… one such novel encoding pattern appends device type, and even the location (home/work/school, etc) into the UUID itself.

An example of creating a UUID based on userID and platform type may look similar to the following:

PlatformUserIDPubNub UUID (UserID :: Platform)
iPad
d6c14c79-11d3-4615-8cfd-6d657aa5d03d
d6c14c79-11d3-4615-8cfd-6d657aa5d03d::iPad
iPhone
d6c14c79-11d3-4615-8cfd-6d657aa5d03d
d6c14c79-11d3-4615-8cfd-6d657aa5d03d::iPhone
Android
100023
100023::Android
Desktop Browser
scalabl3
scalabl3::Browser
Firefox
d6c14c79-11d3-4615-8cfd-6d657aa5d03d
d6c14c79-11d3-4615-8cfd-6d657aa5d03d::Firefox
Of course, these are just examples… you can have any format, from a straight-up randomly generated character string, to a more unique UUID, to something that provides even more value by encoding user, platform, location, etc. attributes as well.
 
Webhooks still follows the Announce-Max message throttling behavior that native PubNub Presence messages also adhere to. Please ask your PubNub CSM (Customer Success Manager), Solution Architect, or support@pubnub.com for more information if you are not familiar with this setting.
The following are the Webhook payloads, organized by Presence Event Type.
User enters/subscribes to a channel (user was not there previously)
	HTTP POST
	Content-Type: application/json
	{
		"action"    : "join",
		"subkey"    : <pubnub-subscribe-key>,
		"channel"   : <channel>,
		"uuid"      : <uuid>,
		"timestamp" : <unix-timestamp>,
		"occupancy" : <#-users-in-channel>,
		"data"      : <state-data-associated-with-uuid>
	}
User leaves/unsubscribes a channel (user was in the channel and then left)
	HTTP POST
	Content-Type: application/json
	{
		"action"    : "leave",
		"subkey"    : <pubnub-subscribe-key>,
		"channel"   : <channel>,
		"uuid"      : <uuid>,
		"timestamp" : <unix-timestamp>,
		"occupancy" : <#-users-in-channel>,
		"data"      : <state-data-associated-with-uuid>
	}
User removed from channel because presence did not hear from user for X seconds (where X is specified during subscribe/heartbeat or defaults to 320 seconds).
	HTTP POST
	Content-Type: application/json
	{
		"action"    : "timeout",
		"subkey"    : <pubnub-subscribe-key>,
		"channel"   : <channel>,
		"uuid"      : <uuid>,
		"timestamp" : <unix-timestamp>,
		"occupancy" : <#-users-in-channel>,
		"data"      : <state-data-associated-with-uuid>
	}
User's state was changed (the newly specified state was different from their old state). data contains the entire new state, not the delta.
	HTTP POST
	Content-Type: application/json
	{
		"action"    : "state-change",
		"subkey"    : <pubnub-subscribe-key>,
		"channel"   : <channel>,
		"timestamp" : <unix-timestamp>,
		"occupancy" : <#-users-in-channel>,
		"data"      : <state-data-associated-with-uuid>
	}
For active and inactive, it's even simpler:
 

It might be convenient to have separate endpoints for active/inactive and join/leave/timeout/state-change, but no reason it can't be all the same endpoint.

The channel state has changed to have at least one client subscribed to channel. This event is only fired when channel state changes from Inactive to Active.
	HTTP POST
	Content-Type: multipart/form-data

	action=inactive,
	timestamp=<unix-timestamp>,
	sub_key=<pubnub-subscribe-key>,
	channel=<channel-name>
 
Please note that due to legacy implementations, the Active and Inactive webhooks are multipart/form-data and not JSON.
The channel state has changed to have 0 clients subscribed to the channel due to unsubscribe (LEAVE) or timeout of the last client on the channel. This event is only fired when channel state changes from Active to Inactive.
	HTTP POST
	Content-Type: multipart/form-data

	action=inactive,
	timestamp=<unix-timestamp>,
	sub_key=<pubnub-subscribe-key>,
	channel=<channel-name>
 
Please note that due to legacy implementations, the Active and Inactive webhooks are multipart/form-data and not JSON.

It is important that your REST endpoint implementation return a 200 response code immediately upon receiving the Webhook from PubNub. Failure to do so will result in PubNub sending duplicate events because PubNub assumes no response means your server did not receive the event.

PubNub will wait five seconds for the 200 response before trying again. After a third retry (four total attempts), PubNub will no longer attempt to send that particular event to your server.

app.post("/chatterbox/api/v1/wh/presence", (request, response) => {
	var event = request.body;
	winston.info('entering presence webhook for uuid/user: ' + event.uuid);

	pubnub.publish({
		channel: "wh-raw",
		message: event,
		callback: function(result) {
			winston.info("published status to wh-raw channel{" + result[2] + "}");
		}
	});


	if ((!event) || (!event.action)) {
		winston.info("could not process event: " + JSON.stringify(event));
		response.status(200).end();
		return;
	}
	//use a channel with the same name as the uuid to determine
	//if you need to update the status of the profile.
	if (event.channel === event.uuid) {
		winston.info("user-status-change-event captured: " + event.channel);
		var profile = profileRepository.find(event.channel);
		if (profile === null) {
			winston.log("profile for uuid not found: " + event.uuid);
			response.status(200).end();
			return;
		}

		if (event.action === "join") {
			profile = new repository.Profile(event.uuid);
			winston.info("user-status-change-event: changed status for" + profile.userName +  " from " + profile.status + "to loggingIn" );
			profile.status = "loggingIn";
			profileRepository.put(profile);
		}

		if (event.action === "state-change") {
			//if the user sends lat/latlong
			winston.info("status-change with data");
			winston.info(event.data);

			if (event.data.status) {
				profile.status = event.data.status;
			}

			profile.firstName = event.data.firstName;
			profile.lastName = event.data.lastName;
			profile.email = event.data.email;
			profile.userName = event.data.userName;

		}

		if ((event.action === "leave") || (event.action === "timeout")) {

			/*SAMPLE...use whereNow to capture a list of offline
				channels to monitor*/
			pubnub.where_now({
				uuid: event.uuid,
				callback: function(results) {
					winston.info(results);
					var lp = profileRepository.find(event.uuid);
					if (lp != null) {
						lp.offlineChannels = results.channels;
						//make sure they are on the global channel
						//this is only for DEMO...
						lp.offlineChannels.push("AWG-global");
						profileRepository.put(lp);
					}
				}
			})

			profile.status = "offline";

		}

		profileRepository.put(profile);
		response.status(200).json(profile).end();
		return;
	}
	response.status(200).end();
});
Check out PubNub's other Java-based SDKs, such as Java V4, Android V4