package org.example.mqtt;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import org.example.mqtt.data.NotificationContentProvider;
import org.example.mqtt.data.NotificationData;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.Callback;
import org.fusesource.mqtt.client.CallbackConnection;
import org.fusesource.mqtt.client.Listener;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.mqtt.client.Topic;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Application;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.util.Log;
public class MQTTSubscriberService extends Service {
private final String TAG = "MQTTSubscriberService";
private final String MQTT_ALL_WILDCARD_SUFFIX = ".>";
/** For showing and hiding our notification. */
Handler threadHandler;
MQTT mqtt = null;
CallbackConnection connection = null;
public static final int MSG_BIND = 0;
public static final int MSG_CONNECT = 1;
public static final int MSG_DISCONNECT = 2;
public static final int MSG_RECONNECT = 3;
public static final int MSG_SUBSCRIBE = 4;
public static final int MSG_UNSUBSCRIBE = 5;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
static final int MSG_UNREGISTER_CLIENT = 2;
// handler for communication with main activity
Messenger mClient;
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mqttServiceMessenger = new Messenger(new IncomingHandler());
/**
* Handler of incoming messages from clients
* As I am expecting to communicate only with the Main activity thread
* I will not get the replyTo from the message all the time to know who
* to answer to
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "message received by service");
switch (msg.what) {
case MSG_BIND:
mClient = msg.replyTo;
break;
case MSG_CONNECT:
connect();
break;
case MSG_DISCONNECT:
disconnect();
break;
case MSG_SUBSCRIBE:
String topic = msg.getData().getString("topic");
if(null != topic) subscribeTopic(topic);
break;
case MSG_UNSUBSCRIBE:
String t = msg.getData().getString("topic");
if(null != t) unsubscribeTopic(t);
break;
default:
super.handleMessage(msg);
}
}
}
@Override
public void onCreate() {
// Handler will get associated with the current thread,
// which is the main thread.
threadHandler = new Handler();
Log.d(TAG, "service created");
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "service bound");
return mqttServiceMessenger.getBinder();
}
public void connect()
{
MqttApplication appHandler = (MqttApplication) getApplication();
mqtt = new MQTT();
mqtt.setKeepAlive((short)2);
try
{
mqtt.setHost(appHandler.getAddress());
Log.d(TAG, "Address set: " + appHandler.getAddress());
}
catch(URISyntaxException urise)
{
Log.e(TAG, "URISyntaxException connecting to - " + urise);
}
TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
mqtt.setClientId(tm.getDeviceId());
mqtt.setCleanSession(false);
connection = mqtt.callbackConnection();
connection.connect(onui(new Callback<Void>(){
public void onSuccess(Void value) {
//connectButton.setEnabled(false);
Log.d(TAG, "on success connection");
connection.listener(new MessageListener());
//toast("Connected");
MqttApplication appHandler = (MqttApplication) getApplication();
appHandler.setConnection(true);
Message msg = Message.obtain(null,
MainActivity.MSG_CONNECTED);
msg.replyTo = mqttServiceMessenger;
try {
mClient.send(msg);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// get topics from shared preferences to subscribe
SharedPreferences keyValues = getSharedPreferences(MqttApplication.sharedPrefName, Context.MODE_PRIVATE);
Map<String, String> initialSubs = (Map<String, String>) keyValues.getAll();
// try to subscribe if there are topics
if(initialSubs.size() >0){
Topic[] topics = new Topic[initialSubs.size()];
int i= 0;
for (Map.Entry<String, String> entry : initialSubs.entrySet()){
topics[i] = new Topic(UTF8Buffer.utf8("pub." + entry.getKey() + MQTT_ALL_WILDCARD_SUFFIX), QoS.EXACTLY_ONCE);
Log.d(TAG, "Added topic " + topics[i].toString());
i++;
}
// now trying to subscribe
connection.subscribe(topics,onui (new OnsubscribeCallback()));
}
else{
// succeed to connect no code to add
}
}
public void onFailure(Throwable e) {
Log.d(TAG, "on failure connection ");
Log.e(TAG, "Exception connecting " + e);
Message msg = Message.obtain(null,
MainActivity.RECONNECT_TIMEOUT_ON_SERVER);
msg.replyTo = mqttServiceMessenger;
try {
mClient.send(msg);
} catch (RemoteException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}));
}
private void subscribeTopic (String topic){
Topic[] topics = new Topic[1];
topics[0] = new Topic(UTF8Buffer.utf8("pub." + topic + MQTT_ALL_WILDCARD_SUFFIX), QoS.EXACTLY_ONCE);
// now trying to subscribe
Log.d(TAG, "calling subscribe to " + topics[0].toString());
connection.subscribe(topics,onui (new OnsubscribeCallback()));
}
private void unsubscribeTopic (String topic){
UTF8Buffer[] topics = new UTF8Buffer[1];
topics[0] = new UTF8Buffer("pub." + topic + MQTT_ALL_WILDCARD_SUFFIX);
Log.d(TAG, "calling unsubscribe" + topics[0].toString());
connection.unsubscribe(topics, onui(new UnsubscribeCallback()));
}
private void disconnect()
{
//connectButton.setEnabled(true);
try
{
if(connection != null )
{
// get topics from shared preferences to subscribe
// notice that we append a "pub." before the topic URI
// and a ".>" after the topic
SharedPreferences keyValues = getSharedPreferences(MqttApplication.sharedPrefName, Context.MODE_PRIVATE);
Map<String, String> initialSubs = (Map<String, String>) keyValues.getAll();
if(initialSubs.size() >0){
UTF8Buffer[] topics = new UTF8Buffer[initialSubs.size()];
int i= 0;
for (Map.Entry<String, String> entry : initialSubs.entrySet()){
topics[i] = new UTF8Buffer("pub." + entry.getKey() + MQTT_ALL_WILDCARD_SUFFIX);
Log.d(TAG, "Unsubscribed topic" + entry.getKey());
i++;
}
connection.unsubscribe(topics, onui(new UnsubscribeCallback()));
}
connection.disconnect(onui(new Callback<Void>(){
public void onSuccess(Void value) {
Log.e(TAG, "disconnected gracefully" );
MqttApplication appHandler = (MqttApplication) getApplication();
appHandler.setConnection(false);
Message msg = Message.obtain(null,
MainActivity.MSG_DISCONNECTED);
msg.replyTo = mqttServiceMessenger;
try {
mClient.send(msg);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void onFailure(Throwable e) {
Log.e(TAG, "Exception disconnecting " + e);
}
}));
}
}
catch(Exception e)
{
Log.e(TAG, "Exception " + e);
}
}
// subscribed callback
private class OnsubscribeCallback implements Callback <byte[]> {
public void onSuccess(byte[] subscription) {
Log.d(TAG, "subscribed");//TODO complement this log
Message msg = Message.obtain(null,
MainActivity.SUBSCRIPTION_DONE);
msg.replyTo = mqttServiceMessenger;
try {
mClient.send(msg);
} catch (RemoteException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public void onFailure(Throwable e) {
connection.suspend();// perhaps change for disconnect
MqttApplication appHandler = (MqttApplication) getApplication();
appHandler.setConnection(false);
//connectButton.setEnabled(true);
Log.e(TAG, "Exception when subscribing, disconnecting: " + e);
}
}
// unsubscribed callback
private class UnsubscribeCallback implements Callback <Void> {
public void onSuccess(Void subscription) {
Log.d(TAG, "Unsubscription worked");
Message msg = Message.obtain(null,
MainActivity.UNSUBSCRIPTION_DONE);
msg.replyTo = mqttServiceMessenger;
try {
mClient.send(msg);
} catch (RemoteException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public void onFailure(Throwable e) {
Log.d(TAG, "Unsubscription failed");
}
}
// listener
private class MessageListener implements Listener {
// apparently this is the function that sense a disconnection first
public void onDisconnected() {
Log.d(TAG, "got disconnected");
//connectButton.setEnabled(true);
connection.suspend();// perhaps change for disconnect
MqttApplication appHandler = (MqttApplication) getApplication();
appHandler.setConnection(false);
Message msg = Message.obtain(null,
MainActivity.MSG_DISCONNECTED);
msg.replyTo = mqttServiceMessenger;
try {
mClient.send(msg);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void onConnected() {
Log.d(TAG, "got connected on message listener");
}
public void onPublish(UTF8Buffer topic, Buffer payload, Runnable ack) {
Log.d(TAG, "on publish called");
// You can now process a received message from a topic
String fullPayLoad = new String(payload.data); // I did not find documentation on this,
Log.d(TAG, "String size is " + fullPayLoad.length() + " , and data size is " + payload.length);
// but the payload seems to in fact consists of 0x32 0xlen (maybe more than a byte) 0x(topic) 0x(message number - in 2 bytes) 0x(message)
String receivedMesageTopic = topic.toString();
String[] fullPayLoadParts = fullPayLoad.split(receivedMesageTopic);// TODO: I should probably check if there are characters that needs to be scaped
Log.d(TAG, "fullpayload = " + fullPayLoad);
if(fullPayLoadParts.length == 2){
// sometimes the payload includes the message ID (2 bytes), sometimes it doesnt....
// if the first character is a "{" then it didnt
String messagePayLoad;
if(fullPayLoadParts[1].charAt(0) == '{')
messagePayLoad = fullPayLoadParts[1];
else
messagePayLoad = fullPayLoadParts[1].substring(2);
String val = this.insertMessage(messagePayLoad,receivedMesageTopic);
// TODO: SEND AN UPDATE MESSAGE TO ACTIVITY
}
// Once process execute the ack runnable.
ack.run();
}
public void onFailure(Throwable value) {
connection.suspend();// perhaps change for disconnect
Log.d(TAG, "On failure in the listener...");
// TODO: check if the disconnect is called of if I need to send
// a disconect message to the Activity
}
// parse the json message, instert it on the db and return the value
private String insertMessage(String messagePayLoad, String receivedMesageTopic){
String value = null;
try {
JSONObject jObject = new JSONObject(messagePayLoad);
String serviceid = jObject.getString("serviceId");
// ill just add the subscription if the serviceId matches the list of services
// im subscribed to
MqttApplication appHandler = (MqttApplication) getApplication();
if(null != appHandler.getServiceNameFromURI(serviceid)){
try {
String timeInString = jObject.getString("serverTime");
SimpleDateFormat format =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");
Date parsed = format.parse(timeInString);
Calendar c = Calendar.getInstance();
c.setTime(parsed);
// real inserting
ContentValues values = new ContentValues();
values.put(NotificationData.SERVICE_ID, serviceid);
values.put(NotificationData.ALERT_TYPE, jObject.getString("alertType"));
values.put(NotificationData.DESCRIPTION, jObject.getString("description"));
values.put(NotificationData.SERVER_TIME, c.getTime().getTime());
values.put(NotificationData.VALUE, jObject.getString("value"));
values.put(NotificationData.THREAT_ID, jObject.getString("threatId"));
values.put(NotificationData.THRESHOLD, jObject.getInt("threshold"));
values.put(NotificationData.SERVICE_FULL_URI, receivedMesageTopic);
Uri instertedUri = getContentResolver().insert(NotificationContentProvider.CONTENT_URI, values);
// TODO: handle failure on content provider and on main code
value = jObject.getString("value");
}
catch(ParseException pe) {
throw new IllegalArgumentException();
}
}
} catch (JSONException e) {
Log.d(TAG, "Failure parsing json message + " + messagePayLoad);
e.printStackTrace();
}
return value;
}
}
// callback used for Future
<T> Callback<T> onui(final Callback<T> original) {
return new Callback<T>() {
public void onSuccess(final T value) {
threadHandler.post(new Runnable(){
public void run() {
original.onSuccess(value);
}
});
}
public void onFailure(final Throwable error) {
threadHandler.post(new Runnable(){
public void run() {
original.onFailure(error);
}
});
}
};
}
}