This class implements the {@link MqttPingSender} pinger interface
+ * allowing applications to send ping packet to server every keep alive interval.
+ *
+ *
+ * @see MqttPingSender
+ */
+class AlarmPingSender implements MqttPingSender {
+ // Identifier for Intents, log messages, etc..
+ private static final String TAG = "AlarmPingSender";
+
+ // TODO: Add log.
+ private ClientComms comms;
+ private MqttService service;
+ private BroadcastReceiver alarmReceiver;
+ private AlarmPingSender that;
+ private PendingIntent pendingIntent;
+ private volatile boolean hasStarted = false;
+
+ public AlarmPingSender(MqttService service) {
+ if (service == null) {
+ throw new IllegalArgumentException(
+ "Neither service nor client can be null.");
+ }
+ this.service = service;
+ that = this;
+ }
+
+ @Override
+ public void init(ClientComms comms) {
+ this.comms = comms;
+ this.alarmReceiver = new AlarmReceiver();
+ }
+
+ @Override
+ public void start() {
+ String action = MqttServiceConstants.PING_SENDER
+ + comms.getClient().getClientId();
+ Log.d(TAG, "Register alarmreceiver to MqttService"+ action);
+ service.registerReceiver(alarmReceiver, new IntentFilter(action));
+
+ pendingIntent = PendingIntent.getBroadcast(service, 0, new Intent(
+ action), PendingIntent.FLAG_UPDATE_CURRENT);
+
+ schedule(comms.getKeepAlive());
+ hasStarted = true;
+ }
+
+ @Override
+ public void stop() {
+
+ Log.d(TAG, "Unregister alarmreceiver to MqttService"+comms.getClient().getClientId());
+ if(hasStarted){
+ if(pendingIntent != null){
+ // Cancel Alarm.
+ AlarmManager alarmManager = (AlarmManager) service.getSystemService(Service.ALARM_SERVICE);
+ alarmManager.cancel(pendingIntent);
+ }
+
+ hasStarted = false;
+ try{
+ service.unregisterReceiver(alarmReceiver);
+ }catch(IllegalArgumentException e){
+ //Ignore unregister errors.
+ }
+ }
+ }
+
+ @Override
+ public void schedule(long delayInMilliseconds) {
+ long nextAlarmInMilliseconds = System.currentTimeMillis()
+ + delayInMilliseconds;
+ Log.d(TAG, "Schedule next alarm at " + nextAlarmInMilliseconds);
+ AlarmManager alarmManager = (AlarmManager) service
+ .getSystemService(Service.ALARM_SERVICE);
+
+ if(Build.VERSION.SDK_INT >= 23){
+ // In SDK 23 and above, dosing will prevent setExact, setExactAndAllowWhileIdle will force
+ // the device to run this task whilst dosing.
+ Log.d(TAG, "Alarm scheule using setExactAndAllowWhileIdle, next: " + delayInMilliseconds);
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
+ pendingIntent);
+ } else if (Build.VERSION.SDK_INT >= 19) {
+ Log.d(TAG, "Alarm scheule using setExact, delay: " + delayInMilliseconds);
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
+ pendingIntent);
+ } else {
+ alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
+ pendingIntent);
+ }
+ }
+
+ /*
+ * This class sends PingReq packet to MQTT broker
+ */
+ class AlarmReceiver extends BroadcastReceiver {
+ private WakeLock wakelock;
+ private final String wakeLockTag = MqttServiceConstants.PING_WAKELOCK
+ + that.comms.getClient().getClientId();
+
+ @Override
+ @SuppressLint("Wakelock")
+ public void onReceive(Context context, Intent intent) {
+ // According to the docs, "Alarm Manager holds a CPU wake lock as
+ // long as the alarm receiver's onReceive() method is executing.
+ // This guarantees that the phone will not sleep until you have
+ // finished handling the broadcast.", but this class still get
+ // a wake lock to wait for ping finished.
+
+ Log.d(TAG, "Sending Ping at:" + System.currentTimeMillis());
+
+ PowerManager pm = (PowerManager) service
+ .getSystemService(Service.POWER_SERVICE);
+ wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+ wakelock.acquire();
+
+ // Assign new callback to token to execute code after PingResq
+ // arrives. Get another wakelock even receiver already has one,
+ // release it until ping response returns.
+ IMqttToken token = comms.checkForActivity(new IMqttActionListener() {
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ Log.d(TAG, "Success. Release lock(" + wakeLockTag + "):"
+ + System.currentTimeMillis());
+ //Release wakelock when it is done.
+ wakelock.release();
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken,
+ Throwable exception) {
+ Log.d(TAG, "Failure. Release lock(" + wakeLockTag + "):"
+ + System.currentTimeMillis());
+ //Release wakelock when it is done.
+ wakelock.release();
+ }
+ });
+
+
+ if (token == null && wakelock.isHeld()) {
+ wakelock.release();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/DatabaseMessageStore.java b/app/src/main/java/org/eclipse/paho/android/service/DatabaseMessageStore.java
new file mode 100644
index 0000000..e11eca1
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/DatabaseMessageStore.java
@@ -0,0 +1,462 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * James Sutton - Removing SQL Injection vunerability (bug 467378)
+ */
+package org.eclipse.paho.android.service;
+
+import java.util.Iterator;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Implementation of the {@link MessageStore} interface, using a SQLite database
+ *
+ */
+class DatabaseMessageStore implements MessageStore {
+
+ // TAG used for indentify trace data etc.
+ private static final String TAG = "DatabaseMessageStore";
+
+ // One "private" database column name
+ // The other database column names are defined in MqttServiceConstants
+ private static final String MTIMESTAMP = "mtimestamp";
+
+ // the name of the table in the database to which we will save messages
+ private static final String ARRIVED_MESSAGE_TABLE_NAME = "MqttArrivedMessageTable";
+
+ // the database
+ private SQLiteDatabase db = null;
+
+ // a SQLiteOpenHelper specific for this database
+ private MQTTDatabaseHelper mqttDb = null;
+
+ // a place to send trace data
+ private MqttTraceHandler traceHandler = null;
+
+ /**
+ * We need a SQLiteOpenHelper to handle database creation and updating
+ *
+ */
+ private static class MQTTDatabaseHelper extends SQLiteOpenHelper {
+ // TAG used for indentify trace data etc.
+ private static final String TAG = "MQTTDatabaseHelper";
+
+ private static final String DATABASE_NAME = "mqttAndroidService.db";
+
+ // database version, used to recognise when we need to upgrade
+ // (delete and recreate)
+ private static final int DATABASE_VERSION = 1;
+
+ // a place to send trace data
+ private MqttTraceHandler traceHandler = null;
+
+ /**
+ * Constructor.
+ *
+ * @param traceHandler
+ * @param context
+ */
+ public MQTTDatabaseHelper(MqttTraceHandler traceHandler, Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ this.traceHandler = traceHandler;
+ }
+
+ /**
+ * When the database is (re)created, create our table
+ *
+ * @param database
+ */
+ @Override
+ public void onCreate(SQLiteDatabase database) {
+ String createArrivedTableStatement = "CREATE TABLE "
+ + ARRIVED_MESSAGE_TABLE_NAME + "("
+ + MqttServiceConstants.MESSAGE_ID + " TEXT PRIMARY KEY, "
+ + MqttServiceConstants.CLIENT_HANDLE + " TEXT, "
+ + MqttServiceConstants.DESTINATION_NAME + " TEXT, "
+ + MqttServiceConstants.PAYLOAD + " BLOB, "
+ + MqttServiceConstants.QOS + " INTEGER, "
+ + MqttServiceConstants.RETAINED + " TEXT, "
+ + MqttServiceConstants.DUPLICATE + " TEXT, " + MTIMESTAMP
+ + " INTEGER" + ");";
+ traceHandler.traceDebug(TAG, "onCreate {"
+ + createArrivedTableStatement + "}");
+ try {
+ database.execSQL(createArrivedTableStatement);
+ traceHandler.traceDebug(TAG, "created the table");
+ } catch (SQLException e) {
+ traceHandler.traceException(TAG, "onCreate", e);
+ throw e;
+ }
+ }
+
+ /**
+ * To upgrade the database, drop and recreate our table
+ *
+ * @param db
+ * the database
+ * @param oldVersion
+ * ignored
+ * @param newVersion
+ * ignored
+ */
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ traceHandler.traceDebug(TAG, "onUpgrade");
+ try {
+ db.execSQL("DROP TABLE IF EXISTS " + ARRIVED_MESSAGE_TABLE_NAME);
+ } catch (SQLException e) {
+ traceHandler.traceException(TAG, "onUpgrade", e);
+ throw e;
+ }
+ onCreate(db);
+ traceHandler.traceDebug(TAG, "onUpgrade complete");
+ }
+ }
+
+ /**
+ * Constructor - create a DatabaseMessageStore to store arrived MQTT message
+ *
+ * @param service
+ * our parent MqttService
+ * @param context
+ * a context to use for android calls
+ */
+ public DatabaseMessageStore(MqttService service, Context context) {
+ this.traceHandler = service;
+
+ // Open message database
+ mqttDb = new MQTTDatabaseHelper(traceHandler, context);
+
+ // Android documentation suggests that this perhaps
+ // could/should be done in another thread, but as the
+ // database is only one table, I doubt it matters...
+
+ traceHandler.traceDebug(TAG, "DatabaseMessageStore complete");
+ }
+
+ /**
+ * Store an MQTT message
+ *
+ * @param clientHandle
+ * identifier for the client storing the message
+ * @param topic
+ * The topic on which the message was published
+ * @param message
+ * the arrived MQTT message
+ * @return an identifier for the message, so that it can be removed when appropriate
+ */
+ @Override
+ public String storeArrived(String clientHandle, String topic,
+ MqttMessage message) {
+
+ db = mqttDb.getWritableDatabase();
+
+ traceHandler.traceDebug(TAG, "storeArrived{" + clientHandle + "}, {"
+ + message.toString() + "}");
+
+ byte[] payload = message.getPayload();
+ int qos = message.getQos();
+ boolean retained = message.isRetained();
+ boolean duplicate = message.isDuplicate();
+
+ ContentValues values = new ContentValues();
+ String id = java.util.UUID.randomUUID().toString();
+ values.put(MqttServiceConstants.MESSAGE_ID, id);
+ values.put(MqttServiceConstants.CLIENT_HANDLE, clientHandle);
+ values.put(MqttServiceConstants.DESTINATION_NAME, topic);
+ values.put(MqttServiceConstants.PAYLOAD, payload);
+ values.put(MqttServiceConstants.QOS, qos);
+ values.put(MqttServiceConstants.RETAINED, retained);
+ values.put(MqttServiceConstants.DUPLICATE, duplicate);
+ values.put(MTIMESTAMP, System.currentTimeMillis());
+ try {
+ db.insertOrThrow(ARRIVED_MESSAGE_TABLE_NAME, null, values);
+ } catch (SQLException e) {
+ traceHandler.traceException(TAG, "onUpgrade", e);
+ throw e;
+ }
+ int count = getArrivedRowCount(clientHandle);
+ traceHandler
+ .traceDebug(
+ TAG,
+ "storeArrived: inserted message with id of {"
+ + id
+ + "} - Number of messages in database for this clientHandle = "
+ + count);
+ return id;
+ }
+
+ private int getArrivedRowCount(String clientHandle) {
+ int count = 0;
+ String[] projection = {
+ MqttServiceConstants.MESSAGE_ID,
+ };
+ String selection = MqttServiceConstants.CLIENT_HANDLE + "=?";
+ String[] selectionArgs = new String[1];
+ selectionArgs[0] = clientHandle;
+ Cursor c = db.query(
+ ARRIVED_MESSAGE_TABLE_NAME, // Table Name
+ projection, // The columns to return;
+ selection, // Columns for WHERE Clause
+ selectionArgs , // The values for the WHERE Cause
+ null, //Don't group the rows
+ null, // Don't filter by row groups
+ null // The sort order
+ );
+
+ if (c.moveToFirst()) {
+ count = c.getInt(0);
+ }
+ c.close();
+ return count;
+ }
+
+ /**
+ * Delete an MQTT message.
+ *
+ * @param clientHandle
+ * identifier for the client which stored the message
+ * @param id
+ * the identifying string returned when the message was stored
+ *
+ * @return true if the message was found and deleted
+ */
+ @Override
+ public boolean discardArrived(String clientHandle, String id) {
+
+ db = mqttDb.getWritableDatabase();
+
+ traceHandler.traceDebug(TAG, "discardArrived{" + clientHandle + "}, {"
+ + id + "}");
+ int rows;
+ String[] selectionArgs = new String[2];
+ selectionArgs[0] = id;
+ selectionArgs[1] = clientHandle;
+
+ try {
+ rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME,
+ MqttServiceConstants.MESSAGE_ID + "=? AND "
+ + MqttServiceConstants.CLIENT_HANDLE + "=?",
+ selectionArgs);
+ } catch (SQLException e) {
+ traceHandler.traceException(TAG, "discardArrived", e);
+ throw e;
+ }
+ if (rows != 1) {
+ traceHandler.traceError(TAG,
+ "discardArrived - Error deleting message {" + id
+ + "} from database: Rows affected = " + rows);
+ return false;
+ }
+ int count = getArrivedRowCount(clientHandle);
+ traceHandler
+ .traceDebug(
+ TAG,
+ "discardArrived - Message deleted successfully. - messages in db for this clientHandle "
+ + count);
+ return true;
+ }
+
+ /**
+ * Get an iterator over all messages stored (optionally for a specific client)
+ *
+ * @param clientHandle
+ * identifier for the client.
+ * If null, all messages are retrieved
+ * @return iterator of all the arrived MQTT messages
+ */
+ @Override
+ public Iterator getAllArrivedMessages(
+ final String clientHandle) {
+ return new Iterator() {
+ private Cursor c;
+ private boolean hasNext;
+ private final String[] selectionArgs = {
+ clientHandle,
+ };
+
+
+ {
+ db = mqttDb.getWritableDatabase();
+ // anonymous initialiser to start a suitable query
+ // and position at the first row, if one exists
+ if (clientHandle == null) {
+ c = db.query(ARRIVED_MESSAGE_TABLE_NAME,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "mtimestamp ASC");
+ } else {
+ c = db.query(ARRIVED_MESSAGE_TABLE_NAME,
+ null,
+ MqttServiceConstants.CLIENT_HANDLE + "=?",
+ selectionArgs,
+ null,
+ null,
+ "mtimestamp ASC");
+ }
+ hasNext = c.moveToFirst();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!hasNext){
+ c.close();
+ }
+ return hasNext;
+ }
+
+ @Override
+ public StoredMessage next() {
+ String messageId = c.getString(c
+ .getColumnIndex(MqttServiceConstants.MESSAGE_ID));
+ String clientHandle = c.getString(c
+ .getColumnIndex(MqttServiceConstants.CLIENT_HANDLE));
+ String topic = c.getString(c
+ .getColumnIndex(MqttServiceConstants.DESTINATION_NAME));
+ byte[] payload = c.getBlob(c
+ .getColumnIndex(MqttServiceConstants.PAYLOAD));
+ int qos = c.getInt(c.getColumnIndex(MqttServiceConstants.QOS));
+ boolean retained = Boolean.parseBoolean(c.getString(c
+ .getColumnIndex(MqttServiceConstants.RETAINED)));
+ boolean dup = Boolean.parseBoolean(c.getString(c
+ .getColumnIndex(MqttServiceConstants.DUPLICATE)));
+
+ // build the result
+ MqttMessageHack message = new MqttMessageHack(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ message.setDuplicate(dup);
+
+ // move on
+ hasNext = c.moveToNext();
+ return new DbStoredData(messageId, clientHandle, topic, message);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ c.close();
+ super.finalize();
+ }
+
+ };
+ }
+
+ /**
+ * Delete all messages (optionally for a specific client)
+ *
+ * @param clientHandle
+ * identifier for the client.
+ * If null, all messages are deleted
+ */
+ @Override
+ public void clearArrivedMessages(String clientHandle) {
+
+ db = mqttDb.getWritableDatabase();
+ String[] selectionArgs = new String[1];
+ selectionArgs[0] = clientHandle;
+
+ int rows = 0;
+ if (clientHandle == null) {
+ traceHandler.traceDebug(TAG,
+ "clearArrivedMessages: clearing the table");
+ rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, null, null);
+ } else {
+ traceHandler.traceDebug(TAG,
+ "clearArrivedMessages: clearing the table of "
+ + clientHandle + " messages");
+ rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME,
+ MqttServiceConstants.CLIENT_HANDLE + "=?",
+ selectionArgs);
+
+ }
+ traceHandler.traceDebug(TAG, "clearArrivedMessages: rows affected = "
+ + rows);
+ }
+
+ private class DbStoredData implements StoredMessage {
+ private String messageId;
+ private String clientHandle;
+ private String topic;
+ private MqttMessage message;
+
+ DbStoredData(String messageId, String clientHandle, String topic,
+ MqttMessage message) {
+ this.messageId = messageId;
+ this.topic = topic;
+ this.message = message;
+ }
+
+ @Override
+ public String getMessageId() {
+ return messageId;
+ }
+
+ @Override
+ public String getClientHandle() {
+ return clientHandle;
+ }
+
+ @Override
+ public String getTopic() {
+ return topic;
+ }
+
+ @Override
+ public MqttMessage getMessage() {
+ return message;
+ }
+ }
+
+ /**
+ * A way to get at the "setDuplicate" method of MqttMessage
+ */
+ private class MqttMessageHack extends MqttMessage {
+
+ public MqttMessageHack(byte[] payload) {
+ super(payload);
+ }
+
+ @Override
+ protected void setDuplicate(boolean dup) {
+ super.setDuplicate(dup);
+ }
+ }
+
+ @Override
+ public void close() {
+ if (this.db!=null)
+ this.db.close();
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MessageStore.java b/app/src/main/java/org/eclipse/paho/android/service/MessageStore.java
new file mode 100644
index 0000000..b8ebd97
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MessageStore.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import java.util.Iterator;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ *
+ * Mechanism for persisting messages until we know they have been received
+ *
+ *
+ *
A Service should store messages as they arrive via
+ * {@link #storeArrived(String, String, MqttMessage)}.
+ *
When a message has been passed to the consuming entity,
+ * {@link #discardArrived(String, String)} should be called.
+ *
To recover messages which have not been definitely passed to the
+ * consumer, {@link MessageStore#getAllArrivedMessages(String)} is used.
+ *
When a clean session is started {@link #clearArrivedMessages(String)} is
+ * used.
+ *
+ */
+interface MessageStore {
+
+ /**
+ * External representation of a stored message
+ */
+ interface StoredMessage {
+ /**
+ * @return the identifier for the message within the store
+ */
+ String getMessageId();
+
+ /**
+ * @return the identifier of the client which stored this message
+ */
+ String getClientHandle();
+
+ /**
+ * @return the topic on which the message was received
+ */
+ String getTopic();
+
+ /**
+ * @return the identifier of the client which stored this message
+ */
+ MqttMessage getMessage();
+ }
+
+ /**
+ * Store a message and return an identifier for it
+ *
+ * @param clientHandle
+ * identifier for the client
+ * @param message
+ * message to be stored
+ * @return a unique identifier for it
+ */
+ String storeArrived(String clientHandle, String Topic,
+ MqttMessage message);
+
+ /**
+ * Discard a message - called when we are certain that an arrived message
+ * has reached the application.
+ *
+ * @param clientHandle
+ * identifier for the client
+ * @param id
+ * id of message to be discarded
+ */
+ boolean discardArrived(String clientHandle, String id);
+
+ /**
+ * Get all the stored messages, usually for a specific client
+ *
+ * @param clientHandle
+ * identifier for the client - if null, then messages for all
+ * clients are returned
+ */
+ Iterator getAllArrivedMessages(String clientHandle);
+
+ /**
+ * Discard stored messages, usually for a specific client
+ *
+ * @param clientHandle
+ * identifier for the client - if null, then messages for all
+ * clients are discarded
+ */
+ void clearArrivedMessages(String clientHandle);
+
+ void close();
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttAndroidClient.java b/app/src/main/java/org/eclipse/paho/android/service/MqttAndroidClient.java
new file mode 100644
index 0000000..6641277
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttAndroidClient.java
@@ -0,0 +1,1764 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2016 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Ian Craggs - Per subscription message handlers bug 466579
+ * Ian Craggs - ack control (bug 472172)
+ *
+ */
+package org.eclipse.paho.android.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import android.util.SparseArray;
+
+/**
+ * Enables an android application to communicate with an MQTT server using non-blocking methods.
+ *
+ * Implementation of the MQTT asynchronous client interface {@link IMqttAsyncClient} , using the MQTT
+ * android service to actually interface with MQTT server. It provides android applications a simple programming interface to all features of the MQTT version 3.1
+ * specification including:
+ *
+ *
+ *
connect
+ *
publish
+ *
subscribe
+ *
unsubscribe
+ *
disconnect
+ *
+ */
+public class MqttAndroidClient extends BroadcastReceiver implements
+ IMqttAsyncClient {
+
+ /**
+ *
+ * The Acknowledgment mode for messages received from {@link MqttCallback#messageArrived(String, MqttMessage)}
+ *
+ */
+ public enum Ack {
+ /**
+ * As soon as the {@link MqttCallback#messageArrived(String, MqttMessage)} returns,
+ * the message has been acknowledged as received .
+ */
+ AUTO_ACK,
+ /**
+ * When {@link MqttCallback#messageArrived(String, MqttMessage)} returns, the message
+ * will not be acknowledged as received, the application will have to make an acknowledgment call
+ * to {@link MqttAndroidClient} using {@link MqttAndroidClient#acknowledgeMessage(String)}
+ */
+ MANUAL_ACK
+ }
+
+ private static final String SERVICE_NAME = "org.eclipse.paho.android.service.MqttService";
+
+ private static final int BIND_SERVICE_FLAG = 0;
+
+ private static final ExecutorService pool = Executors.newCachedThreadPool();
+
+ /**
+ * ServiceConnection to process when we bind to our service
+ */
+ private final class MyServiceConnection implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ mqttService = ((MqttServiceBinder) binder).getService();
+ bindedService = true;
+ // now that we have the service available, we can actually
+ // connect...
+ doConnect();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mqttService = null;
+ }
+ }
+
+ // Listener for when the service is connected or disconnected
+ private final MyServiceConnection serviceConnection = new MyServiceConnection();
+
+ // The Android Service which will process our mqtt calls
+ private MqttService mqttService;
+
+ // An identifier for the underlying client connection, which we can pass to
+ // the service
+ private String clientHandle;
+
+ private Context myContext;
+
+ // We hold the various tokens in a collection and pass identifiers for them
+ // to the service
+ private final SparseArray tokenMap = new SparseArray<>();
+ private int tokenNumber = 0;
+
+ // Connection data
+ private final String serverURI;
+ private final String clientId;
+ private MqttClientPersistence persistence = null;
+ private MqttConnectOptions connectOptions;
+ private IMqttToken connectToken;
+
+ // The MqttCallback provided by the application
+ private MqttCallback callback;
+ private MqttTraceHandler traceCallback;
+
+ //The acknowledgment that a message has been processed by the application
+ private final Ack messageAck;
+ private boolean traceEnabled = false;
+
+ private volatile boolean receiverRegistered = false;
+ private volatile boolean bindedService = false;
+
+ /**
+ * Constructor - create an MqttAndroidClient that can be used to communicate with an MQTT server on android
+ *
+ * @param context
+ * object used to pass context to the callback.
+ * @param serverURI
+ * specifies the protocol, host name and port to be used to
+ * connect to an MQTT server
+ * @param clientId
+ * specifies the name by which this connection should be
+ * identified to the server
+ */
+ public MqttAndroidClient(Context context, String serverURI,
+ String clientId) {
+ this(context, serverURI, clientId, null, Ack.AUTO_ACK);
+ }
+
+ /**
+ * Constructor - create an MqttAndroidClient that can be used to communicate
+ * with an MQTT server on android
+ *
+ * @param ctx
+ * Application's context
+ * @param serverURI
+ * specifies the protocol, host name and port to be used to
+ * connect to an MQTT server
+ * @param clientId
+ * specifies the name by which this connection should be
+ * identified to the server
+ * @param ackType
+ * how the application wishes to acknowledge a message has been
+ * processed
+ */
+ public MqttAndroidClient(Context ctx, String serverURI, String clientId, Ack ackType) {
+ this(ctx, serverURI, clientId, null, ackType);
+ }
+
+ /**
+ * Constructor - create an MqttAndroidClient that can be used to communicate
+ * with an MQTT server on android
+ *
+ * @param ctx
+ * Application's context
+ * @param serverURI
+ * specifies the protocol, host name and port to be used to
+ * connect to an MQTT server
+ * @param clientId
+ * specifies the name by which this connection should be
+ * identified to the server
+ * @param persistence
+ * The object to use to store persisted data
+ */
+ public MqttAndroidClient(Context ctx, String serverURI, String clientId, MqttClientPersistence persistence) {
+ this(ctx, serverURI, clientId, persistence, Ack.AUTO_ACK);
+ }
+
+ /**
+ * Constructor- create an MqttAndroidClient that can be used to communicate
+ * with an MQTT server on android
+ *
+ * @param context
+ * used to pass context to the callback.
+ * @param serverURI
+ * specifies the protocol, host name and port to be used to
+ * connect to an MQTT server
+ * @param clientId
+ * specifies the name by which this connection should be
+ * identified to the server
+ * @param persistence
+ * the persistence class to use to store in-flight message. If
+ * null then the default persistence mechanism is used
+ * @param ackType
+ * how the application wishes to acknowledge a message has been
+ * processed.
+ */
+ public MqttAndroidClient(Context context, String serverURI,
+ String clientId, MqttClientPersistence persistence, Ack ackType) {
+ myContext = context;
+ this.serverURI = serverURI;
+ this.clientId = clientId;
+ this.persistence = persistence;
+ messageAck = ackType;
+ }
+
+ /**
+ * Determines if this client is currently connected to the server.
+ *
+ * @return true if connected, false otherwise.
+ */
+ @Override
+ public boolean isConnected() {
+
+ return clientHandle != null && mqttService != null && mqttService.isConnected(clientHandle);
+ }
+
+ /**
+ * Returns the client ID used by this client.
+ *
+ * All clients connected to the same server or server farm must have a
+ * unique ID.
+ *
+ *
+ * @return the client ID used by this client.
+ */
+ @Override
+ public String getClientId() {
+ return clientId;
+ }
+
+ /**
+ * Returns the URI address of the server used by this client.
+ *
+ * The format of the returned String is the same as that used on the
+ * constructor.
+ *
+ *
+ * @return the server's address, as a URI String.
+ */
+ @Override
+ public String getServerURI() {
+ return serverURI;
+ }
+
+ /**
+ * Close the client. Releases all resource associated with the client. After
+ * the client has been closed it cannot be reused. For instance attempts to
+ * connect will fail.
+ *
+ */
+ @Override
+ public void close() {
+ if(mqttService != null){
+ if (clientHandle == null) {
+ clientHandle = mqttService.getClient(serverURI, clientId, myContext.getApplicationInfo().packageName,persistence);
+ }
+ mqttService.close(clientHandle);
+ }
+ }
+
+ /**
+ * Connects to an MQTT server using the default options.
+ *
+ * The default options are specified in {@link MqttConnectOptions} class.
+ *
+ *
+ * @throws MqttException
+ * for any connected problems
+ * @return token used to track and wait for the connect to complete. The
+ * token will be passed to the callback methods if a callback is
+ * set.
+ * @see #connect(MqttConnectOptions, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken connect() throws MqttException {
+ return connect(null, null);
+ }
+
+
+ /**
+ * Connects to an MQTT server using the provided connect options.
+ *
+ * The connection will be established using the options specified in the
+ * {@link MqttConnectOptions} parameter.
+ *
+ *
+ * @param options
+ * a set of connection parameters that override the defaults.
+ * @throws MqttException
+ * for any connected problems
+ * @return token used to track and wait for the connect to complete. The
+ * token will be passed to any callback that has been set.
+ * @see #connect(MqttConnectOptions, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken connect(MqttConnectOptions options) throws MqttException {
+ return connect(options, null, null);
+ }
+
+ /**
+ * Connects to an MQTT server using the default options.
+ *
+ * The default options are specified in {@link MqttConnectOptions} class.
+ *
+ *
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when the connect
+ * completes. Use null if not required.
+ * @throws MqttException
+ * for any connected problems
+ * @return token used to track and wait for the connect to complete. The
+ * token will be passed to any callback that has been set.
+ * @see #connect(MqttConnectOptions, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken connect(Object userContext, IMqttActionListener callback)
+ throws MqttException {
+ return connect(new MqttConnectOptions(), userContext, callback);
+ }
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
+ * The server to connect to is specified on the constructor. It is
+ * recommended to call {@link #setCallback(MqttCallback)} prior to
+ * connecting in order that messages destined for the client can be accepted
+ * as soon as the client is connected.
+ *
+ *
+ *
+ * The method returns control before the connect completes. Completion can
+ * be tracked by:
+ *
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener}
+ *
+ *
+ *
+ * @param options
+ * a set of connection parameters that override the defaults.
+ * @param userContext
+ * optional object for used to pass context to the callback. Use
+ * null if not required.
+ * @param callback
+ * optional listener that will be notified when the connect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the connect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for any connected problems, including communication errors
+ */
+
+ @Override
+ public IMqttToken connect(MqttConnectOptions options, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+
+ connectOptions = options;
+ connectToken = token;
+
+ /*
+ * The actual connection depends on the service, which we start and bind
+ * to here, but which we can't actually use until the serviceConnection
+ * onServiceConnected() method has run (asynchronously), so the
+ * connection itself takes place in the onServiceConnected() method
+ */
+ if (mqttService == null) { // First time - must bind to the service
+ Intent serviceStartIntent = new Intent();
+ serviceStartIntent.setClassName(myContext, SERVICE_NAME);
+ Object service = myContext.startService(serviceStartIntent);
+ if (service == null) {
+ IMqttActionListener listener = token.getActionCallback();
+ if (listener != null) {
+ listener.onFailure(token, new RuntimeException(
+ "cannot start service " + SERVICE_NAME));
+ }
+ }
+
+ // We bind with BIND_SERVICE_FLAG (0), leaving us the manage the lifecycle
+ // until the last time it is stopped by a call to stopService()
+ myContext.bindService(serviceStartIntent, serviceConnection,
+ Context.BIND_AUTO_CREATE);
+
+ if (!receiverRegistered) registerReceiver(this);
+ }
+ else {
+ pool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ doConnect();
+
+ //Register receiver to show shoulder tap.
+ if (!receiverRegistered) registerReceiver(MqttAndroidClient.this);
+ }
+
+ });
+ }
+
+ return token;
+ }
+
+ private void registerReceiver(BroadcastReceiver receiver) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(MqttServiceConstants.CALLBACK_TO_ACTIVITY);
+ LocalBroadcastManager.getInstance(myContext).registerReceiver(receiver, filter);
+ receiverRegistered = true;
+ }
+
+ /**
+ * Actually do the mqtt connect operation
+ */
+ private void doConnect() {
+ if (clientHandle == null) {
+ clientHandle = mqttService.getClient(serverURI, clientId,myContext.getApplicationInfo().packageName,
+ persistence);
+ }
+ mqttService.setTraceEnabled(traceEnabled);
+ mqttService.setTraceCallbackId(clientHandle);
+
+ String activityToken = storeToken(connectToken);
+ try {
+ mqttService.connect(clientHandle, connectOptions, null,
+ activityToken);
+ }
+ catch (MqttException e) {
+ IMqttActionListener listener = connectToken.getActionCallback();
+ if (listener != null) {
+ listener.onFailure(connectToken, e);
+ }
+ }
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of 30 seconds
+ * for work to quiesce before disconnecting. This method must not be called
+ * from inside {@link MqttCallback} methods.
+ *
+ *
+ * @return token used to track and wait for disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect() throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, null,
+ null);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of the
+ * specified quiesce time for work to complete before disconnecting. This
+ * method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work
+ * to finish before disconnecting. A value of zero or less means
+ * the client will not quiesce.
+ * @return token used to track and wait for disconnect to complete. The
+ * token will be passed to the callback methods if a callback is
+ * set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect(long quiesceTimeout) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, null,
+ null);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, quiesceTimeout, null,
+ activityToken);
+ return token;
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of 30 seconds
+ * for work to quiesce before disconnecting. This method must not be called
+ * from inside {@link MqttCallback} methods.
+ *
+ *
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when the disconnect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect(Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * The client will wait for {@link MqttCallback} methods to complete. It
+ * will then wait for up to the quiesce timeout to allow for work which has
+ * already been initiated to complete. For instance when a QoS 2 message has
+ * started flowing to the server but the QoS 2 flow has not completed.It
+ * prevents new messages being accepted and does not send any messages that
+ * have been accepted but not yet started delivery across the network to the
+ * server. When work has completed or after the quiesce timeout, the client
+ * will disconnect from the server. If the cleanSession flag was set to
+ * false and next time it is also set to false in the connection, the
+ * messages made in QoS 1 or 2 which were not previously delivered will be
+ * delivered this time.
+ *
+ *
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * The method returns control before the disconnect completes. Completion
+ * can be tracked by:
+ *
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener}
+ *
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work
+ * to finish before disconnecting. A value of zero or less means
+ * the client will not quiesce.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when the disconnect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ */
+ @Override
+ public IMqttToken disconnect(long quiesceTimeout, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, quiesceTimeout, null,
+ activityToken);
+ return token;
+ }
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * A convenience method, which will create a new {@link MqttMessage} object
+ * with a byte array payload and the specified QoS, and then publish it.
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service to deliver the message at. Valid values
+ * are 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance, too many messages are being processed.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained) throws MqttException, MqttPersistenceException {
+ return publish(topic, payload, qos, retained, null, null);
+ }
+
+ /**
+ * Publishes a message to a topic on the server. Takes an
+ * {@link MqttMessage} message and delivers it to the server at the
+ * requested quality of service.
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param message
+ * to deliver to the server
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, MqttMessage message)
+ throws MqttException, MqttPersistenceException {
+ return publish(topic, message, null, null);
+ }
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * A convenience method, which will create a new {@link MqttMessage} object
+ * with a byte array payload, the specified QoS and retained, then publish it.
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service to deliver the message at. Valid values
+ * are 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when message delivery
+ * has completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained, Object userContext, IMqttActionListener callback)
+ throws MqttException, MqttPersistenceException {
+
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid(
+ this, userContext, callback, message);
+ String activityToken = storeToken(token);
+ IMqttDeliveryToken internalToken = mqttService.publish(clientHandle,
+ topic, payload, qos, retained, null, activityToken);
+ token.setDelegate(internalToken);
+ return token;
+ }
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * Once this method has returned cleanly, the message has been accepted for
+ * publication by the client and will be delivered on a background thread.
+ * In the event the connection fails or the client stops, Messages will be
+ * delivered to the requested quality of service once the connection is
+ * re-established to the server on condition that:
+ *
+ *
+ *
The connection is re-established with the same clientID
+ *
The original connection was made with (@link
+ * MqttConnectOptions#setCleanSession(boolean)} set to false
+ *
The connection is re-established with (@link
+ * MqttConnectOptions#setCleanSession(boolean)} set to false
+ *
Depending when the failure occurs QoS 0 messages may not be
+ * delivered.
+ *
+ *
+ *
+ * When building an application, the design of the topic tree should take
+ * into account the following principles of topic name syntax and semantics:
+ *
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and
+ * Accounts are two different topics.
+ *
Topic names can include the space character. For example,
+ * Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example,
+ * /finance is different from finance. /finance
+ * matches "+/+" and "/+", but not "+".
+ *
Do not include the null character (Unicode \x0000) in any topic.
+ *
+ *
+ *
+ * The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to
+ * the number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any
+ * number of topic trees.
+ *
+ *
+ * The method returns control before the publish completes. Completion can
+ * be tracked by:
+ *
+ *
+ *
Setting an {@link IMqttAsyncClient#setCallback(MqttCallback)} where
+ * the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)} method will
+ * be called.
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param message
+ * to deliver to the server
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when message delivery
+ * has completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance, client not connected.
+ * @see MqttMessage
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, MqttMessage message,
+ Object userContext, IMqttActionListener callback)
+ throws MqttException, MqttPersistenceException {
+ MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid(
+ this, userContext, callback, message);
+ String activityToken = storeToken(token);
+ IMqttDeliveryToken internalToken = mqttService.publish(clientHandle,
+ topic, message, null, activityToken);
+ token.setDelegate(internalToken);
+ return token;
+ }
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @param topic
+ * the topic to subscribe to, which can include wildcards.
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at
+ * the published QoS. Messages published at a higher quality of
+ * service will be received using the QoS specified on the
+ * subscription.
+ * @return token used to track and wait for the subscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String topic, int qos) throws MqttException,
+ MqttSecurityException {
+ return subscribe(topic, qos, null, null);
+ }
+
+ /**
+ * Subscribe to multiple topics, each topic may include wildcards.
+ *
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * @param topic
+ * one or more topics to subscribe to, which can include
+ * wildcards
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at
+ * the published QoS. Messages published at a higher quality of
+ * service will be received using the QoS specified on the
+ * subscription.
+ * @return token used to track and wait for the subscription to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String[] topic, int[] qos)
+ throws MqttException, MqttSecurityException {
+ return subscribe(topic, qos, null, null);
+ }
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @param topic
+ * the topic to subscribe to, which can include wildcards.
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at
+ * the published QoS. Messages published at a higher quality of
+ * service will be received using the QoS specified on the
+ * subscription.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @return token used to track and wait for the subscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error when registering the subscription.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String topic, int qos, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback, new String[]{topic});
+ String activityToken = storeToken(token);
+ mqttService.subscribe(clientHandle, topic, qos, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Subscribes to multiple topics, each topic may include wildcards.
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * The {@link #setCallback(MqttCallback)} method should be called before
+ * this method, otherwise any received messages will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to true,
+ * when connecting to the server, the subscription remains in place until
+ * either:
+ *
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to false,
+ * when connecting to the server, the subscription remains in place
+ * until either:
+ *
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The next time the client connects with cleanSession set to true
+ *
+ *
With cleanSession set to false the MQTT server will store messages
+ * on behalf of the client when the client is not connected. The next time
+ * the client connects with the same client ID the server will
+ * deliver the stored messages to the client.
+ *
+ *
+ *
+ * The "topic filter" string is used when subscription may contain special
+ * characters, which allows you to subscribe to multiple topics at once.
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within a topic
+ * tree and provide a hierarchical structure to the topic space. The use of
+ * the topic level separator is significant when the two wildcard characters
+ * are encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
+ *
+ * The number sign (#) is a wildcard character that matches any number of
+ * levels within a topic. For example, if you subscribe to finance/stock/ibm/#, you receive messages
+ * on these topics:
+ *
+ *
+ *
finance/stock/ibm
+ *
finance/stock/ibm/closingprice
+ *
finance/stock/ibm/currentprice
+ *
+ *
+ *
+ * The multi-level wildcard can represent zero or more levels. Therefore,
+ * finance/# can also match the singular finance, where
+ * # represents zero levels. The topic level separator is
+ * meaningless in this context, because there are no levels to separate.
+ *
+ *
+ *
+ * The multi-level wildcard can be specified only on its own or
+ * next to the topic level separator character. Therefore, # and
+ * finance/# are both valid, but finance# is not valid.
+ * The multi-level wildcard must be the last character used within the
+ * topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
+ *
+ *
Single-level wildcard
+ *
+ *
+ * The plus sign (+) is a wildcard character that matches only one topic
+ * level. For example, finance/stock/+ matches
+ * finance/stock/ibm and finance/stock/xyz, but not
+ * finance/stock/ibm/closingprice. Also, because the single-level
+ * wildcard matches only a single level, finance/+ does not match
+ * finance.
+ *
+ *
+ *
+ * Use the single-level wildcard at any level in the topic tree, and in
+ * conjunction with the multilevel wildcard. Specify the single-level
+ * wildcard next to the topic level separator, except when it is specified
+ * on its own. Therefore, + and finance/+ are both valid,
+ * but finance+ is not valid. The single-level wildcard can
+ * be used at the end of the topic tree or within the topic tree. For
+ * example, finance/+ and finance/+/ibm are both
+ * valid.
+ *
+ *
+ *
+ *
+ * The method returns control before the subscribe completes. Completion can
+ * be tracked by:
+ *
+ *
+ *
Waiting on the supplied token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ * @param topic
+ * one or more topics to subscribe to, which can include
+ * wildcards
+ * @param qos
+ * the maximum quality of service to subscribe each topic
+ * at.Messages published at a lower quality of service will be
+ * received at the published QoS. Messages published at a higher
+ * quality of service will be received using the QoS specified on
+ * the subscription.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @return token used to track and wait for the subscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ * @throws IllegalArgumentException
+ * if the two supplied arrays are not the same size.
+ */
+ @Override
+ public IMqttToken subscribe(String[] topic, int[] qos, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback, topic);
+ String activityToken = storeToken(token);
+ mqttService.subscribe(clientHandle, topic, qos, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when subscribe
+ * has completed
+ * @param messageListener a callback to handle incoming messages
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback, IMqttMessageListener messageListener) throws MqttException {
+
+ return subscribe(new String[] {topicFilter}, new int[] {qos}, userContext, callback, new IMqttMessageListener[] {messageListener});
+ }
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param messageListener a callback to handle incoming messages
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos, IMqttMessageListener messageListener) throws MqttException {
+
+ return subscribe(topicFilter, qos, null, null, messageListener);
+ }
+
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param messageListeners an array of callbacks to handle incoming messages
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException {
+
+ return subscribe(topicFilters, qos, null, null, messageListeners);
+ }
+
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when subscribe
+ * has completed
+ * @param messageListeners an array of callbacks to handle incoming messages
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback, IMqttMessageListener[] messageListeners) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext, callback, topicFilters);
+ String activityToken = storeToken(token);
+ mqttService.subscribe(clientHandle, topicFilters, qos, null, activityToken, messageListeners);
+
+ return null;
+ }
+
+
+ /**
+ * Requests the server unsubscribe the client from a topic.
+ *
+ * @param topic
+ * the topic to unsubscribe from. It must match a topic specified
+ * on an earlier subscribe.
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String topic) throws MqttException {
+ return unsubscribe(topic, null, null);
+ }
+
+ /**
+ * Requests the server to unsubscribe the client from one or more topics.
+ *
+ * @param topic
+ * one or more topics to unsubscribe from. Each topic must match
+ * one specified on an earlier subscription.
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String[] topic) throws MqttException {
+ return unsubscribe(topic, null, null);
+ }
+
+ /**
+ * Requests the server to unsubscribe the client from a topics.
+ *
+ * @param topic
+ * the topic to unsubscribe from. It must match a topic specified
+ * on an earlier subscribe.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when unsubscribe has
+ * completed
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String topic, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.unsubscribe(clientHandle, topic, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Requests the server to unsubscribe the client from one or more topics.
+ *
+ * Unsubcribing is the opposite of subscribing. When the server receives the
+ * unsubscribe request it looks to see if it can find a matching
+ * subscription for the client and then removes it. After this point the
+ * server will send no more messages to the client for this subscription.
+ *
+ *
+ * The topic(s) specified on the unsubscribe must match the topic(s)
+ * specified in the original subscribe request for the unsubscribe to
+ * succeed
+ *
+ *
+ * The method returns control before the unsubscribe completes. Completion
+ * can be tracked by:
+ *
+ *
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ * @param topic
+ * one or more topics to unsubscribe from. Each topic must match
+ * one specified on an earlier subscription.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when unsubscribe has
+ * completed
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ */
+ @Override
+ public IMqttToken unsubscribe(String[] topic, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.unsubscribe(clientHandle, topic, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Returns the delivery tokens for any outstanding publish operations.
+ *
+ * If a client has been restarted and there are messages that were in the
+ * process of being delivered when the client stopped, this method returns a
+ * token for each in-flight message to enable the delivery to be tracked.
+ * Alternately the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}
+ * callback can be used to track the delivery of outstanding messages.
+ *
+ *
+ * If a client connects with cleanSession true then there will be no
+ * delivery tokens as the cleanSession option deletes all earlier state. For
+ * state to be remembered the client must connect with cleanSession set to
+ * false
+ *
+ *
+ * @return zero or more delivery tokens
+ */
+ @Override
+ public IMqttDeliveryToken[] getPendingDeliveryTokens() {
+ return mqttService.getPendingDeliveryTokens(clientHandle);
+ }
+
+ /**
+ * Sets a callback listener to use for events that happen asynchronously.
+ *
+ * There are a number of events that the listener will be notified about.
+ * These include:
+ *
+ *
+ *
A new message has arrived and is ready to be processed
+ *
The connection to the server has been lost
+ *
Delivery of a message to the server has completed
+ *
+ *
+ * Other events that track the progress of an individual operation such as
+ * connect and subscribe can be tracked using the {@link MqttToken} returned
+ * from each non-blocking method or using setting a
+ * {@link IMqttActionListener} on the non-blocking method.
+ *
+ *
+ * @param callback
+ * which will be invoked for certain asynchronous events
+ *
+ * @see MqttCallback
+ */
+ @Override
+ public void setCallback(MqttCallback callback) {
+ this.callback = callback;
+
+ }
+
+ /**
+ * identify the callback to be invoked when making tracing calls back into
+ * the Activity
+ *
+ * @param traceCallback handler
+ */
+ public void setTraceCallback(MqttTraceHandler traceCallback) {
+ this.traceCallback = traceCallback;
+ // mqttService.setTraceCallbackId(traceCallbackId);
+ }
+
+ /**
+ * turn tracing on and off
+ *
+ * @param traceEnabled
+ * set true to enable trace, otherwise, set
+ * false to disable trace
+ *
+ */
+ public void setTraceEnabled(boolean traceEnabled) {
+ this.traceEnabled = traceEnabled;
+ if (mqttService !=null)
+ mqttService.setTraceEnabled(traceEnabled);
+ }
+
+ /**
+ *
+ * Process incoming Intent objects representing the results of operations
+ * and asynchronous activities such as message received
+ *
+ *
+ * Note: This is only a public method because the Android
+ * APIs require such.
+ * This method should not be explicitly invoked.
+ *
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle data = intent.getExtras();
+
+ String handleFromIntent = data
+ .getString(MqttServiceConstants.CALLBACK_CLIENT_HANDLE);
+
+ if ((handleFromIntent == null)
+ || (!handleFromIntent.equals(clientHandle))) {
+ return;
+ }
+
+ String action = data.getString(MqttServiceConstants.CALLBACK_ACTION);
+
+ if (MqttServiceConstants.CONNECT_ACTION.equals(action)) {
+ connectAction(data);
+ }
+ else if (MqttServiceConstants.CONNECT_EXTENDED_ACTION.equals(action)){
+ connectExtendedAction(data);
+ }
+ else if (MqttServiceConstants.MESSAGE_ARRIVED_ACTION.equals(action)) {
+ messageArrivedAction(data);
+ }
+ else if (MqttServiceConstants.SUBSCRIBE_ACTION.equals(action)) {
+ subscribeAction(data);
+ }
+ else if (MqttServiceConstants.UNSUBSCRIBE_ACTION.equals(action)) {
+ unSubscribeAction(data);
+ }
+ else if (MqttServiceConstants.SEND_ACTION.equals(action)) {
+ sendAction(data);
+ }
+ else if (MqttServiceConstants.MESSAGE_DELIVERED_ACTION.equals(action)) {
+ messageDeliveredAction(data);
+ }
+ else if (MqttServiceConstants.ON_CONNECTION_LOST_ACTION
+ .equals(action)) {
+ connectionLostAction(data);
+ }
+ else if (MqttServiceConstants.DISCONNECT_ACTION.equals(action)) {
+ disconnected(data);
+ }
+ else if (MqttServiceConstants.TRACE_ACTION.equals(action)) {
+ traceAction(data);
+ }else{
+ mqttService.traceError(MqttService.TAG, "Callback action doesn't exist.");
+ }
+
+ }
+
+ /**
+ * Acknowledges a message received on the
+ * {@link MqttCallback#messageArrived(String, MqttMessage)}
+ *
+ * @param messageId
+ * the messageId received from the MqttMessage (To access this
+ * field you need to cast {@link MqttMessage} to
+ * {@link ParcelableMqttMessage})
+ * @return whether or not the message was successfully acknowledged
+ */
+ public boolean acknowledgeMessage(String messageId) {
+ if (messageAck == Ack.MANUAL_ACK) {
+ Status status = mqttService.acknowledgeMessageArrival(clientHandle, messageId);
+ return status == Status.OK;
+ }
+ return false;
+
+ }
+
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setManualAcks(boolean manualAcks) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Process the results of a connection
+ *
+ * @param data
+ */
+ private void connectAction(Bundle data) {
+ IMqttToken token = connectToken;
+ removeMqttToken(data);
+
+ simpleAction(token, data);
+ }
+
+
+
+ /**
+ * Process a notification that we have disconnected
+ *
+ * @param data
+ */
+ private void disconnected(Bundle data) {
+ clientHandle = null; // avoid reuse!
+ IMqttToken token = removeMqttToken(data);
+ if (token != null) {
+ ((MqttTokenAndroid) token).notifyComplete();
+ }
+ if (callback != null) {
+ callback.connectionLost(null);
+ }
+ }
+
+ /**
+ * Process a Connection Lost notification
+ *
+ * @param data
+ */
+ private void connectionLostAction(Bundle data) {
+ if (callback != null) {
+ Exception reason = (Exception) data
+ .getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
+ callback.connectionLost(reason);
+ }
+ }
+
+ private void connectExtendedAction(Bundle data){
+ // This is called differently from a normal connect
+
+ if(callback instanceof MqttCallbackExtended){
+ boolean reconnect = data.getBoolean(MqttServiceConstants.CALLBACK_RECONNECT, false);
+ String serverURI = data.getString(MqttServiceConstants.CALLBACK_SERVER_URI);
+ ((MqttCallbackExtended) callback).connectComplete(reconnect, serverURI);
+ }
+
+ }
+
+ /**
+ * Common processing for many notifications
+ *
+ * @param token
+ * the token associated with the action being undertake
+ * @param data
+ * the result data
+ */
+ private void simpleAction(IMqttToken token, Bundle data) {
+ if (token != null) {
+ Status status = (Status) data
+ .getSerializable(MqttServiceConstants.CALLBACK_STATUS);
+ if (status == Status.OK) {
+ ((MqttTokenAndroid) token).notifyComplete();
+ }
+ else {
+ Exception exceptionThrown = (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
+ ((MqttTokenAndroid) token).notifyFailure(exceptionThrown);
+ }
+ } else {
+ mqttService.traceError(MqttService.TAG, "simpleAction : token is null");
+ }
+ }
+
+ /**
+ * Process notification of a publish(send) operation
+ *
+ * @param data
+ */
+ private void sendAction(Bundle data) {
+ IMqttToken token = getMqttToken(data); // get, don't remove - will
+ // remove on delivery
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process notification of a subscribe operation
+ *
+ * @param data
+ */
+ private void subscribeAction(Bundle data) {
+ IMqttToken token = removeMqttToken(data);
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process notification of an unsubscribe operation
+ *
+ * @param data
+ */
+ private void unSubscribeAction(Bundle data) {
+ IMqttToken token = removeMqttToken(data);
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process notification of a published message having been delivered
+ *
+ * @param data
+ */
+ private void messageDeliveredAction(Bundle data) {
+ IMqttToken token = removeMqttToken(data);
+ if (token != null) {
+ if (callback != null) {
+ Status status = (Status) data
+ .getSerializable(MqttServiceConstants.CALLBACK_STATUS);
+ if (status == Status.OK && token instanceof IMqttDeliveryToken) {
+ callback.deliveryComplete((IMqttDeliveryToken) token);
+ }
+ }
+ }
+ }
+
+ /**
+ * Process notification of a message's arrival
+ *
+ * @param data
+ */
+ private void messageArrivedAction(Bundle data) {
+ if (callback != null) {
+ String messageId = data
+ .getString(MqttServiceConstants.CALLBACK_MESSAGE_ID);
+ String destinationName = data
+ .getString(MqttServiceConstants.CALLBACK_DESTINATION_NAME);
+
+ ParcelableMqttMessage message = data
+ .getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
+ try {
+ if (messageAck == Ack.AUTO_ACK) {
+ callback.messageArrived(destinationName, message);
+ mqttService.acknowledgeMessageArrival(clientHandle, messageId);
+ }
+ else {
+ message.messageId = messageId;
+ callback.messageArrived(destinationName, message);
+ }
+
+ // let the service discard the saved message details
+ }
+ catch (Exception e) {
+ // Swallow the exception
+ }
+ }
+ }
+
+ /**
+ * Process trace action - pass trace data back to the callback
+ *
+ * @param data
+ */
+ private void traceAction(Bundle data) {
+
+ if (traceCallback != null) {
+ String severity = data.getString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY);
+ String message = data.getString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE);
+ String tag = data.getString(MqttServiceConstants.CALLBACK_TRACE_TAG);
+ if (MqttServiceConstants.TRACE_DEBUG.equals(severity))
+ traceCallback.traceDebug(tag, message);
+ else if (MqttServiceConstants.TRACE_ERROR.equals(severity))
+ traceCallback.traceError(tag, message);
+ else
+ {
+ Exception e = (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
+ traceCallback.traceException(tag, message, e);
+ }
+ }
+ }
+
+ /**
+ * @param token
+ * identifying an operation
+ * @return an identifier for the token which can be passed to the Android
+ * Service
+ */
+ private synchronized String storeToken(IMqttToken token) {
+ tokenMap.put(tokenNumber, token);
+ return Integer.toString(tokenNumber++);
+ }
+
+ /**
+ * Get a token identified by a string, and remove it from our map
+ *
+ * @param data
+ * @return the token
+ */
+ private synchronized IMqttToken removeMqttToken(Bundle data) {
+
+ String activityToken = data.getString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN);
+ if (activityToken!=null){
+ int tokenNumber = Integer.parseInt(activityToken);
+ IMqttToken token = tokenMap.get(tokenNumber);
+ tokenMap.delete(tokenNumber);
+ return token;
+ }
+ return null;
+ }
+
+ /**
+ * Get a token identified by a string, and remove it from our map
+ *
+ * @param data
+ * @return the token
+ */
+ private synchronized IMqttToken getMqttToken(Bundle data) {
+ String activityToken = data
+ .getString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN);
+ return tokenMap.get(Integer.parseInt(activityToken));
+ }
+
+ /**
+ * Sets the DisconnectedBufferOptions for this client
+ * @param bufferOpts the DisconnectedBufferOptions
+ */
+ public void setBufferOpts(DisconnectedBufferOptions bufferOpts) {
+ mqttService.setBufferOpts(clientHandle, bufferOpts);
+ }
+
+ public int getBufferedMessageCount(){
+ return mqttService.getBufferedMessageCount(clientHandle);
+ }
+
+ public MqttMessage getBufferedMessage(int bufferIndex){
+ return mqttService.getBufferedMessage(clientHandle, bufferIndex);
+ }
+
+ public void deleteBufferedMessage(int bufferIndex){
+ mqttService.deleteBufferedMessage(clientHandle, bufferIndex);
+ }
+
+ /**
+ * Get the SSLSocketFactory using SSL key store and password
+ *
+ * A convenience method, which will help user to create a SSLSocketFactory
+ * object
+ *
+ *
+ * @param keyStore
+ * the SSL key store which is generated by some SSL key tool,
+ * such as keytool in Java JDK
+ * @param password
+ * the password of the key store which is set when the key store
+ * is generated
+ * @return SSLSocketFactory used to connect to the server with SSL
+ * authentication
+ * @throws MqttSecurityException
+ * if there was any error when getting the SSLSocketFactory
+ */
+ public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
+ try{
+ SSLContext ctx = null;
+ SSLSocketFactory sslSockFactory=null;
+ KeyStore ts;
+ ts = KeyStore.getInstance("BKS");
+ ts.load(keyStore, password.toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ tmf.init(ts);
+ TrustManager[] tm = tmf.getTrustManagers();
+ ctx = SSLContext.getInstance("TLSv1");
+ ctx.init(null, tm, null);
+
+ sslSockFactory=ctx.getSocketFactory();
+ return sslSockFactory;
+
+ } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
+ throw new MqttSecurityException(e);
+ }
+ }
+
+ @Override
+ public void disconnectForcibly() throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout)
+ throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Unregister receiver which receives intent from MqttService avoids
+ * IntentReceiver leaks.
+ */
+ public void unregisterResources(){
+ if(myContext != null && receiverRegistered){
+ synchronized (MqttAndroidClient.this) {
+ LocalBroadcastManager.getInstance(myContext).unregisterReceiver(this);
+ receiverRegistered = false;
+ }
+ if(bindedService){
+ try{
+ myContext.unbindService(serviceConnection);
+ bindedService = false;
+ }catch(IllegalArgumentException e){
+ //Ignore unbind issue.
+ }
+ }
+ }
+ }
+
+ /**
+ * Register receiver to receiver intent from MqttService. Call this method
+ * when activity is hidden and become to show again.
+ *
+ * @param context
+ * - Current activity context.
+ */
+ public void registerResources(Context context){
+ if(context != null){
+ this.myContext = context;
+ if(!receiverRegistered){
+ registerReceiver(this);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttConnection.java b/app/src/main/java/org/eclipse/paho/android/service/MqttConnection.java
new file mode 100644
index 0000000..a744efe
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttConnection.java
@@ -0,0 +1,1146 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2016 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.paho.android.service.MessageStore.StoredMessage;
+import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+
+import android.app.Service;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ *
+ * MqttConnection holds a MqttAsyncClient {host,port,clientId} instance to perform
+ * MQTT operations to MQTT broker.
+ *
+ *
+ * Most of the major API here is intended to implement the most general forms of
+ * the methods in IMqttAsyncClient, with slight adjustments for the Android
+ * environment
+ * These adjustments usually consist of adding two parameters to each method :-
+ *
+ *
invocationContext - a string passed from the application to identify the
+ * context of the operation (mainly included for support of the javascript API
+ * implementation)
+ *
activityToken - a string passed from the Activity to relate back to a
+ * callback method or other context-specific data
+ *
+ *
+ *
+ * Operations are very much asynchronous, so success and failure are notified by
+ * packing the relevant data into Intent objects which are broadcast back to the
+ * Activity via the MqttService.callbackToActivity() method.
+ *
+ */
+class MqttConnection implements MqttCallbackExtended {
+
+ // Strings for Intents etc..
+ private static final String TAG = "MqttConnection";
+ // Error status messages
+ private static final String NOT_CONNECTED = "not connected";
+
+ // fields for the connection definition
+ private String serverURI;
+ public String getServerURI() {
+ return serverURI;
+ }
+
+ public void setServerURI(String serverURI) {
+ this.serverURI = serverURI;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ private String clientId;
+ private MqttClientPersistence persistence = null;
+ private MqttConnectOptions connectOptions;
+
+ public MqttConnectOptions getConnectOptions() {
+ return connectOptions;
+ }
+
+ public void setConnectOptions(MqttConnectOptions connectOptions) {
+ this.connectOptions = connectOptions;
+ }
+
+ // Client handle, used for callbacks...
+ private String clientHandle;
+
+ public String getClientHandle() {
+ return clientHandle;
+ }
+
+ public void setClientHandle(String clientHandle) {
+ this.clientHandle = clientHandle;
+ }
+
+ //store connect ActivityToken for reconnect
+ private String reconnectActivityToken = null;
+
+ // our client object - instantiated on connect
+ private MqttAsyncClient myClient = null;
+
+ //private AlarmPingSender alarmPingSender = null;
+ private RitoPingSender alarmPingSender = null;
+
+ // our (parent) service object
+ private MqttService service = null;
+
+ private volatile boolean disconnected = true;
+ private boolean cleanSession = true;
+
+ // Indicate this connection is connecting or not.
+ // This variable uses to avoid reconnect multiple times.
+ private volatile boolean isConnecting = false;
+
+ // Saved sent messages and their corresponding Topics, activityTokens and
+ // invocationContexts, so we can handle "deliveryComplete" callbacks
+ // from the mqttClient
+ private Map savedTopics = new HashMap<>();
+ private Map savedSentMessages = new HashMap<>();
+ private Map savedActivityTokens = new HashMap<>();
+ private Map savedInvocationContexts = new HashMap<>();
+
+ private WakeLock wakelock = null;
+ private String wakeLockTag = null;
+
+ private DisconnectedBufferOptions bufferOpts = null;
+
+ /**
+ * Constructor - create an MqttConnection to communicate with MQTT server
+ *
+ * @param service
+ * our "parent" service - we make callbacks to it
+ * @param serverURI
+ * the URI of the MQTT server to which we will connect
+ * @param clientId
+ * the name by which we will identify ourselves to the MQTT
+ * server
+ * @param persistence
+ * the persistence class to use to store in-flight message. If
+ * null then the default persistence mechanism is used
+ * @param clientHandle
+ * the "handle" by which the activity will identify us
+ */
+ MqttConnection(MqttService service, String serverURI, String clientId,
+ MqttClientPersistence persistence, String clientHandle) {
+ this.serverURI = serverURI;
+ this.service = service;
+ this.clientId = clientId;
+ this.persistence = persistence;
+ this.clientHandle = clientHandle;
+
+ StringBuilder stringBuilder = new StringBuilder(this.getClass().getCanonicalName());
+ stringBuilder.append(" ");
+ stringBuilder.append(clientId);
+ stringBuilder.append(" ");
+ stringBuilder.append("on host ");
+ stringBuilder.append(serverURI);
+ wakeLockTag = stringBuilder.toString();
+ }
+
+ // The major API implementation follows
+ /**
+ * Connect to the server specified when we were instantiated
+ *
+ * @param options
+ * timeout, etc
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void connect(MqttConnectOptions options, String invocationContext,
+ String activityToken) {
+
+ connectOptions = options;
+ reconnectActivityToken = activityToken;
+
+ if (options != null) {
+ cleanSession = options.isCleanSession();
+ }
+
+ if (connectOptions.isCleanSession()) { // if it's a clean session,
+ // discard old data
+ service.messageStore.clearArrivedMessages(clientHandle);
+ }
+
+ service.traceDebug(TAG, "Connecting {" + serverURI + "} as {" + clientId + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.CONNECT_ACTION);
+
+
+ try {
+ if (persistence == null) {
+ // ask Android where we can put files
+ File myDir = service.getExternalFilesDir(TAG);
+
+ if (myDir == null) {
+ // No external storage, use internal storage instead.
+ myDir = service.getDir(TAG, Context.MODE_PRIVATE);
+
+ if(myDir == null){
+ //Shouldn't happen.
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ "Error! No external and internal storage available");
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, new MqttPersistenceException());
+ service.callbackToActivity(clientHandle, Status.ERROR,
+ resultBundle);
+ return;
+ }
+ }
+
+ // use that to setup MQTT client persistence storage
+ persistence = new MqttDefaultFilePersistence(
+ myDir.getAbsolutePath());
+ }
+
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle) {
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ doAfterConnectSuccess(resultBundle);
+ service.traceDebug(TAG, "connect success!");
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken,
+ Throwable exception) {
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ exception.getLocalizedMessage());
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, exception);
+ service.traceError(TAG,
+ "connect fail, call connect to reconnect.reason:"
+ + exception.getMessage());
+
+ doAfterConnectFail(resultBundle);
+
+ }
+ };
+
+ if (myClient != null) {
+ if (isConnecting ) {
+ service.traceDebug(TAG,
+ "myClient != null and the client is connecting. Connect return directly.");
+ service.traceDebug(TAG,"Connect return:isConnecting:"+isConnecting+".disconnected:"+disconnected);
+ }else if(!disconnected){
+ service.traceDebug(TAG,"myClient != null and the client is connected and notify!");
+ doAfterConnectSuccess(resultBundle);
+ }
+ else {
+ service.traceDebug(TAG, "myClient != null and the client is not connected");
+ service.traceDebug(TAG,"Do Real connect!");
+ setConnectingState(true);
+ myClient.connect(connectOptions, invocationContext, listener);
+ }
+ }
+
+ // if myClient is null, then create a new connection
+ else {
+ //alarmPingSender = new AlarmPingSender(service);
+ alarmPingSender = new RitoPingSender();
+ myClient = new MqttAsyncClient(serverURI, clientId,
+ persistence, alarmPingSender);
+ myClient.setCallback(this);
+
+ service.traceDebug(TAG,"Do Real connect!");
+ setConnectingState(true);
+ myClient.connect(connectOptions, invocationContext, listener);
+ }
+ } catch (Exception e) {
+ service.traceError(TAG, "Exception occurred attempting to connect: " + e.getMessage());
+ setConnectingState(false);
+ handleException(resultBundle, e);
+ }
+ }
+
+ private void doAfterConnectSuccess(final Bundle resultBundle) {
+ //since the device's cpu can go to sleep, acquire a wakelock and drop it later.
+ acquireWakeLock();
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ deliverBacklog();
+ setConnectingState(false);
+ disconnected = false;
+ releaseWakeLock();
+ }
+
+ @Override
+ public void connectComplete(boolean reconnect, String serverURI) {
+ Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.CONNECT_EXTENDED_ACTION);
+ resultBundle.putBoolean(MqttServiceConstants.CALLBACK_RECONNECT, reconnect);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_SERVER_URI, serverURI);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+
+ private void doAfterConnectFail(final Bundle resultBundle){
+ //
+ acquireWakeLock();
+ disconnected = true;
+ setConnectingState(false);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ releaseWakeLock();
+ }
+
+ private void handleException(final Bundle resultBundle, Exception e) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ e.getLocalizedMessage());
+
+ resultBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e);
+
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ /**
+ * Attempt to deliver any outstanding messages we've received but which the
+ * application hasn't acknowledged. If "cleanSession" was specified, we'll
+ * have already purged any such messages from our messageStore.
+ */
+ private void deliverBacklog() {
+ Iterator backlog = service.messageStore
+ .getAllArrivedMessages(clientHandle);
+ while (backlog.hasNext()) {
+ StoredMessage msgArrived = backlog.next();
+ Bundle resultBundle = messageToBundle(msgArrived.getMessageId(),
+ msgArrived.getTopic(), msgArrived.getMessage());
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.MESSAGE_ARRIVED_ACTION);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+ }
+
+ /**
+ * Create a bundle containing all relevant data pertaining to a message
+ *
+ * @param messageId
+ * the message's identifier in the messageStore, so that a
+ * callback can be made to remove it once delivered
+ * @param topic
+ * the topic on which the message was delivered
+ * @param message
+ * the message itself
+ * @return the bundle
+ */
+ private Bundle messageToBundle(String messageId, String topic,
+ MqttMessage message) {
+ Bundle result = new Bundle();
+ result.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID, messageId);
+ result.putString(MqttServiceConstants.CALLBACK_DESTINATION_NAME, topic);
+ result.putParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL,
+ new ParcelableMqttMessage(message));
+ return result;
+ }
+
+ /**
+ * Close connection from the server
+ *
+ */
+ void close() {
+ service.traceDebug(TAG, "close()");
+ try {
+ if (myClient != null) {
+ myClient.close();
+ }
+ } catch (MqttException e) {
+ // Pass a new bundle, let handleException stores error messages.
+ handleException(new Bundle(), e);
+ }
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param quiesceTimeout
+ * in milliseconds
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ */
+ void disconnect(long quiesceTimeout, String invocationContext,
+ String activityToken) {
+ service.traceDebug(TAG, "disconnect()");
+ disconnected = true;
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.DISCONNECT_ACTION);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.disconnect(quiesceTimeout, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.DISCONNECT_ACTION,
+ NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ if (connectOptions != null && connectOptions.isCleanSession()) {
+ // assume we'll clear the stored messages at this point
+ service.messageStore.clearArrivedMessages(clientHandle);
+ }
+
+ releaseWakeLock();
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ */
+ void disconnect(String invocationContext, String activityToken) {
+ service.traceDebug(TAG, "disconnect()");
+ disconnected = true;
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.DISCONNECT_ACTION);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.disconnect(invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.DISCONNECT_ACTION,
+ NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ if (connectOptions != null && connectOptions.isCleanSession()) {
+ // assume we'll clear the stored messages at this point
+ service.messageStore.clearArrivedMessages(clientHandle);
+ }
+ releaseWakeLock();
+ }
+
+ /**
+ * @return true if we are connected to an MQTT server
+ */
+ public boolean isConnected() {
+ return myClient != null && myClient.isConnected();
+ }
+
+ /**
+ * Publish a message on a topic
+ *
+ * @param topic
+ * the topic on which to publish - represented as a string, not
+ * an MqttTopic object
+ * @param payload
+ * the content of the message to publish
+ * @param qos
+ * the quality of service requested
+ * @param retained
+ * whether the MQTT server should retain this message
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained, String invocationContext, String activityToken) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SEND_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ IMqttDeliveryToken sendToken = null;
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ sendToken = myClient.publish(topic, payload, qos, retained,
+ invocationContext, listener);
+ storeSendDetails(topic, message, sendToken, invocationContext,
+ activityToken);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ return sendToken;
+ }
+
+ /**
+ * Publish a message on a topic
+ *
+ * @param topic
+ * the topic on which to publish - represented as a string, not
+ * an MqttTopic object
+ * @param message
+ * the message to publish
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String topic, MqttMessage message,
+ String invocationContext, String activityToken) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SEND_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ IMqttDeliveryToken sendToken = null;
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ sendToken = myClient.publish(topic, message, invocationContext,
+ listener);
+ storeSendDetails(topic, message, sendToken, invocationContext,
+ activityToken);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else if ((myClient !=null) && (this.bufferOpts != null) && (this.bufferOpts.isBufferEnabled())){
+ // Client is not connected, but buffer is enabled, so sending message
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ sendToken = myClient.publish(topic, message, invocationContext,
+ listener);
+ storeSendDetails(topic, message, sendToken, invocationContext,
+ activityToken);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ Log.i(TAG, "Client is not connected, so not sending message");
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ return sendToken;
+ }
+
+ /**
+ * Subscribe to a topic
+ *
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param qos
+ * requested quality of service for the topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(final String topic, final int qos,
+ String invocationContext, String activityToken) {
+ service.traceDebug(TAG, "subscribe({" + topic + "}," + qos + ",{"
+ + invocationContext + "}, {" + activityToken + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.subscribe(topic, qos, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Subscribe to one or more topics
+ *
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param qos
+ * requested quality of service for each topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(final String[] topic, final int[] qos,
+ String invocationContext, String activityToken) {
+ service.traceDebug(TAG, "subscribe({" + Arrays.toString(topic) + "}," + Arrays.toString(qos) + ",{"
+ + invocationContext + "}, {" + activityToken + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.subscribe(topic, qos, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ public void subscribe(String[] topicFilters, int[] qos, String invocationContext, String activityToken, IMqttMessageListener[] messageListeners) {
+ service.traceDebug(TAG, "subscribe({" + Arrays.toString(topicFilters) + "}," + Arrays.toString(qos) + ",{"
+ + invocationContext + "}, {" + activityToken + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.SUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, activityToken);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, invocationContext);
+ if((myClient != null) && (myClient.isConnected())){
+ IMqttActionListener listener = new MqttConnectionListener(resultBundle);
+ try {
+
+ myClient.subscribe(topicFilters, qos,messageListeners);
+ } catch (Exception e){
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, NOT_CONNECTED);
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Unsubscribe from a topic
+ *
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ void unsubscribe(final String topic, String invocationContext,
+ String activityToken) {
+ service.traceDebug(TAG, "unsubscribe({" + topic + "},{"
+ + invocationContext + "}, {" + activityToken + "})");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.UNSUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.unsubscribe(topic, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Unsubscribe from one or more topics
+ *
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ void unsubscribe(final String[] topic, String invocationContext,
+ String activityToken) {
+ service.traceDebug(TAG, "unsubscribe({" + Arrays.toString(topic) + "},{"
+ + invocationContext + "}, {" + activityToken + "})");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.UNSUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.unsubscribe(topic, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Get tokens for all outstanding deliveries for a client
+ *
+ * @return an array (possibly empty) of tokens
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens() {
+ return myClient.getPendingDeliveryTokens();
+ }
+
+ // Implement MqttCallback
+ /**
+ * Callback for connectionLost
+ *
+ * @param why
+ * the exeception causing the break in communications
+ */
+ @Override
+ public void connectionLost(Throwable why) {
+ service.traceDebug(TAG, "connectionLost(" + why.getMessage() + ")");
+ disconnected = true;
+ try {
+ if(!this.connectOptions.isAutomaticReconnect()) {
+ myClient.disconnect(null, new IMqttActionListener() {
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ // No action
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken,
+ Throwable exception) {
+ // No action
+ }
+ });
+ } else {
+ // Using the new Automatic reconnect functionality.
+ // We can't force a disconnection, but we can speed one up
+ alarmPingSender.schedule(100);
+
+ }
+ } catch (Exception e) {
+ // ignore it - we've done our best
+ }
+
+ Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.ON_CONNECTION_LOST_ACTION);
+ if (why != null) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ why.getMessage());
+ if (why instanceof MqttException) {
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, why);
+ }
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_EXCEPTION_STACK,
+ Log.getStackTraceString(why));
+ }
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ // client has lost connection no need for wake lock
+ releaseWakeLock();
+ }
+
+ /**
+ * Callback to indicate a message has been delivered (the exact meaning of
+ * "has been delivered" is dependent on the QOS value)
+ *
+ * @param messageToken
+ * the messge token provided when the message was originally sent
+ */
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken messageToken) {
+
+ service.traceDebug(TAG, "deliveryComplete(" + messageToken + ")");
+
+ MqttMessage message = savedSentMessages.remove(messageToken);
+ if (message != null) { // If I don't know about the message, it's
+ // irrelevant
+ String topic = savedTopics.remove(messageToken);
+ String activityToken = savedActivityTokens.remove(messageToken);
+ String invocationContext = savedInvocationContexts
+ .remove(messageToken);
+
+ Bundle resultBundle = messageToBundle(null, topic, message);
+ if (activityToken != null) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SEND_ACTION);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ service.callbackToActivity(clientHandle, Status.OK,
+ resultBundle);
+ }
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.MESSAGE_DELIVERED_ACTION);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+
+ // this notification will have kept the connection alive but send the previously sechudled ping anyway
+ }
+
+ /**
+ * Callback when a message is received
+ *
+ * @param topic
+ * the topic on which the message was received
+ * @param message
+ * the message itself
+ */
+ @Override
+ public void messageArrived(String topic, MqttMessage message)
+ throws Exception {
+
+ service.traceDebug(TAG,
+ "messageArrived(" + topic + ",{" + message.toString() + "})");
+
+ String messageId = service.messageStore.storeArrived(clientHandle,
+ topic, message);
+
+ Bundle resultBundle = messageToBundle(messageId, topic, message);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.MESSAGE_ARRIVED_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID,
+ messageId);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+
+ }
+
+
+
+ /**
+ * Store details of sent messages so we can handle "deliveryComplete"
+ * callbacks from the mqttClient
+ *
+ * @param topic
+ * @param msg
+ * @param messageToken
+ * @param invocationContext
+ * @param activityToken
+ */
+ private void storeSendDetails(final String topic, final MqttMessage msg,
+ final IMqttDeliveryToken messageToken,
+ final String invocationContext, final String activityToken) {
+ savedTopics.put(messageToken, topic);
+ savedSentMessages.put(messageToken, msg);
+ savedActivityTokens.put(messageToken, activityToken);
+ savedInvocationContexts.put(messageToken, invocationContext);
+ }
+
+ /**
+ * Acquires a partial wake lock for this client
+ */
+ private void acquireWakeLock() {
+ if (wakelock == null) {
+ PowerManager pm = (PowerManager) service
+ .getSystemService(Service.POWER_SERVICE);
+ wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ wakeLockTag);
+ }
+ wakelock.acquire();
+
+ }
+
+ /**
+ * Releases the currently held wake lock for this client
+ */
+ private void releaseWakeLock() {
+ if(wakelock != null && wakelock.isHeld()){
+ wakelock.release();
+ }
+ }
+
+
+
+ /**
+ * General-purpose IMqttActionListener for the Client context
+ *
+ * Simply handles the basic success/failure cases for operations which don't
+ * return results
+ *
+ */
+ private class MqttConnectionListener implements IMqttActionListener {
+
+ private final Bundle resultBundle;
+
+ private MqttConnectionListener(Bundle resultBundle) {
+ this.resultBundle = resultBundle;
+ }
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ exception.getLocalizedMessage());
+
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, exception);
+
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Receive notification that we are offline
+ * if cleanSession is true, we need to regard this as a disconnection
+ */
+ void offline() {
+
+ if (!disconnected && !cleanSession) {
+ Exception e = new Exception("Android offline");
+ connectionLost(e);
+ }
+ }
+
+ /**
+ * Reconnect
+ * Only appropriate if cleanSession is false and we were connected.
+ * Declare as synchronized to avoid multiple calls to this method to send connect
+ * multiple times
+ */
+ synchronized void reconnect() {
+
+ if (myClient == null) {
+ service.traceError(TAG,"Reconnect myClient = null. Will not do reconnect");
+ return;
+ }
+
+ if (isConnecting) {
+ service.traceDebug(TAG, "The client is connecting. Reconnect return directly.");
+ return ;
+ }
+
+ if(!service.isOnline()){
+ service.traceDebug(TAG,
+ "The network is not reachable. Will not do reconnect");
+ return;
+ }
+
+ if(connectOptions.isAutomaticReconnect()){
+ //The Automatic reconnect functionality is enabled here
+ Log.i(TAG, "Requesting Automatic reconnect using New Java AC");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ reconnectActivityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.CONNECT_ACTION);
+ try {
+ myClient.reconnect();
+ } catch (MqttException ex){
+ Log.e(TAG, "Exception occurred attempting to reconnect: " + ex.getMessage());
+ setConnectingState(false);
+ handleException(resultBundle, ex);
+ }
+ } else if (disconnected && !cleanSession) {
+ // use the activityToke the same with action connect
+ service.traceDebug(TAG,"Do Real Reconnect!");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ reconnectActivityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.CONNECT_ACTION);
+
+ try {
+
+ IMqttActionListener listener = new MqttConnectionListener(resultBundle) {
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ // since the device's cpu can go to sleep, acquire a
+ // wakelock and drop it later.
+ service.traceDebug(TAG,"Reconnect Success!");
+ service.traceDebug(TAG,"DeliverBacklog when reconnect.");
+ doAfterConnectSuccess(resultBundle);
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ exception.getLocalizedMessage());
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION,
+ exception);
+ service.callbackToActivity(clientHandle, Status.ERROR,
+ resultBundle);
+
+ doAfterConnectFail(resultBundle);
+
+ }
+ };
+
+ myClient.connect(connectOptions, null, listener);
+ setConnectingState(true);
+ } catch (MqttException e) {
+ service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage());
+ setConnectingState(false);
+ handleException(resultBundle, e);
+ } catch (Exception e){
+ /* TODO: Added Due to: https://github.com/eclipse/paho.mqtt.android/issues/101
+ For some reason in a small number of cases, myClient is null here and so
+ a NullPointer Exception is thrown. This is a workaround to pass the exception
+ up to the application. myClient should not be null so more investigation is
+ required.
+ */
+ service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage());
+ setConnectingState(false);
+ MqttException newEx = new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR, e.getCause());
+ handleException(resultBundle, newEx);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param isConnecting
+ */
+ private synchronized void setConnectingState(boolean isConnecting){
+ this.isConnecting = isConnecting;
+ }
+
+ /**
+ * Sets the DisconnectedBufferOptions for this client
+ * @param bufferOpts
+ */
+ public void setBufferOpts(DisconnectedBufferOptions bufferOpts) {
+ this.bufferOpts = bufferOpts;
+ myClient.setBufferOpts(bufferOpts);
+ }
+
+ public int getBufferedMessageCount(){
+ return myClient.getBufferedMessageCount();
+ }
+
+ public MqttMessage getBufferedMessage(int bufferIndex){
+ return myClient.getBufferedMessage(bufferIndex);
+ }
+
+ public void deleteBufferedMessage(int bufferIndex){
+ myClient.deleteBufferedMessage(bufferIndex);
+ }
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java b/app/src/main/java/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java
new file mode 100644
index 0000000..12ae877
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ *
+ * Implementation of the IMqttDeliveryToken interface for use from within the
+ * MqttAndroidClient implementation
+ */
+class MqttDeliveryTokenAndroid extends MqttTokenAndroid
+ implements IMqttDeliveryToken {
+
+ // The message which is being tracked by this token
+ private MqttMessage message;
+
+ MqttDeliveryTokenAndroid(MqttAndroidClient client,
+ Object userContext, IMqttActionListener listener, MqttMessage message) {
+ super(client, userContext, listener);
+ this.message = message;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttDeliveryToken#getMessage()
+ */
+ @Override
+ public MqttMessage getMessage() throws MqttException {
+ return message;
+ }
+
+ void setMessage(MqttMessage message) {
+ this.message = message;
+ }
+
+ void notifyDelivery(MqttMessage delivered) {
+ message = delivered;
+ super.notifyComplete();
+ }
+
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttService.java b/app/src/main/java/org/eclipse/paho/android/service/MqttService.java
new file mode 100644
index 0000000..3c902ed
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttService.java
@@ -0,0 +1,912 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2016 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * James Sutton - isOnline Null Pointer (bug 473775)
+ */
+package org.eclipse.paho.android.service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+/**
+ *
+ * The android service which interfaces with an MQTT client implementation
+ *
+ *
+ * The main API of MqttService is intended to pretty much mirror the
+ * IMqttAsyncClient with appropriate adjustments for the Android environment.
+ * These adjustments usually consist of adding two parameters to each method :-
+ *
+ *
+ *
invocationContext - a string passed from the application to identify the
+ * context of the operation (mainly included for support of the javascript API
+ * implementation)
+ *
activityToken - a string passed from the Activity to relate back to a
+ * callback method or other context-specific data
+ *
+ *
+ * To support multiple client connections, the bulk of the MQTT work is
+ * delegated to MqttConnection objects. These are identified by "client
+ * handle" strings, which is how the Activity, and the higher-level APIs refer
+ * to them.
+ *
+ *
+ * Activities using this service are expected to start it and bind to it using
+ * the BIND_AUTO_CREATE flag. The life cycle of this service is based on this
+ * approach.
+ *
+ *
+ * Operations are highly asynchronous - in most cases results are returned to
+ * the Activity by broadcasting one (or occasionally more) appropriate Intents,
+ * which the Activity is expected to register a listener for.
+ * The Intents have an Action of
+ * {@link MqttServiceConstants#CALLBACK_TO_ACTIVITY
+ * MqttServiceConstants.CALLBACK_TO_ACTIVITY} which allows the Activity to
+ * register a listener with an appropriate IntentFilter.
+ * Further data is provided by "Extra Data" in the Intent, as follows :-
+ *
The identifier for the message in the message
+ * store, used by the Activity to acknowledge the arrival of the message, so
+ * that the service may remove it from the store
The new message encapsulated in Android
+ * Parcelable format as a {@link ParcelableMqttMessage}
+ *
The Message Arrived event
+ *
+ *
+ */
+@SuppressLint("Registered")
+public class MqttService extends Service implements MqttTraceHandler {
+
+ // Identifier for Intents, log messages, etc..
+ static final String TAG = "MqttService";
+
+ // callback id for making trace callbacks to the Activity
+ // needs to be set by the activity as appropriate
+ private String traceCallbackId;
+ // state of tracing
+ private boolean traceEnabled = false;
+
+ // somewhere to persist received messages until we're sure
+ // that they've reached the application
+ MessageStore messageStore;
+
+ // An intent receiver to deal with changes in network connectivity
+ private NetworkConnectionIntentReceiver networkConnectionMonitor;
+
+ //a receiver to recognise when the user changes the "background data" preference
+ // and a flag to track that preference
+ // Only really relevant below android version ICE_CREAM_SANDWICH - see
+ // android docs
+ private BackgroundDataPreferenceReceiver backgroundDataPreferenceMonitor;
+ private volatile boolean backgroundDataEnabled = true;
+
+ // a way to pass ourself back to the activity
+ private MqttServiceBinder mqttServiceBinder;
+
+ // mapping from client handle strings to actual client connections.
+ private Map connections = new ConcurrentHashMap<>();
+
+ public MqttService() {
+ super();
+ }
+
+ /**
+ * pass data back to the Activity, by building a suitable Intent object and
+ * broadcasting it
+ *
+ * @param clientHandle
+ * source of the data
+ * @param status
+ * OK or Error
+ * @param dataBundle
+ * the data to be passed
+ */
+ void callbackToActivity(String clientHandle, Status status,
+ Bundle dataBundle) {
+ // Don't call traceDebug, as it will try to callbackToActivity leading
+ // to recursion.
+ Intent callbackIntent = new Intent(
+ MqttServiceConstants.CALLBACK_TO_ACTIVITY);
+ if (clientHandle != null) {
+ callbackIntent.putExtra(
+ MqttServiceConstants.CALLBACK_CLIENT_HANDLE, clientHandle);
+ }
+ callbackIntent.putExtra(MqttServiceConstants.CALLBACK_STATUS, status);
+ if (dataBundle != null) {
+ callbackIntent.putExtras(dataBundle);
+ }
+ LocalBroadcastManager.getInstance(this).sendBroadcast(callbackIntent);
+ }
+
+ // The major API implementation follows :-
+
+ /**
+ * Get an MqttConnection object to represent a connection to a server
+ *
+ * @param serverURI specifies the protocol, host name and port to be used to connect to an MQTT server
+ * @param clientId specifies the name by which this connection should be identified to the server
+ * @param contextId specifies the app conext info to make a difference between apps
+ * @param persistence specifies the persistence layer to be used with this client
+ * @return a string to be used by the Activity as a "handle" for this
+ * MqttConnection
+ */
+ public String getClient(String serverURI, String clientId, String contextId, MqttClientPersistence persistence) {
+ String clientHandle = serverURI + ":" + clientId+":"+contextId;
+ if (!connections.containsKey(clientHandle)) {
+ MqttConnection client = new MqttConnection(this, serverURI,
+ clientId, persistence, clientHandle);
+ connections.put(clientHandle, client);
+ }
+ return clientHandle;
+ }
+
+ /**
+ * Connect to the MQTT server specified by a particular client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param connectOptions
+ * the MQTT connection options to be used
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @throws MqttSecurityException thrown if there is a security exception
+ * @throws MqttException thrown for all other MqttExceptions
+ */
+ public void connect(String clientHandle, MqttConnectOptions connectOptions,
+ String invocationContext, String activityToken)
+ throws MqttSecurityException, MqttException {
+ MqttConnection client = getConnection(clientHandle);
+ client.connect(connectOptions, null, activityToken);
+
+ }
+
+ /**
+ * Request all clients to reconnect if appropriate
+ */
+ void reconnect() {
+ traceDebug(TAG, "Reconnect to server, client size=" + connections.size());
+ for (MqttConnection client : connections.values()) {
+ traceDebug("Reconnect Client:",
+ client.getClientId() + '/' + client.getServerURI());
+ if(this.isOnline()){
+ client.reconnect();
+ }
+ }
+ }
+
+ /**
+ * Close connection from a particular client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ */
+ public void close(String clientHandle) {
+ MqttConnection client = getConnection(clientHandle);
+ client.close();
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void disconnect(String clientHandle, String invocationContext,
+ String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.disconnect(invocationContext, activityToken);
+ connections.remove(clientHandle);
+
+
+ // the activity has finished using us, so we can stop the service
+ // the activities are bound with BIND_AUTO_CREATE, so the service will
+ // remain around until the last activity disconnects
+ stopSelf();
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param quiesceTimeout
+ * in milliseconds
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void disconnect(String clientHandle, long quiesceTimeout,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.disconnect(quiesceTimeout, invocationContext, activityToken);
+ connections.remove(clientHandle);
+
+ // the activity has finished using us, so we can stop the service
+ // the activities are bound with BIND_AUTO_CREATE, so the service will
+ // remain around until the last activity disconnects
+ stopSelf();
+ }
+
+ /**
+ * Get the status of a specific client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @return true if the specified client is connected to an MQTT server
+ */
+ public boolean isConnected(String clientHandle) {
+ MqttConnection client = getConnection(clientHandle);
+ return client.isConnected();
+ }
+
+ /**
+ * Publish a message to a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * the topic to which to publish
+ * @param payload
+ * the content of the message to publish
+ * @param qos
+ * the quality of service requested
+ * @param retained
+ * whether the MQTT server should retain this message
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @throws MqttPersistenceException when a problem occurs storing the message
+ * @throws MqttException if there was an error publishing the message
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String clientHandle, String topic,
+ byte[] payload, int qos, boolean retained,
+ String invocationContext, String activityToken)
+ throws MqttPersistenceException, MqttException {
+ MqttConnection client = getConnection(clientHandle);
+ return client.publish(topic, payload, qos, retained, invocationContext,
+ activityToken);
+ }
+
+ /**
+ * Publish a message to a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * the topic to which to publish
+ * @param message
+ * the message to publish
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @throws MqttPersistenceException when a problem occurs storing the message
+ * @throws MqttException if there was an error publishing the message
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String clientHandle, String topic,
+ MqttMessage message, String invocationContext, String activityToken)
+ throws MqttPersistenceException, MqttException {
+ MqttConnection client = getConnection(clientHandle);
+ return client.publish(topic, message, invocationContext, activityToken);
+ }
+
+ /**
+ * Subscribe to a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param qos
+ * requested quality of service for the topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(String clientHandle, String topic, int qos,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.subscribe(topic, qos, invocationContext, activityToken);
+ }
+
+ /**
+ * Subscribe to one or more topics
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param qos
+ * requested quality of service for each topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(String clientHandle, String[] topic, int[] qos,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.subscribe(topic, qos, invocationContext, activityToken);
+ }
+
+ /**
+ * Subscribe using topic filters
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topicFilters
+ * a list of possibly wildcarded topicfilters
+ * @param qos
+ * requested quality of service for each topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @param messageListeners a callback to handle incoming messages
+ */
+ public void subscribe(String clientHandle, String[] topicFilters, int[] qos, String invocationContext, String activityToken, IMqttMessageListener[] messageListeners){
+ MqttConnection client = getConnection(clientHandle);
+ client.subscribe(topicFilters, qos, invocationContext, activityToken, messageListeners);
+ }
+
+ /**
+ * Unsubscribe from a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void unsubscribe(String clientHandle, final String topic,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.unsubscribe(topic, invocationContext, activityToken);
+ }
+
+ /**
+ * Unsubscribe from one or more topics
+ *
+ * @param clientHandle
+ * identifies the MqttConnection
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void unsubscribe(String clientHandle, final String[] topic,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.unsubscribe(topic, invocationContext, activityToken);
+ }
+
+ /**
+ * Get tokens for all outstanding deliveries for a client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection
+ * @return an array (possibly empty) of tokens
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens(String clientHandle) {
+ MqttConnection client = getConnection(clientHandle);
+ return client.getPendingDeliveryTokens();
+ }
+
+ /**
+ * Get the MqttConnection identified by this client handle
+ *
+ * @param clientHandle identifies the MqttConnection
+ * @return the MqttConnection identified by this handle
+ */
+ private MqttConnection getConnection(String clientHandle) {
+ MqttConnection client = connections.get(clientHandle);
+ if (client == null) {
+ throw new IllegalArgumentException("Invalid ClientHandle");
+ }
+ return client;
+ }
+
+ /**
+ * Called by the Activity when a message has been passed back to the
+ * application
+ *
+ * @param clientHandle identifier for the client which received the message
+ * @param id identifier for the MQTT message
+ * @return {@link Status}
+ */
+ public Status acknowledgeMessageArrival(String clientHandle, String id) {
+ if (messageStore.discardArrived(clientHandle, id)) {
+ return Status.OK;
+ }
+ else {
+ return Status.ERROR;
+ }
+ }
+
+ // Extend Service
+
+ /**
+ * @see android.app.Service#onCreate()
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // create a binder that will let the Activity UI send
+ // commands to the Service
+ mqttServiceBinder = new MqttServiceBinder(this);
+
+ // create somewhere to buffer received messages until
+ // we know that they have been passed to the application
+ messageStore = new DatabaseMessageStore(this, this);
+ }
+
+
+
+ /**
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ // disconnect immediately
+ for (MqttConnection client : connections.values()) {
+ client.disconnect(null, null);
+ }
+
+ // clear down
+ if (mqttServiceBinder != null) {
+ mqttServiceBinder = null;
+ }
+
+ unregisterBroadcastReceivers();
+
+ if (this.messageStore !=null )
+ this.messageStore.close();
+
+ super.onDestroy();
+ }
+
+ /**
+ * @see android.app.Service#onBind(Intent)
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ // What we pass back to the Activity on binding -
+ // a reference to ourself, and the activityToken
+ // we were given when started
+ String activityToken = intent
+ .getStringExtra(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN);
+ mqttServiceBinder.setActivityToken(activityToken);
+ return mqttServiceBinder;
+ }
+
+ /**
+ * @see android.app.Service#onStartCommand(Intent,int,int)
+ */
+ @Override
+ public int onStartCommand(final Intent intent, int flags, final int startId) {
+ // run till explicitly stopped, restart when
+ // process restarted
+ registerBroadcastReceivers();
+
+ return START_STICKY;
+ }
+
+ /**
+ * Identify the callbackId to be passed when making tracing calls back into
+ * the Activity
+ *
+ * @param traceCallbackId identifier to the callback into the Activity
+ */
+ public void setTraceCallbackId(String traceCallbackId) {
+ this.traceCallbackId = traceCallbackId;
+ }
+
+ /**
+ * Turn tracing on and off
+ *
+ * @param traceEnabled set true to turn on tracing, false to turn off tracing
+ */
+ public void setTraceEnabled(boolean traceEnabled) {
+ this.traceEnabled = traceEnabled;
+ }
+
+ /**
+ * Check whether trace is on or off.
+ *
+ * @return the state of trace
+ */
+ public boolean isTraceEnabled(){
+ return this.traceEnabled;
+ }
+
+ /**
+ * Trace debugging information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ @Override
+ public void traceDebug(String tag, String message) {
+ traceCallback(MqttServiceConstants.TRACE_DEBUG, tag, message);
+ }
+
+ /**
+ * Trace error information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ @Override
+ public void traceError(String tag, String message) {
+ traceCallback(MqttServiceConstants.TRACE_ERROR, tag, message);
+ }
+
+ private void traceCallback(String severity, String tag, String message) {
+ if ((traceCallbackId != null) && (traceEnabled)) {
+ Bundle dataBundle = new Bundle();
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, severity);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag);
+ //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message);
+ callbackToActivity(traceCallbackId, Status.ERROR, dataBundle);
+ }
+ }
+
+ /**
+ * trace exceptions
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ * @param e
+ * the exception
+ */
+ @Override
+ public void traceException(String tag, String message, Exception e) {
+ if (traceCallbackId != null) {
+ Bundle dataBundle = new Bundle();
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, MqttServiceConstants.TRACE_EXCEPTION);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message);
+ dataBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e); //TODO: Check
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag);
+ //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId);
+ callbackToActivity(traceCallbackId, Status.ERROR, dataBundle);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void registerBroadcastReceivers() {
+ if (networkConnectionMonitor == null) {
+ networkConnectionMonitor = new NetworkConnectionIntentReceiver();
+ registerReceiver(networkConnectionMonitor, new IntentFilter(
+ ConnectivityManager.CONNECTIVITY_ACTION));
+ }
+
+ if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) {
+ // Support the old system for background data preferences
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ backgroundDataEnabled = cm.getBackgroundDataSetting();
+ if (backgroundDataPreferenceMonitor == null) {
+ backgroundDataPreferenceMonitor = new BackgroundDataPreferenceReceiver();
+ registerReceiver(
+ backgroundDataPreferenceMonitor,
+ new IntentFilter(
+ ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED));
+ }
+ }
+ }
+
+ private void unregisterBroadcastReceivers(){
+ if(networkConnectionMonitor != null){
+ unregisterReceiver(networkConnectionMonitor);
+ networkConnectionMonitor = null;
+ }
+
+ if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) {
+ if(backgroundDataPreferenceMonitor != null){
+ unregisterReceiver(backgroundDataPreferenceMonitor);
+ }
+ }
+ }
+
+ /*
+ * Called in response to a change in network connection - after losing a
+ * connection to the server, this allows us to wait until we have a usable
+ * data connection again
+ */
+ private class NetworkConnectionIntentReceiver extends BroadcastReceiver {
+
+ @Override
+ @SuppressLint("Wakelock")
+ public void onReceive(Context context, Intent intent) {
+ traceDebug(TAG, "Internal network status receive.");
+ // we protect against the phone switching off
+ // by requesting a wake lock - we request the minimum possible wake
+ // lock - just enough to keep the CPU running until we've finished
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ WakeLock wl = pm
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MQTT");
+ wl.acquire();
+ traceDebug(TAG,"Reconnect for Network recovery.");
+ if (isOnline()) {
+ traceDebug(TAG,"Online,reconnect.");
+ // we have an internet connection - have another try at
+ // connecting
+ reconnect();
+ } else {
+ notifyClientsOffline();
+ }
+
+ wl.release();
+ }
+ }
+
+ /**
+ * @return whether the android service can be regarded as online
+ */
+ public boolean isOnline() {
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ //noinspection RedundantIfStatement
+ if (networkInfo != null
+ && networkInfo.isAvailable()
+ && networkInfo.isConnected()
+ && backgroundDataEnabled) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Notify clients we're offline
+ */
+ private void notifyClientsOffline() {
+ for (MqttConnection connection : connections.values()) {
+ connection.offline();
+ }
+ }
+
+ /**
+ * Detect changes of the Allow Background Data setting - only used below
+ * ICE_CREAM_SANDWICH
+ */
+ private class BackgroundDataPreferenceReceiver extends BroadcastReceiver {
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ traceDebug(TAG,"Reconnect since BroadcastReceiver.");
+ if (cm.getBackgroundDataSetting()) {
+ if (!backgroundDataEnabled) {
+ backgroundDataEnabled = true;
+ // we have the Internet connection - have another try at
+ // connecting
+ reconnect();
+ }
+ } else {
+ backgroundDataEnabled = false;
+ notifyClientsOffline();
+ }
+ }
+ }
+
+ /**
+ * Sets the DisconnectedBufferOptions for this client
+ * @param clientHandle identifier for the client
+ * @param bufferOpts the DisconnectedBufferOptions for this client
+ */
+ public void setBufferOpts(String clientHandle, DisconnectedBufferOptions bufferOpts) {
+ MqttConnection client = getConnection(clientHandle);
+ client.setBufferOpts(bufferOpts);
+ }
+
+ public int getBufferedMessageCount(String clientHandle){
+ MqttConnection client = getConnection(clientHandle);
+ return client.getBufferedMessageCount();
+ }
+
+ public MqttMessage getBufferedMessage(String clientHandle, int bufferIndex){
+ MqttConnection client = getConnection(clientHandle);
+ return client.getBufferedMessage(bufferIndex);
+ }
+
+ public void deleteBufferedMessage(String clientHandle, int bufferIndex){
+ MqttConnection client = getConnection(clientHandle);
+ client.deleteBufferedMessage(bufferIndex);
+ }
+
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttServiceBinder.java b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceBinder.java
new file mode 100644
index 0000000..6dbf3fd
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceBinder.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import android.os.Binder;
+
+/**
+ * What the Service passes to the Activity on binding:-
+ *
+ *
a reference to the Service
+ *
the activityToken provided when the Service was started
+ *
+ *
+ */
+class MqttServiceBinder extends Binder {
+
+ private MqttService mqttService;
+ private String activityToken;
+
+ MqttServiceBinder(MqttService mqttService) {
+ this.mqttService = mqttService;
+ }
+
+ /**
+ * @return a reference to the Service
+ */
+ public MqttService getService() {
+ return mqttService;
+ }
+
+ void setActivityToken(String activityToken) {
+ this.activityToken = activityToken;
+ }
+
+ /**
+ * @return the activityToken provided when the Service was started
+ */
+ public String getActivityToken() {
+ return activityToken;
+ }
+
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttServiceConstants.java b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceConstants.java
new file mode 100644
index 0000000..b05feb9
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceConstants.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2016 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+/**
+ * Various strings used to identify operations or data in the Android MQTT
+ * service, mainly used in Intents passed between Activities and the Service.
+ */
+interface MqttServiceConstants {
+
+ /*
+ * Version information
+ */
+
+ String VERSION = "v0";
+
+ /*
+ * Attributes of messages
Used for the column names in the database
+ */
+ String DUPLICATE = "duplicate";
+ String RETAINED = "retained";
+ String QOS = "qos";
+ String PAYLOAD = "payload";
+ String DESTINATION_NAME = "destinationName";
+ String CLIENT_HANDLE = "clientHandle";
+ String MESSAGE_ID = "messageId";
+
+ /* Tags for actions passed between the Activity and the Service */
+ String SEND_ACTION = "send";
+ String UNSUBSCRIBE_ACTION = "unsubscribe";
+ String SUBSCRIBE_ACTION = "subscribe";
+ String DISCONNECT_ACTION = "disconnect";
+ String CONNECT_ACTION = "connect";
+ String CONNECT_EXTENDED_ACTION = "connectExtended";
+ String MESSAGE_ARRIVED_ACTION = "messageArrived";
+ String MESSAGE_DELIVERED_ACTION = "messageDelivered";
+ String ON_CONNECTION_LOST_ACTION = "onConnectionLost";
+ String TRACE_ACTION = "trace";
+
+ /* Identifies an Intent which calls back to the Activity */
+ String CALLBACK_TO_ACTIVITY = MqttService.TAG
+ + ".callbackToActivity"+"."+VERSION;
+
+ /* Identifiers for extra data on Intents broadcast to the Activity */
+ String CALLBACK_ACTION = MqttService.TAG + ".callbackAction";
+ String CALLBACK_STATUS = MqttService.TAG + ".callbackStatus";
+ String CALLBACK_CLIENT_HANDLE = MqttService.TAG + "."
+ + CLIENT_HANDLE;
+ String CALLBACK_ERROR_MESSAGE = MqttService.TAG
+ + ".errorMessage";
+ String CALLBACK_EXCEPTION_STACK = MqttService.TAG
+ + ".exceptionStack";
+ String CALLBACK_INVOCATION_CONTEXT = MqttService.TAG + "."
+ + "invocationContext";
+ String CALLBACK_ACTIVITY_TOKEN = MqttService.TAG + "."
+ + "activityToken";
+ String CALLBACK_DESTINATION_NAME = MqttService.TAG + '.'
+ + DESTINATION_NAME;
+ String CALLBACK_MESSAGE_ID = MqttService.TAG + '.'
+ + MESSAGE_ID;
+ String CALLBACK_RECONNECT = MqttService.TAG + ".reconnect";
+ String CALLBACK_SERVER_URI = MqttService.TAG + ".serverURI";
+ String CALLBACK_MESSAGE_PARCEL = MqttService.TAG + ".PARCEL";
+ String CALLBACK_TRACE_SEVERITY = MqttService.TAG
+ + ".traceSeverity";
+ String CALLBACK_TRACE_TAG = MqttService.TAG + ".traceTag";
+ String CALLBACK_TRACE_ID = MqttService.TAG + ".traceId";
+ String CALLBACK_ERROR_NUMBER = MqttService.TAG
+ + ".ERROR_NUMBER";
+
+ String CALLBACK_EXCEPTION = MqttService.TAG + ".exception";
+
+ //Intent prefix for Ping sender.
+ String PING_SENDER = MqttService.TAG + ".pingSender.";
+
+ //Constant for wakelock
+ String PING_WAKELOCK = MqttService.TAG + ".client.";
+ String WAKELOCK_NETWORK_INTENT = MqttService.TAG + "";
+
+ //Trace severity levels
+ String TRACE_ERROR = "error";
+ String TRACE_DEBUG = "debug";
+ String TRACE_EXCEPTION = "exception";
+
+
+ //exception code for non MqttExceptions
+ int NON_MQTT_EXCEPTION = -1;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttTokenAndroid.java b/app/src/main/java/org/eclipse/paho/android/service/MqttTokenAndroid.java
new file mode 100644
index 0000000..01a357a
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttTokenAndroid.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+
+/**
+ *
+ * Implementation of the IMqttToken interface for use from within the
+ * MqttAndroidClient implementation
+ */
+
+class MqttTokenAndroid implements IMqttToken {
+
+ private IMqttActionListener listener;
+
+ private volatile boolean isComplete;
+
+ private volatile MqttException lastException;
+
+ private Object waitObject = new Object();
+
+ private MqttAndroidClient client;
+
+ private Object userContext;
+
+ private String[] topics;
+
+ private IMqttToken delegate; // specifically for getMessageId
+
+ private MqttException pendingException;
+
+ /**
+ * Standard constructor
+ *
+ * @param client used to pass MqttAndroidClient object
+ * @param userContext used to pass context
+ * @param listener optional listener that will be notified when the action completes. Use null if not required.
+ */
+ MqttTokenAndroid(MqttAndroidClient client,
+ Object userContext, IMqttActionListener listener) {
+ this(client, userContext, listener, null);
+ }
+
+ /**
+ * Constructor for use with subscribe operations
+ *
+ * @param client used to pass MqttAndroidClient object
+ * @param userContext used to pass context
+ * @param listener optional listener that will be notified when the action completes. Use null if not required.
+ * @param topics topics to subscribe to, which can include wildcards.
+ */
+ MqttTokenAndroid(MqttAndroidClient client,
+ Object userContext, IMqttActionListener listener, String[] topics) {
+ this.client = client;
+ this.userContext = userContext;
+ this.listener = listener;
+ this.topics = topics;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion()
+ */
+ @Override
+ public void waitForCompletion() throws MqttException, MqttSecurityException {
+ synchronized (waitObject) {
+ try {
+ waitObject.wait();
+ }
+ catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ if (pendingException != null) {
+ throw pendingException;
+ }
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion(long)
+ */
+ @Override
+ public void waitForCompletion(long timeout) throws MqttException,
+ MqttSecurityException {
+ synchronized (waitObject) {
+ try {
+ waitObject.wait(timeout);
+ }
+ catch (InterruptedException e) {
+ // do nothing
+ }
+ if (!isComplete) {
+ throw new MqttException(MqttException.REASON_CODE_CLIENT_TIMEOUT);
+ }
+ if (pendingException != null) {
+ throw pendingException;
+ }
+ }
+ }
+
+ /**
+ * notify successful completion of the operation
+ */
+ void notifyComplete() {
+ synchronized (waitObject) {
+ isComplete = true;
+ waitObject.notifyAll();
+ if (listener != null) {
+ listener.onSuccess(this);
+ }
+ }
+ }
+
+ /**
+ * notify unsuccessful completion of the operation
+ */
+ void notifyFailure(Throwable exception) {
+ synchronized (waitObject) {
+ isComplete = true;
+ if (exception instanceof MqttException) {
+ pendingException = (MqttException) exception;
+ }
+ else {
+ pendingException = new MqttException(exception);
+ }
+ waitObject.notifyAll();
+ if (exception instanceof MqttException) {
+ lastException = (MqttException) exception;
+ }
+ if (listener != null) {
+ listener.onFailure(this, exception);
+ }
+ }
+
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#isComplete()
+ */
+ @Override
+ public boolean isComplete() {
+ return isComplete;
+ }
+
+ void setComplete(boolean complete) {
+ isComplete = complete;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getException()
+ */
+ @Override
+ public MqttException getException() {
+ return lastException;
+ }
+
+ void setException(MqttException exception) {
+ lastException = exception;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getClient()
+ */
+ @Override
+ public IMqttAsyncClient getClient() {
+ return client;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#setActionCallback(IMqttActionListener)
+ */
+ @Override
+ public void setActionCallback(IMqttActionListener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getActionCallback()
+ */
+ @Override
+ public IMqttActionListener getActionCallback() {
+ return listener;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getTopics()
+ */
+ @Override
+ public String[] getTopics() {
+ return topics;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#setUserContext(Object)
+ */
+ @Override
+ public void setUserContext(Object userContext) {
+ this.userContext = userContext;
+
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getUserContext()
+ */
+ @Override
+ public Object getUserContext() {
+ return userContext;
+ }
+
+ void setDelegate(IMqttToken delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getMessageId()
+ */
+ @Override
+ public int getMessageId() {
+ return (delegate != null) ? delegate.getMessageId() : 0;
+ }
+
+ @Override
+ public MqttWireMessage getResponse() {
+ return delegate.getResponse();
+ }
+
+ @Override
+ public boolean getSessionPresent() {
+ return delegate.getSessionPresent();
+ }
+
+ @Override
+ public int[] getGrantedQos() {
+ return delegate.getGrantedQos();
+ }
+
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttTraceHandler.java b/app/src/main/java/org/eclipse/paho/android/service/MqttTraceHandler.java
new file mode 100644
index 0000000..8118c43
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/MqttTraceHandler.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+/**
+ * Interface for simple trace handling, pass the trace message to trace
+ * callback.
+ *
+ */
+
+public interface MqttTraceHandler {
+
+ /**
+ * Trace debugging information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ void traceDebug(String tag, String message);
+
+ /**
+ * Trace error information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ void traceError(String tag, String message);
+
+ /**
+ * trace exceptions
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ * @param e
+ * the exception
+ */
+ void traceException(String tag, String message,
+ Exception e);
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/eclipse/paho/android/service/ParcelableMqttMessage.java b/app/src/main/java/org/eclipse/paho/android/service/ParcelableMqttMessage.java
new file mode 100644
index 0000000..9164cee
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/ParcelableMqttMessage.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ *
+ * A way to flow MqttMessages via Bundles/Intents
+ *
+ *
+ *
+ * An application will probably use this only when receiving a message from a
+ * Service in a Bundle - the necessary code will be something like this :-
+ *
+ *
+ *
+ * private void messageArrivedAction(Bundle data) {
+ * ParcelableMqttMessage message = (ParcelableMqttMessage) data
+ * .getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
+ * Use the normal {@link MqttMessage} methods on the the message object.
+ * }
+ *
+ *
+ *
+ *
+ *
+ * It is unlikely that an application will directly use the methods which are
+ * specific to this class.
+ *
+ */
+
+public class ParcelableMqttMessage extends MqttMessage implements Parcelable {
+
+ String messageId = null;
+
+ ParcelableMqttMessage(MqttMessage original) {
+ super(original.getPayload());
+ setQos(original.getQos());
+ setRetained(original.isRetained());
+ setDuplicate(original.isDuplicate());
+ }
+
+ ParcelableMqttMessage(Parcel parcel) {
+ super(parcel.createByteArray());
+ setQos(parcel.readInt());
+ boolean[] flags = parcel.createBooleanArray();
+ setRetained(flags[0]);
+ setDuplicate(flags[1]);
+ messageId = parcel.readString();
+ }
+
+ /**
+ * @return the messageId
+ */
+ public String getMessageId() {
+ return messageId;
+ }
+
+ /**
+ * Describes the contents of this object
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes the contents of this object to a parcel
+ *
+ * @param parcel
+ * The parcel to write the data to.
+ * @param flags
+ * this parameter is ignored
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeByteArray(getPayload());
+ parcel.writeInt(getQos());
+ parcel.writeBooleanArray(new boolean[]{isRetained(), isDuplicate()});
+ parcel.writeString(messageId);
+ }
+
+ /**
+ * A creator which creates the message object from a parcel
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+
+ /**
+ * Creates a message from the parcel object
+ */
+ @Override
+ public ParcelableMqttMessage createFromParcel(Parcel parcel) {
+ return new ParcelableMqttMessage(parcel);
+ }
+
+ /**
+ * creates an array of type {@link ParcelableMqttMessage}[]
+ *
+ */
+ @Override
+ public ParcelableMqttMessage[] newArray(int size) {
+ return new ParcelableMqttMessage[size];
+ }
+ };
+}
diff --git a/app/src/main/java/org/eclipse/paho/android/service/RitoPingSender.java b/app/src/main/java/org/eclipse/paho/android/service/RitoPingSender.java
new file mode 100644
index 0000000..7c22210
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/RitoPingSender.java
@@ -0,0 +1,55 @@
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.MqttPingSender;
+import org.eclipse.paho.client.mqttv3.internal.ClientComms;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class RitoPingSender implements MqttPingSender {
+
+ private ClientComms comms;
+ private ScheduledExecutorService scheduler;
+ private ScheduledFuture> pingFuture;
+
+ @Override
+ public void init(ClientComms comms) {
+ this.comms = comms;
+ this.scheduler = Executors.newSingleThreadScheduledExecutor();
+ }
+
+ @Override
+ public void start() {
+ schedule(comms.getKeepAlive());
+ }
+
+ private void schedule(int delayInSeconds) {
+ pingFuture = scheduler.scheduleAtFixedRate(() -> {
+ try {
+ comms.checkForActivity();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }, delayInSeconds, delayInSeconds, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void stop() {
+ if (pingFuture != null) pingFuture.cancel(true);
+ scheduler.shutdownNow();
+ }
+
+ @Override
+ public void schedule(long delayInMilliseconds) {
+ if (pingFuture != null) pingFuture.cancel(true);
+ pingFuture = scheduler.schedule(() -> {
+ try {
+ comms.checkForActivity();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }, delayInMilliseconds, TimeUnit.MILLISECONDS);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/eclipse/paho/android/service/Status.java b/app/src/main/java/org/eclipse/paho/android/service/Status.java
new file mode 100644
index 0000000..dadde36
--- /dev/null
+++ b/app/src/main/java/org/eclipse/paho/android/service/Status.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+/**
+ * Enumeration representing the success or failure of an operation
+ */
+enum Status {
+ /**
+ * Indicates that the operation succeeded
+ */
+ OK,
+
+ /**
+ * Indicates that the operation failed
+ */
+ ERROR,
+
+ /**
+ * Indicates that the operation's result may be returned asynchronously
+ */
+ NO_RESULT
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/blackground.png b/app/src/main/res/drawable/blackground.png
new file mode 100644
index 0000000..a869da0
Binary files /dev/null and b/app/src/main/res/drawable/blackground.png differ
diff --git a/app/src/main/res/drawable/channel_osd.png b/app/src/main/res/drawable/channel_osd.png
new file mode 100644
index 0000000..d8eca67
Binary files /dev/null and b/app/src/main/res/drawable/channel_osd.png differ
diff --git a/app/src/main/res/drawable/demo_119.png b/app/src/main/res/drawable/demo_119.png
new file mode 100644
index 0000000..fbf1f05
Binary files /dev/null and b/app/src/main/res/drawable/demo_119.png differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/no_broadcast.png b/app/src/main/res/drawable/no_broadcast.png
new file mode 100644
index 0000000..d390745
Binary files /dev/null and b/app/src/main/res/drawable/no_broadcast.png differ
diff --git a/app/src/main/res/drawable/on_tts.png b/app/src/main/res/drawable/on_tts.png
new file mode 100644
index 0000000..391af2a
Binary files /dev/null and b/app/src/main/res/drawable/on_tts.png differ
diff --git a/app/src/main/res/drawable/selector_button.xml b/app/src/main/res/drawable/selector_button.xml
new file mode 100644
index 0000000..0d6e48d
--- /dev/null
+++ b/app/src/main/res/drawable/selector_button.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/splash.png b/app/src/main/res/drawable/splash.png
new file mode 100644
index 0000000..39d030e
Binary files /dev/null and b/app/src/main/res/drawable/splash.png differ
diff --git a/app/src/main/res/drawable/wait_broadcast.png b/app/src/main/res/drawable/wait_broadcast.png
new file mode 100644
index 0000000..1bab36c
Binary files /dev/null and b/app/src/main/res/drawable/wait_broadcast.png differ
diff --git a/app/src/main/res/drawable/webview_loading.png b/app/src/main/res/drawable/webview_loading.png
new file mode 100644
index 0000000..56a708a
Binary files /dev/null and b/app/src/main/res/drawable/webview_loading.png differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..390c91f
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml
new file mode 100644
index 0000000..9418564
--- /dev/null
+++ b/app/src/main/res/layout/activity_setting.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml
new file mode 100644
index 0000000..bef5834
--- /dev/null
+++ b/app/src/main/res/layout/fragment_setting.xml
@@ -0,0 +1,399 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/raw/downchimes.wav b/app/src/main/res/raw/downchimes.wav
new file mode 100644
index 0000000..c465e47
Binary files /dev/null and b/app/src/main/res/raw/downchimes.wav differ
diff --git a/app/src/main/res/raw/mute.wav b/app/src/main/res/raw/mute.wav
new file mode 100644
index 0000000..6f60a33
Binary files /dev/null and b/app/src/main/res/raw/mute.wav differ
diff --git a/app/src/main/res/raw/upchimes.wav b/app/src/main/res/raw/upchimes.wav
new file mode 100644
index 0000000..c6015ab
Binary files /dev/null and b/app/src/main/res/raw/upchimes.wav differ
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..030098f
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #6200EE
+ #3700B3
+ #03DAC5
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..1927bee
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,8 @@
+
+
+ 16dp
+ 16dp
+ 16dp
+ 16dp
+ 8dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..30b9e5d
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+ OsicManager
+ Image Desc
+ SettingActivity
+ 기기 정보
+ 네트워크 설정
+ 서버 설정
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4b840b8
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/kr/co/rito/osicmanager/ExampleUnitTest.java b/app/src/test/java/kr/co/rito/osicmanager/ExampleUnitTest.java
new file mode 100644
index 0000000..74f391d
--- /dev/null
+++ b/app/src/test/java/kr/co/rito/osicmanager/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package kr.co.rito.osicmanager;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..6f47173
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,53 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ //classpath 'com.android.tools.build:gradle:4.0.1'
+ classpath "com.android.tools.build:gradle:7.0.2"
+
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+// gradle.projectsEvaluated {
+// tasks.withType(JavaCompile) {
+// options.compilerArgs.add('-Xbootclasspath/p:app/libs/ritocls.jar')
+// }
+// }
+
+ gradle.projectsEvaluated {
+ tasks.withType(JavaCompile) {
+ options.compilerArgs.add('-Xbootclasspath/p:app\\libs\\ritocls.jar')
+ }
+
+ // After Android 4.2.x
+ tasks.withType(JavaCompile) {
+ Set fileSet = options.bootstrapClasspath.getFiles()
+ List newFileList = new ArrayList<>();
+ newFileList.add(new File("./app/libs/ritocls.jar"))
+ newFileList.addAll(fileSet)
+ options.bootstrapClasspath = files(
+ newFileList.toArray()
+ )
+ }
+ }
+
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..a20f62a
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..7d18f7e
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name='OsicManager'
+include ':app'