├── .gitignore ├── MQTTLibrary ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── itfitness │ │ └── mqttlibrary │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── itfitness │ │ └── mqttlibrary │ │ ├── AlarmPingSender.java │ │ ├── DatabaseMessageStore.java │ │ ├── MessageStore.java │ │ ├── MqttAndroidClient.java │ │ ├── MqttConnection.java │ │ ├── MqttDeliveryTokenAndroid.java │ │ ├── MqttService.java │ │ ├── MqttServiceBinder.java │ │ ├── MqttServiceConstants.java │ │ ├── MqttTokenAndroid.java │ │ ├── MqttTraceHandler.java │ │ ├── ParcelableMqttMessage.java │ │ └── Status.java │ └── test │ └── java │ └── com │ └── itfitness │ └── mqttlibrary │ └── ExampleUnitTest.kt ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── itfitness │ │ └── mqttandroid │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── itfitness │ │ │ └── mqttandroid │ │ │ ├── MQTTHelper.kt │ │ │ ├── MainActivity.kt │ │ │ └── data │ │ │ ├── Qos.kt │ │ │ └── Topic.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── itfitness │ └── mqttandroid │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /MQTTLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /MQTTLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | minSdk 21 11 | targetSdk 31 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles "consumer-rules.pro" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | } 33 | 34 | dependencies { 35 | 36 | implementation 'androidx.core:core-ktx:1.7.0' 37 | implementation 'androidx.appcompat:appcompat:1.4.1' 38 | implementation 'com.google.android.material:material:1.5.0' 39 | testImplementation 'junit:junit:4.+' 40 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 42 | api 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' 43 | } -------------------------------------------------------------------------------- /MQTTLibrary/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itfitness/MQTTAndroid/268eba5feca208d3d91ada4479182f288ad3cc14/MQTTLibrary/consumer-rules.pro -------------------------------------------------------------------------------- /MQTTLibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /MQTTLibrary/src/androidTest/java/com/itfitness/mqttlibrary/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.itfitness.mqttlibrary 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.itfitness.mqttlibrary.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /MQTTLibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/AlarmPingSender.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | 16 | import android.annotation.SuppressLint; 17 | import android.app.AlarmManager; 18 | import android.app.PendingIntent; 19 | import android.app.Service; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.IntentFilter; 24 | import android.os.Build; 25 | import android.os.PowerManager; 26 | import android.os.PowerManager.WakeLock; 27 | import android.util.Log; 28 | 29 | import androidx.annotation.RequiresApi; 30 | 31 | import org.eclipse.paho.client.mqttv3.IMqttActionListener; 32 | import org.eclipse.paho.client.mqttv3.IMqttToken; 33 | import org.eclipse.paho.client.mqttv3.MqttPingSender; 34 | import org.eclipse.paho.client.mqttv3.internal.ClientComms; 35 | 36 | /** 37 | * Default ping sender implementation on Android. It is based on AlarmManager. 38 | * 39 | *

This class implements the {@link MqttPingSender} pinger interface 40 | * allowing applications to send ping packet to server every keep alive interval. 41 | *

42 | * 43 | * @see MqttPingSender 44 | */ 45 | class AlarmPingSender implements MqttPingSender { 46 | // Identifier for Intents, log messages, etc.. 47 | private static final String TAG = "AlarmPingSender"; 48 | 49 | // TODO: Add log. 50 | private ClientComms comms; 51 | private MqttService service; 52 | private BroadcastReceiver alarmReceiver; 53 | private AlarmPingSender that; 54 | private PendingIntent pendingIntent; 55 | private volatile boolean hasStarted = false; 56 | 57 | public AlarmPingSender(MqttService service) { 58 | if (service == null) { 59 | throw new IllegalArgumentException( 60 | "Neither service nor client can be null."); 61 | } 62 | this.service = service; 63 | that = this; 64 | } 65 | 66 | @Override 67 | public void init(ClientComms comms) { 68 | this.comms = comms; 69 | this.alarmReceiver = new AlarmReceiver(); 70 | } 71 | 72 | @RequiresApi(api = Build.VERSION_CODES.M) 73 | @Override 74 | public void start() { 75 | String action = MqttServiceConstants.PING_SENDER 76 | + comms.getClient().getClientId(); 77 | Log.d(TAG, "Register alarmreceiver to MqttService"+ action); 78 | service.registerReceiver(alarmReceiver, new IntentFilter(action)); 79 | 80 | pendingIntent = PendingIntent.getBroadcast(service, 0, new Intent( 81 | action), PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE); 82 | 83 | schedule(comms.getKeepAlive()); 84 | hasStarted = true; 85 | } 86 | 87 | @Override 88 | public void stop() { 89 | 90 | Log.d(TAG, "Unregister alarmreceiver to MqttService"+comms.getClient().getClientId()); 91 | if(hasStarted){ 92 | if(pendingIntent != null){ 93 | // Cancel Alarm. 94 | AlarmManager alarmManager = (AlarmManager) service.getSystemService(Service.ALARM_SERVICE); 95 | alarmManager.cancel(pendingIntent); 96 | } 97 | 98 | hasStarted = false; 99 | try{ 100 | service.unregisterReceiver(alarmReceiver); 101 | }catch(IllegalArgumentException e){ 102 | //Ignore unregister errors. 103 | } 104 | } 105 | } 106 | 107 | @Override 108 | public void schedule(long delayInMilliseconds) { 109 | long nextAlarmInMilliseconds = System.currentTimeMillis() 110 | + delayInMilliseconds; 111 | Log.d(TAG, "Schedule next alarm at " + nextAlarmInMilliseconds); 112 | AlarmManager alarmManager = (AlarmManager) service 113 | .getSystemService(Service.ALARM_SERVICE); 114 | 115 | if(Build.VERSION.SDK_INT >= 23){ 116 | // In SDK 23 and above, dosing will prevent setExact, setExactAndAllowWhileIdle will force 117 | // the device to run this task whilst dosing. 118 | Log.d(TAG, "Alarm scheule using setExactAndAllowWhileIdle, next: " + delayInMilliseconds); 119 | alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds, 120 | pendingIntent); 121 | } else if (Build.VERSION.SDK_INT >= 19) { 122 | Log.d(TAG, "Alarm scheule using setExact, delay: " + delayInMilliseconds); 123 | alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds, 124 | pendingIntent); 125 | } else { 126 | alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds, 127 | pendingIntent); 128 | } 129 | } 130 | 131 | /* 132 | * This class sends PingReq packet to MQTT broker 133 | */ 134 | class AlarmReceiver extends BroadcastReceiver { 135 | private WakeLock wakelock; 136 | private final String wakeLockTag = MqttServiceConstants.PING_WAKELOCK 137 | + that.comms.getClient().getClientId(); 138 | 139 | @Override 140 | @SuppressLint("Wakelock") 141 | public void onReceive(Context context, Intent intent) { 142 | // According to the docs, "Alarm Manager holds a CPU wake lock as 143 | // long as the alarm receiver's onReceive() method is executing. 144 | // This guarantees that the phone will not sleep until you have 145 | // finished handling the broadcast.", but this class still get 146 | // a wake lock to wait for ping finished. 147 | 148 | Log.d(TAG, "Sending Ping at:" + System.currentTimeMillis()); 149 | 150 | PowerManager pm = (PowerManager) service 151 | .getSystemService(Service.POWER_SERVICE); 152 | wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag); 153 | wakelock.acquire(); 154 | 155 | // Assign new callback to token to execute code after PingResq 156 | // arrives. Get another wakelock even receiver already has one, 157 | // release it until ping response returns. 158 | IMqttToken token = comms.checkForActivity(new IMqttActionListener() { 159 | 160 | @Override 161 | public void onSuccess(IMqttToken asyncActionToken) { 162 | Log.d(TAG, "Success. Release lock(" + wakeLockTag + "):" 163 | + System.currentTimeMillis()); 164 | //Release wakelock when it is done. 165 | wakelock.release(); 166 | } 167 | 168 | @Override 169 | public void onFailure(IMqttToken asyncActionToken, 170 | Throwable exception) { 171 | Log.d(TAG, "Failure. Release lock(" + wakeLockTag + "):" 172 | + System.currentTimeMillis()); 173 | //Release wakelock when it is done. 174 | wakelock.release(); 175 | } 176 | }); 177 | 178 | 179 | if (token == null && wakelock.isHeld()) { 180 | wakelock.release(); 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/DatabaseMessageStore.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | * 13 | * Contributors: 14 | * James Sutton - Removing SQL Injection vunerability (bug 467378) 15 | */ 16 | package com.itfitness.mqttlibrary; 17 | 18 | import android.content.ContentValues; 19 | import android.content.Context; 20 | import android.database.Cursor; 21 | import android.database.SQLException; 22 | import android.database.sqlite.SQLiteDatabase; 23 | import android.database.sqlite.SQLiteOpenHelper; 24 | 25 | import org.eclipse.paho.client.mqttv3.MqttMessage; 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Implementation of the {@link MessageStore} interface, using a SQLite database 31 | * 32 | */ 33 | class DatabaseMessageStore implements MessageStore { 34 | 35 | // TAG used for indentify trace data etc. 36 | private static final String TAG = "DatabaseMessageStore"; 37 | 38 | // One "private" database column name 39 | // The other database column names are defined in MqttServiceConstants 40 | private static final String MTIMESTAMP = "mtimestamp"; 41 | 42 | // the name of the table in the database to which we will save messages 43 | private static final String ARRIVED_MESSAGE_TABLE_NAME = "MqttArrivedMessageTable"; 44 | 45 | // the database 46 | private SQLiteDatabase db = null; 47 | 48 | // a SQLiteOpenHelper specific for this database 49 | private MQTTDatabaseHelper mqttDb = null; 50 | 51 | // a place to send trace data 52 | private MqttTraceHandler traceHandler = null; 53 | 54 | /** 55 | * We need a SQLiteOpenHelper to handle database creation and updating 56 | * 57 | */ 58 | private static class MQTTDatabaseHelper extends SQLiteOpenHelper { 59 | // TAG used for indentify trace data etc. 60 | private static final String TAG = "MQTTDatabaseHelper"; 61 | 62 | private static final String DATABASE_NAME = "mqttAndroidService.db"; 63 | 64 | // database version, used to recognise when we need to upgrade 65 | // (delete and recreate) 66 | private static final int DATABASE_VERSION = 1; 67 | 68 | // a place to send trace data 69 | private MqttTraceHandler traceHandler = null; 70 | 71 | /** 72 | * Constructor. 73 | * 74 | * @param traceHandler 75 | * @param context 76 | */ 77 | public MQTTDatabaseHelper(MqttTraceHandler traceHandler, Context context) { 78 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 79 | this.traceHandler = traceHandler; 80 | } 81 | 82 | /** 83 | * When the database is (re)created, create our table 84 | * 85 | * @param database 86 | */ 87 | @Override 88 | public void onCreate(SQLiteDatabase database) { 89 | String createArrivedTableStatement = "CREATE TABLE " 90 | + ARRIVED_MESSAGE_TABLE_NAME + "(" 91 | + MqttServiceConstants.MESSAGE_ID + " TEXT PRIMARY KEY, " 92 | + MqttServiceConstants.CLIENT_HANDLE + " TEXT, " 93 | + MqttServiceConstants.DESTINATION_NAME + " TEXT, " 94 | + MqttServiceConstants.PAYLOAD + " BLOB, " 95 | + MqttServiceConstants.QOS + " INTEGER, " 96 | + MqttServiceConstants.RETAINED + " TEXT, " 97 | + MqttServiceConstants.DUPLICATE + " TEXT, " + MTIMESTAMP 98 | + " INTEGER" + ");"; 99 | traceHandler.traceDebug(TAG, "onCreate {" 100 | + createArrivedTableStatement + "}"); 101 | try { 102 | database.execSQL(createArrivedTableStatement); 103 | traceHandler.traceDebug(TAG, "created the table"); 104 | } catch (SQLException e) { 105 | traceHandler.traceException(TAG, "onCreate", e); 106 | throw e; 107 | } 108 | } 109 | 110 | /** 111 | * To upgrade the database, drop and recreate our table 112 | * 113 | * @param db 114 | * the database 115 | * @param oldVersion 116 | * ignored 117 | * @param newVersion 118 | * ignored 119 | */ 120 | 121 | @Override 122 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 123 | traceHandler.traceDebug(TAG, "onUpgrade"); 124 | try { 125 | db.execSQL("DROP TABLE IF EXISTS " + ARRIVED_MESSAGE_TABLE_NAME); 126 | } catch (SQLException e) { 127 | traceHandler.traceException(TAG, "onUpgrade", e); 128 | throw e; 129 | } 130 | onCreate(db); 131 | traceHandler.traceDebug(TAG, "onUpgrade complete"); 132 | } 133 | } 134 | 135 | /** 136 | * Constructor - create a DatabaseMessageStore to store arrived MQTT message 137 | * 138 | * @param service 139 | * our parent MqttService 140 | * @param context 141 | * a context to use for android calls 142 | */ 143 | public DatabaseMessageStore(MqttService service, Context context) { 144 | this.traceHandler = service; 145 | 146 | // Open message database 147 | mqttDb = new MQTTDatabaseHelper(traceHandler, context); 148 | 149 | // Android documentation suggests that this perhaps 150 | // could/should be done in another thread, but as the 151 | // database is only one table, I doubt it matters... 152 | 153 | traceHandler.traceDebug(TAG, "DatabaseMessageStore complete"); 154 | } 155 | 156 | /** 157 | * Store an MQTT message 158 | * 159 | * @param clientHandle 160 | * identifier for the client storing the message 161 | * @param topic 162 | * The topic on which the message was published 163 | * @param message 164 | * the arrived MQTT message 165 | * @return an identifier for the message, so that it can be removed when appropriate 166 | */ 167 | @Override 168 | public String storeArrived(String clientHandle, String topic, 169 | MqttMessage message) { 170 | 171 | db = mqttDb.getWritableDatabase(); 172 | 173 | traceHandler.traceDebug(TAG, "storeArrived{" + clientHandle + "}, {" 174 | + message.toString() + "}"); 175 | 176 | byte[] payload = message.getPayload(); 177 | int qos = message.getQos(); 178 | boolean retained = message.isRetained(); 179 | boolean duplicate = message.isDuplicate(); 180 | 181 | ContentValues values = new ContentValues(); 182 | String id = java.util.UUID.randomUUID().toString(); 183 | values.put(MqttServiceConstants.MESSAGE_ID, id); 184 | values.put(MqttServiceConstants.CLIENT_HANDLE, clientHandle); 185 | values.put(MqttServiceConstants.DESTINATION_NAME, topic); 186 | values.put(MqttServiceConstants.PAYLOAD, payload); 187 | values.put(MqttServiceConstants.QOS, qos); 188 | values.put(MqttServiceConstants.RETAINED, retained); 189 | values.put(MqttServiceConstants.DUPLICATE, duplicate); 190 | values.put(MTIMESTAMP, System.currentTimeMillis()); 191 | try { 192 | db.insertOrThrow(ARRIVED_MESSAGE_TABLE_NAME, null, values); 193 | } catch (SQLException e) { 194 | traceHandler.traceException(TAG, "onUpgrade", e); 195 | throw e; 196 | } 197 | int count = getArrivedRowCount(clientHandle); 198 | traceHandler 199 | .traceDebug( 200 | TAG, 201 | "storeArrived: inserted message with id of {" 202 | + id 203 | + "} - Number of messages in database for this clientHandle = " 204 | + count); 205 | return id; 206 | } 207 | 208 | private int getArrivedRowCount(String clientHandle) { 209 | int count = 0; 210 | String[] projection = { 211 | MqttServiceConstants.MESSAGE_ID, 212 | }; 213 | String selection = MqttServiceConstants.CLIENT_HANDLE + "=?"; 214 | String[] selectionArgs = new String[1]; 215 | selectionArgs[0] = clientHandle; 216 | Cursor c = db.query( 217 | ARRIVED_MESSAGE_TABLE_NAME, // Table Name 218 | projection, // The columns to return; 219 | selection, // Columns for WHERE Clause 220 | selectionArgs , // The values for the WHERE Cause 221 | null, //Don't group the rows 222 | null, // Don't filter by row groups 223 | null // The sort order 224 | ); 225 | 226 | if (c.moveToFirst()) { 227 | count = c.getInt(0); 228 | } 229 | c.close(); 230 | return count; 231 | } 232 | 233 | /** 234 | * Delete an MQTT message. 235 | * 236 | * @param clientHandle 237 | * identifier for the client which stored the message 238 | * @param id 239 | * the identifying string returned when the message was stored 240 | * 241 | * @return true if the message was found and deleted 242 | */ 243 | @Override 244 | public boolean discardArrived(String clientHandle, String id) { 245 | 246 | db = mqttDb.getWritableDatabase(); 247 | 248 | traceHandler.traceDebug(TAG, "discardArrived{" + clientHandle + "}, {" 249 | + id + "}"); 250 | int rows; 251 | String[] selectionArgs = new String[2]; 252 | selectionArgs[0] = id; 253 | selectionArgs[1] = clientHandle; 254 | 255 | try { 256 | rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, 257 | MqttServiceConstants.MESSAGE_ID + "=? AND " 258 | + MqttServiceConstants.CLIENT_HANDLE + "=?", 259 | selectionArgs); 260 | } catch (SQLException e) { 261 | traceHandler.traceException(TAG, "discardArrived", e); 262 | throw e; 263 | } 264 | if (rows != 1) { 265 | traceHandler.traceError(TAG, 266 | "discardArrived - Error deleting message {" + id 267 | + "} from database: Rows affected = " + rows); 268 | return false; 269 | } 270 | int count = getArrivedRowCount(clientHandle); 271 | traceHandler 272 | .traceDebug( 273 | TAG, 274 | "discardArrived - Message deleted successfully. - messages in db for this clientHandle " 275 | + count); 276 | return true; 277 | } 278 | 279 | /** 280 | * Get an iterator over all messages stored (optionally for a specific client) 281 | * 282 | * @param clientHandle 283 | * identifier for the client.
284 | * If null, all messages are retrieved 285 | * @return iterator of all the arrived MQTT messages 286 | */ 287 | @Override 288 | public Iterator getAllArrivedMessages( 289 | final String clientHandle) { 290 | return new Iterator() { 291 | private Cursor c; 292 | private boolean hasNext; 293 | private final String[] selectionArgs = { 294 | clientHandle, 295 | }; 296 | 297 | 298 | { 299 | db = mqttDb.getWritableDatabase(); 300 | // anonymous initialiser to start a suitable query 301 | // and position at the first row, if one exists 302 | if (clientHandle == null) { 303 | c = db.query(ARRIVED_MESSAGE_TABLE_NAME, 304 | null, 305 | null, 306 | null, 307 | null, 308 | null, 309 | "mtimestamp ASC"); 310 | } else { 311 | c = db.query(ARRIVED_MESSAGE_TABLE_NAME, 312 | null, 313 | MqttServiceConstants.CLIENT_HANDLE + "=?", 314 | selectionArgs, 315 | null, 316 | null, 317 | "mtimestamp ASC"); 318 | } 319 | hasNext = c.moveToFirst(); 320 | } 321 | 322 | @Override 323 | public boolean hasNext() { 324 | if (!hasNext){ 325 | c.close(); 326 | } 327 | return hasNext; 328 | } 329 | 330 | @Override 331 | public StoredMessage next() { 332 | String messageId = c.getString(c 333 | .getColumnIndex(MqttServiceConstants.MESSAGE_ID)); 334 | String clientHandle = c.getString(c 335 | .getColumnIndex(MqttServiceConstants.CLIENT_HANDLE)); 336 | String topic = c.getString(c 337 | .getColumnIndex(MqttServiceConstants.DESTINATION_NAME)); 338 | byte[] payload = c.getBlob(c 339 | .getColumnIndex(MqttServiceConstants.PAYLOAD)); 340 | int qos = c.getInt(c.getColumnIndex(MqttServiceConstants.QOS)); 341 | boolean retained = Boolean.parseBoolean(c.getString(c 342 | .getColumnIndex(MqttServiceConstants.RETAINED))); 343 | boolean dup = Boolean.parseBoolean(c.getString(c 344 | .getColumnIndex(MqttServiceConstants.DUPLICATE))); 345 | 346 | // build the result 347 | MqttMessageHack message = new MqttMessageHack(payload); 348 | message.setQos(qos); 349 | message.setRetained(retained); 350 | message.setDuplicate(dup); 351 | 352 | // move on 353 | hasNext = c.moveToNext(); 354 | return new DbStoredData(messageId, clientHandle, topic, message); 355 | } 356 | 357 | @Override 358 | public void remove() { 359 | throw new UnsupportedOperationException(); 360 | } 361 | 362 | /* (non-Javadoc) 363 | * @see java.lang.Object#finalize() 364 | */ 365 | @Override 366 | protected void finalize() throws Throwable { 367 | c.close(); 368 | super.finalize(); 369 | } 370 | 371 | }; 372 | } 373 | 374 | /** 375 | * Delete all messages (optionally for a specific client) 376 | * 377 | * @param clientHandle 378 | * identifier for the client.
379 | * If null, all messages are deleted 380 | */ 381 | @Override 382 | public void clearArrivedMessages(String clientHandle) { 383 | 384 | db = mqttDb.getWritableDatabase(); 385 | String[] selectionArgs = new String[1]; 386 | selectionArgs[0] = clientHandle; 387 | 388 | int rows = 0; 389 | if (clientHandle == null) { 390 | traceHandler.traceDebug(TAG, 391 | "clearArrivedMessages: clearing the table"); 392 | rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, null, null); 393 | } else { 394 | traceHandler.traceDebug(TAG, 395 | "clearArrivedMessages: clearing the table of " 396 | + clientHandle + " messages"); 397 | rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, 398 | MqttServiceConstants.CLIENT_HANDLE + "=?", 399 | selectionArgs); 400 | 401 | } 402 | traceHandler.traceDebug(TAG, "clearArrivedMessages: rows affected = " 403 | + rows); 404 | } 405 | 406 | private class DbStoredData implements StoredMessage { 407 | private String messageId; 408 | private String clientHandle; 409 | private String topic; 410 | private MqttMessage message; 411 | 412 | DbStoredData(String messageId, String clientHandle, String topic, 413 | MqttMessage message) { 414 | this.messageId = messageId; 415 | this.topic = topic; 416 | this.message = message; 417 | } 418 | 419 | @Override 420 | public String getMessageId() { 421 | return messageId; 422 | } 423 | 424 | @Override 425 | public String getClientHandle() { 426 | return clientHandle; 427 | } 428 | 429 | @Override 430 | public String getTopic() { 431 | return topic; 432 | } 433 | 434 | @Override 435 | public MqttMessage getMessage() { 436 | return message; 437 | } 438 | } 439 | 440 | /** 441 | * A way to get at the "setDuplicate" method of MqttMessage 442 | */ 443 | private class MqttMessageHack extends MqttMessage { 444 | 445 | public MqttMessageHack(byte[] payload) { 446 | super(payload); 447 | } 448 | 449 | @Override 450 | protected void setDuplicate(boolean dup) { 451 | super.setDuplicate(dup); 452 | } 453 | } 454 | 455 | @Override 456 | public void close() { 457 | if (this.db!=null) 458 | this.db.close(); 459 | 460 | } 461 | 462 | } -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MessageStore.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | import org.eclipse.paho.client.mqttv3.MqttMessage; 16 | 17 | import java.util.Iterator; 18 | 19 | /** 20 | *

21 | * Mechanism for persisting messages until we know they have been received 22 | *

23 | *
    24 | *
  • A Service should store messages as they arrive via 25 | * {@link #storeArrived(String, String, MqttMessage)}. 26 | *
  • When a message has been passed to the consuming entity, 27 | * {@link #discardArrived(String, String)} should be called. 28 | *
  • To recover messages which have not been definitely passed to the 29 | * consumer, {@link MessageStore#getAllArrivedMessages(String)} is used. 30 | *
  • When a clean session is started {@link #clearArrivedMessages(String)} is 31 | * used. 32 | *
33 | */ 34 | interface MessageStore { 35 | 36 | /** 37 | * External representation of a stored message 38 | */ 39 | interface StoredMessage { 40 | /** 41 | * @return the identifier for the message within the store 42 | */ 43 | String getMessageId(); 44 | 45 | /** 46 | * @return the identifier of the client which stored this message 47 | */ 48 | String getClientHandle(); 49 | 50 | /** 51 | * @return the topic on which the message was received 52 | */ 53 | String getTopic(); 54 | 55 | /** 56 | * @return the identifier of the client which stored this message 57 | */ 58 | MqttMessage getMessage(); 59 | } 60 | 61 | /** 62 | * Store a message and return an identifier for it 63 | * 64 | * @param clientHandle 65 | * identifier for the client 66 | * @param message 67 | * message to be stored 68 | * @return a unique identifier for it 69 | */ 70 | String storeArrived(String clientHandle, String Topic, 71 | MqttMessage message); 72 | 73 | /** 74 | * Discard a message - called when we are certain that an arrived message 75 | * has reached the application. 76 | * 77 | * @param clientHandle 78 | * identifier for the client 79 | * @param id 80 | * id of message to be discarded 81 | */ 82 | boolean discardArrived(String clientHandle, String id); 83 | 84 | /** 85 | * Get all the stored messages, usually for a specific client 86 | * 87 | * @param clientHandle 88 | * identifier for the client - if null, then messages for all 89 | * clients are returned 90 | */ 91 | Iterator getAllArrivedMessages(String clientHandle); 92 | 93 | /** 94 | * Discard stored messages, usually for a specific client 95 | * 96 | * @param clientHandle 97 | * identifier for the client - if null, then messages for all 98 | * clients are discarded 99 | */ 100 | void clearArrivedMessages(String clientHandle); 101 | 102 | void close(); 103 | } 104 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttConnection.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2016 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | import android.app.Service; 16 | import android.content.Context; 17 | import android.os.Bundle; 18 | import android.os.PowerManager; 19 | import android.os.PowerManager.WakeLock; 20 | import android.util.Log; 21 | 22 | import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; 23 | import org.eclipse.paho.client.mqttv3.IMqttActionListener; 24 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 25 | import org.eclipse.paho.client.mqttv3.IMqttMessageListener; 26 | import org.eclipse.paho.client.mqttv3.IMqttToken; 27 | import org.eclipse.paho.client.mqttv3.MqttAsyncClient; 28 | import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; 29 | import org.eclipse.paho.client.mqttv3.MqttClientPersistence; 30 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 31 | import org.eclipse.paho.client.mqttv3.MqttException; 32 | import org.eclipse.paho.client.mqttv3.MqttMessage; 33 | import org.eclipse.paho.client.mqttv3.MqttPersistenceException; 34 | import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; 35 | 36 | import java.io.File; 37 | import java.util.Arrays; 38 | import java.util.HashMap; 39 | import java.util.Iterator; 40 | import java.util.Map; 41 | 42 | /** 43 | *

44 | * MqttConnection holds a MqttAsyncClient {host,port,clientId} instance to perform 45 | * MQTT operations to MQTT broker. 46 | *

47 | *

48 | * Most of the major API here is intended to implement the most general forms of 49 | * the methods in IMqttAsyncClient, with slight adjustments for the Android 50 | * environment
51 | * These adjustments usually consist of adding two parameters to each method :- 52 | *

    53 | *
  • invocationContext - a string passed from the application to identify the 54 | * context of the operation (mainly included for support of the javascript API 55 | * implementation)
  • 56 | *
  • activityToken - a string passed from the Activity to relate back to a 57 | * callback method or other context-specific data
  • 58 | *
59 | *

60 | *

61 | * Operations are very much asynchronous, so success and failure are notified by 62 | * packing the relevant data into Intent objects which are broadcast back to the 63 | * Activity via the MqttService.callbackToActivity() method. 64 | *

65 | */ 66 | class MqttConnection implements MqttCallbackExtended { 67 | 68 | // Strings for Intents etc.. 69 | private static final String TAG = "MqttConnection"; 70 | // Error status messages 71 | private static final String NOT_CONNECTED = "not connected"; 72 | 73 | // fields for the connection definition 74 | private String serverURI; 75 | public String getServerURI() { 76 | return serverURI; 77 | } 78 | 79 | public void setServerURI(String serverURI) { 80 | this.serverURI = serverURI; 81 | } 82 | 83 | public String getClientId() { 84 | return clientId; 85 | } 86 | 87 | public void setClientId(String clientId) { 88 | this.clientId = clientId; 89 | } 90 | 91 | private String clientId; 92 | private MqttClientPersistence persistence = null; 93 | private MqttConnectOptions connectOptions; 94 | 95 | public MqttConnectOptions getConnectOptions() { 96 | return connectOptions; 97 | } 98 | 99 | public void setConnectOptions(MqttConnectOptions connectOptions) { 100 | this.connectOptions = connectOptions; 101 | } 102 | 103 | // Client handle, used for callbacks... 104 | private String clientHandle; 105 | 106 | public String getClientHandle() { 107 | return clientHandle; 108 | } 109 | 110 | public void setClientHandle(String clientHandle) { 111 | this.clientHandle = clientHandle; 112 | } 113 | 114 | //store connect ActivityToken for reconnect 115 | private String reconnectActivityToken = null; 116 | 117 | // our client object - instantiated on connect 118 | private MqttAsyncClient myClient = null; 119 | 120 | private AlarmPingSender alarmPingSender = null; 121 | 122 | // our (parent) service object 123 | private MqttService service = null; 124 | 125 | private volatile boolean disconnected = true; 126 | private boolean cleanSession = true; 127 | 128 | // Indicate this connection is connecting or not. 129 | // This variable uses to avoid reconnect multiple times. 130 | private volatile boolean isConnecting = false; 131 | 132 | // Saved sent messages and their corresponding Topics, activityTokens and 133 | // invocationContexts, so we can handle "deliveryComplete" callbacks 134 | // from the mqttClient 135 | private Map savedTopics = new HashMap<>(); 136 | private Map savedSentMessages = new HashMap<>(); 137 | private Map savedActivityTokens = new HashMap<>(); 138 | private Map savedInvocationContexts = new HashMap<>(); 139 | 140 | private WakeLock wakelock = null; 141 | private String wakeLockTag = null; 142 | 143 | private DisconnectedBufferOptions bufferOpts = null; 144 | 145 | /** 146 | * Constructor - create an MqttConnection to communicate with MQTT server 147 | * 148 | * @param service 149 | * our "parent" service - we make callbacks to it 150 | * @param serverURI 151 | * the URI of the MQTT server to which we will connect 152 | * @param clientId 153 | * the name by which we will identify ourselves to the MQTT 154 | * server 155 | * @param persistence 156 | * the persistence class to use to store in-flight message. If 157 | * null then the default persistence mechanism is used 158 | * @param clientHandle 159 | * the "handle" by which the activity will identify us 160 | */ 161 | MqttConnection(MqttService service, String serverURI, String clientId, 162 | MqttClientPersistence persistence, String clientHandle) { 163 | this.serverURI = serverURI; 164 | this.service = service; 165 | this.clientId = clientId; 166 | this.persistence = persistence; 167 | this.clientHandle = clientHandle; 168 | 169 | StringBuilder stringBuilder = new StringBuilder(this.getClass().getCanonicalName()); 170 | stringBuilder.append(" "); 171 | stringBuilder.append(clientId); 172 | stringBuilder.append(" "); 173 | stringBuilder.append("on host "); 174 | stringBuilder.append(serverURI); 175 | wakeLockTag = stringBuilder.toString(); 176 | } 177 | 178 | // The major API implementation follows 179 | /** 180 | * Connect to the server specified when we were instantiated 181 | * 182 | * @param options 183 | * timeout, etc 184 | * @param invocationContext 185 | * arbitrary data to be passed back to the application 186 | * @param activityToken 187 | * arbitrary identifier to be passed back to the Activity 188 | */ 189 | public void connect(MqttConnectOptions options, String invocationContext, 190 | String activityToken) { 191 | 192 | connectOptions = options; 193 | reconnectActivityToken = activityToken; 194 | 195 | if (options != null) { 196 | cleanSession = options.isCleanSession(); 197 | } 198 | 199 | if (connectOptions.isCleanSession()) { // if it's a clean session, 200 | // discard old data 201 | service.messageStore.clearArrivedMessages(clientHandle); 202 | } 203 | 204 | service.traceDebug(TAG, "Connecting {" + serverURI + "} as {" + clientId + "}"); 205 | final Bundle resultBundle = new Bundle(); 206 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 207 | activityToken); 208 | resultBundle.putString( 209 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 210 | invocationContext); 211 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 212 | MqttServiceConstants.CONNECT_ACTION); 213 | 214 | 215 | try { 216 | if (persistence == null) { 217 | // ask Android where we can put files 218 | File myDir = service.getExternalFilesDir(TAG); 219 | 220 | if (myDir == null) { 221 | // No external storage, use internal storage instead. 222 | myDir = service.getDir(TAG, Context.MODE_PRIVATE); 223 | 224 | if(myDir == null){ 225 | //Shouldn't happen. 226 | resultBundle.putString( 227 | MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 228 | "Error! No external and internal storage available"); 229 | resultBundle.putSerializable( 230 | MqttServiceConstants.CALLBACK_EXCEPTION, new MqttPersistenceException()); 231 | service.callbackToActivity(clientHandle, Status.ERROR, 232 | resultBundle); 233 | return; 234 | } 235 | } 236 | 237 | // use that to setup MQTT client persistence storage 238 | persistence = new MqttDefaultFilePersistence( 239 | myDir.getAbsolutePath()); 240 | } 241 | 242 | IMqttActionListener listener = new MqttConnectionListener( 243 | resultBundle) { 244 | 245 | @Override 246 | public void onSuccess(IMqttToken asyncActionToken) { 247 | doAfterConnectSuccess(resultBundle); 248 | service.traceDebug(TAG, "connect success!"); 249 | } 250 | 251 | @Override 252 | public void onFailure(IMqttToken asyncActionToken, 253 | Throwable exception) { 254 | resultBundle.putString( 255 | MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 256 | exception.getLocalizedMessage()); 257 | resultBundle.putSerializable( 258 | MqttServiceConstants.CALLBACK_EXCEPTION, exception); 259 | service.traceError(TAG, 260 | "connect fail, call connect to reconnect.reason:" 261 | + exception.getMessage()); 262 | 263 | doAfterConnectFail(resultBundle); 264 | 265 | } 266 | }; 267 | 268 | if (myClient != null) { 269 | if (isConnecting ) { 270 | service.traceDebug(TAG, 271 | "myClient != null and the client is connecting. Connect return directly."); 272 | service.traceDebug(TAG,"Connect return:isConnecting:"+isConnecting+".disconnected:"+disconnected); 273 | }else if(!disconnected){ 274 | service.traceDebug(TAG,"myClient != null and the client is connected and notify!"); 275 | doAfterConnectSuccess(resultBundle); 276 | } 277 | else { 278 | service.traceDebug(TAG, "myClient != null and the client is not connected"); 279 | service.traceDebug(TAG,"Do Real connect!"); 280 | setConnectingState(true); 281 | myClient.connect(connectOptions, invocationContext, listener); 282 | } 283 | } 284 | 285 | // if myClient is null, then create a new connection 286 | else { 287 | alarmPingSender = new AlarmPingSender(service); 288 | myClient = new MqttAsyncClient(serverURI, clientId, 289 | persistence, alarmPingSender); 290 | myClient.setCallback(this); 291 | 292 | service.traceDebug(TAG,"Do Real connect!"); 293 | setConnectingState(true); 294 | myClient.connect(connectOptions, invocationContext, listener); 295 | } 296 | } catch (Exception e) { 297 | service.traceError(TAG, "Exception occurred attempting to connect: " + e.getMessage()); 298 | setConnectingState(false); 299 | handleException(resultBundle, e); 300 | } 301 | } 302 | 303 | private void doAfterConnectSuccess(final Bundle resultBundle) { 304 | //since the device's cpu can go to sleep, acquire a wakelock and drop it later. 305 | acquireWakeLock(); 306 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 307 | deliverBacklog(); 308 | setConnectingState(false); 309 | disconnected = false; 310 | releaseWakeLock(); 311 | } 312 | 313 | @Override 314 | public void connectComplete(boolean reconnect, String serverURI) { 315 | Bundle resultBundle = new Bundle(); 316 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 317 | MqttServiceConstants.CONNECT_EXTENDED_ACTION); 318 | resultBundle.putBoolean(MqttServiceConstants.CALLBACK_RECONNECT, reconnect); 319 | resultBundle.putString(MqttServiceConstants.CALLBACK_SERVER_URI, serverURI); 320 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 321 | } 322 | 323 | private void doAfterConnectFail(final Bundle resultBundle){ 324 | // 325 | acquireWakeLock(); 326 | disconnected = true; 327 | setConnectingState(false); 328 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 329 | releaseWakeLock(); 330 | } 331 | 332 | private void handleException(final Bundle resultBundle, Exception e) { 333 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 334 | e.getLocalizedMessage()); 335 | 336 | resultBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e); 337 | 338 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 339 | } 340 | 341 | /** 342 | * Attempt to deliver any outstanding messages we've received but which the 343 | * application hasn't acknowledged. If "cleanSession" was specified, we'll 344 | * have already purged any such messages from our messageStore. 345 | */ 346 | private void deliverBacklog() { 347 | Iterator backlog = service.messageStore 348 | .getAllArrivedMessages(clientHandle); 349 | while (backlog.hasNext()) { 350 | MessageStore.StoredMessage msgArrived = backlog.next(); 351 | Bundle resultBundle = messageToBundle(msgArrived.getMessageId(), 352 | msgArrived.getTopic(), msgArrived.getMessage()); 353 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 354 | MqttServiceConstants.MESSAGE_ARRIVED_ACTION); 355 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 356 | } 357 | } 358 | 359 | /** 360 | * Create a bundle containing all relevant data pertaining to a message 361 | * 362 | * @param messageId 363 | * the message's identifier in the messageStore, so that a 364 | * callback can be made to remove it once delivered 365 | * @param topic 366 | * the topic on which the message was delivered 367 | * @param message 368 | * the message itself 369 | * @return the bundle 370 | */ 371 | private Bundle messageToBundle(String messageId, String topic, 372 | MqttMessage message) { 373 | Bundle result = new Bundle(); 374 | result.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID, messageId); 375 | result.putString(MqttServiceConstants.CALLBACK_DESTINATION_NAME, topic); 376 | result.putParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL, 377 | new ParcelableMqttMessage(message)); 378 | return result; 379 | } 380 | 381 | /** 382 | * Close connection from the server 383 | * 384 | */ 385 | void close() { 386 | service.traceDebug(TAG, "close()"); 387 | try { 388 | if (myClient != null) { 389 | myClient.close(); 390 | } 391 | } catch (MqttException e) { 392 | // Pass a new bundle, let handleException stores error messages. 393 | handleException(new Bundle(), e); 394 | } 395 | } 396 | 397 | /** 398 | * Disconnect from the server 399 | * 400 | * @param quiesceTimeout 401 | * in milliseconds 402 | * @param invocationContext 403 | * arbitrary data to be passed back to the application 404 | * @param activityToken 405 | * arbitrary string to be passed back to the activity 406 | */ 407 | void disconnect(long quiesceTimeout, String invocationContext, 408 | String activityToken) { 409 | service.traceDebug(TAG, "disconnect()"); 410 | disconnected = true; 411 | final Bundle resultBundle = new Bundle(); 412 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 413 | activityToken); 414 | resultBundle.putString( 415 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 416 | invocationContext); 417 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 418 | MqttServiceConstants.DISCONNECT_ACTION); 419 | if ((myClient != null) && (myClient.isConnected())) { 420 | IMqttActionListener listener = new MqttConnectionListener( 421 | resultBundle); 422 | try { 423 | myClient.disconnect(quiesceTimeout, invocationContext, listener); 424 | } catch (Exception e) { 425 | handleException(resultBundle, e); 426 | } 427 | } else { 428 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 429 | NOT_CONNECTED); 430 | service.traceError(MqttServiceConstants.DISCONNECT_ACTION, 431 | NOT_CONNECTED); 432 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 433 | } 434 | 435 | if (connectOptions != null && connectOptions.isCleanSession()) { 436 | // assume we'll clear the stored messages at this point 437 | service.messageStore.clearArrivedMessages(clientHandle); 438 | } 439 | 440 | releaseWakeLock(); 441 | } 442 | 443 | /** 444 | * Disconnect from the server 445 | * 446 | * @param invocationContext 447 | * arbitrary data to be passed back to the application 448 | * @param activityToken 449 | * arbitrary string to be passed back to the activity 450 | */ 451 | void disconnect(String invocationContext, String activityToken) { 452 | service.traceDebug(TAG, "disconnect()"); 453 | disconnected = true; 454 | final Bundle resultBundle = new Bundle(); 455 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 456 | activityToken); 457 | resultBundle.putString( 458 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 459 | invocationContext); 460 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 461 | MqttServiceConstants.DISCONNECT_ACTION); 462 | if ((myClient != null) && (myClient.isConnected())) { 463 | IMqttActionListener listener = new MqttConnectionListener( 464 | resultBundle); 465 | try { 466 | myClient.disconnect(invocationContext, listener); 467 | } catch (Exception e) { 468 | handleException(resultBundle, e); 469 | } 470 | } else { 471 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 472 | NOT_CONNECTED); 473 | service.traceError(MqttServiceConstants.DISCONNECT_ACTION, 474 | NOT_CONNECTED); 475 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 476 | } 477 | 478 | if (connectOptions != null && connectOptions.isCleanSession()) { 479 | // assume we'll clear the stored messages at this point 480 | service.messageStore.clearArrivedMessages(clientHandle); 481 | } 482 | releaseWakeLock(); 483 | } 484 | 485 | /** 486 | * @return true if we are connected to an MQTT server 487 | */ 488 | public boolean isConnected() { 489 | return myClient != null && myClient.isConnected(); 490 | } 491 | 492 | /** 493 | * Publish a message on a topic 494 | * 495 | * @param topic 496 | * the topic on which to publish - represented as a string, not 497 | * an MqttTopic object 498 | * @param payload 499 | * the content of the message to publish 500 | * @param qos 501 | * the quality of service requested 502 | * @param retained 503 | * whether the MQTT server should retain this message 504 | * @param invocationContext 505 | * arbitrary data to be passed back to the application 506 | * @param activityToken 507 | * arbitrary string to be passed back to the activity 508 | * @return token for tracking the operation 509 | */ 510 | public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, 511 | boolean retained, String invocationContext, String activityToken) { 512 | final Bundle resultBundle = new Bundle(); 513 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 514 | MqttServiceConstants.SEND_ACTION); 515 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 516 | activityToken); 517 | resultBundle.putString( 518 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 519 | invocationContext); 520 | 521 | IMqttDeliveryToken sendToken = null; 522 | 523 | if ((myClient != null) && (myClient.isConnected())) { 524 | IMqttActionListener listener = new MqttConnectionListener( 525 | resultBundle); 526 | try { 527 | MqttMessage message = new MqttMessage(payload); 528 | message.setQos(qos); 529 | message.setRetained(retained); 530 | sendToken = myClient.publish(topic, payload, qos, retained, 531 | invocationContext, listener); 532 | storeSendDetails(topic, message, sendToken, invocationContext, 533 | activityToken); 534 | } catch (Exception e) { 535 | handleException(resultBundle, e); 536 | } 537 | } else { 538 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 539 | NOT_CONNECTED); 540 | service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED); 541 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 542 | } 543 | 544 | return sendToken; 545 | } 546 | 547 | /** 548 | * Publish a message on a topic 549 | * 550 | * @param topic 551 | * the topic on which to publish - represented as a string, not 552 | * an MqttTopic object 553 | * @param message 554 | * the message to publish 555 | * @param invocationContext 556 | * arbitrary data to be passed back to the application 557 | * @param activityToken 558 | * arbitrary string to be passed back to the activity 559 | * @return token for tracking the operation 560 | */ 561 | public IMqttDeliveryToken publish(String topic, MqttMessage message, 562 | String invocationContext, String activityToken) { 563 | final Bundle resultBundle = new Bundle(); 564 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 565 | MqttServiceConstants.SEND_ACTION); 566 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 567 | activityToken); 568 | resultBundle.putString( 569 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 570 | invocationContext); 571 | 572 | IMqttDeliveryToken sendToken = null; 573 | 574 | if ((myClient != null) && (myClient.isConnected())) { 575 | IMqttActionListener listener = new MqttConnectionListener( 576 | resultBundle); 577 | try { 578 | sendToken = myClient.publish(topic, message, invocationContext, 579 | listener); 580 | storeSendDetails(topic, message, sendToken, invocationContext, 581 | activityToken); 582 | } catch (Exception e) { 583 | handleException(resultBundle, e); 584 | } 585 | } else if ((myClient !=null) && (this.bufferOpts != null) && (this.bufferOpts.isBufferEnabled())){ 586 | // Client is not connected, but buffer is enabled, so sending message 587 | IMqttActionListener listener = new MqttConnectionListener( 588 | resultBundle); 589 | try { 590 | sendToken = myClient.publish(topic, message, invocationContext, 591 | listener); 592 | storeSendDetails(topic, message, sendToken, invocationContext, 593 | activityToken); 594 | } catch (Exception e) { 595 | handleException(resultBundle, e); 596 | } 597 | } else { 598 | Log.i(TAG, "Client is not connected, so not sending message"); 599 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 600 | NOT_CONNECTED); 601 | service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED); 602 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 603 | } 604 | return sendToken; 605 | } 606 | 607 | /** 608 | * Subscribe to a topic 609 | * 610 | * @param topic 611 | * a possibly wildcarded topic name 612 | * @param qos 613 | * requested quality of service for the topic 614 | * @param invocationContext 615 | * arbitrary data to be passed back to the application 616 | * @param activityToken 617 | * arbitrary identifier to be passed back to the Activity 618 | */ 619 | public void subscribe(final String topic, final int qos, 620 | String invocationContext, String activityToken) { 621 | service.traceDebug(TAG, "subscribe({" + topic + "}," + qos + ",{" 622 | + invocationContext + "}, {" + activityToken + "}"); 623 | final Bundle resultBundle = new Bundle(); 624 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 625 | MqttServiceConstants.SUBSCRIBE_ACTION); 626 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 627 | activityToken); 628 | resultBundle.putString( 629 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 630 | invocationContext); 631 | 632 | if ((myClient != null) && (myClient.isConnected())) { 633 | IMqttActionListener listener = new MqttConnectionListener( 634 | resultBundle); 635 | try { 636 | myClient.subscribe(topic, qos, invocationContext, listener); 637 | } catch (Exception e) { 638 | handleException(resultBundle, e); 639 | } 640 | } else { 641 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 642 | NOT_CONNECTED); 643 | service.traceError("subscribe", NOT_CONNECTED); 644 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 645 | } 646 | } 647 | 648 | /** 649 | * Subscribe to one or more topics 650 | * 651 | * @param topic 652 | * a list of possibly wildcarded topic names 653 | * @param qos 654 | * requested quality of service for each topic 655 | * @param invocationContext 656 | * arbitrary data to be passed back to the application 657 | * @param activityToken 658 | * arbitrary identifier to be passed back to the Activity 659 | */ 660 | public void subscribe(final String[] topic, final int[] qos, 661 | String invocationContext, String activityToken) { 662 | service.traceDebug(TAG, "subscribe({" + Arrays.toString(topic) + "}," + Arrays.toString(qos) + ",{" 663 | + invocationContext + "}, {" + activityToken + "}"); 664 | final Bundle resultBundle = new Bundle(); 665 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 666 | MqttServiceConstants.SUBSCRIBE_ACTION); 667 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 668 | activityToken); 669 | resultBundle.putString( 670 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 671 | invocationContext); 672 | 673 | if ((myClient != null) && (myClient.isConnected())) { 674 | IMqttActionListener listener = new MqttConnectionListener( 675 | resultBundle); 676 | try { 677 | myClient.subscribe(topic, qos, invocationContext, listener); 678 | } catch (Exception e) { 679 | handleException(resultBundle, e); 680 | } 681 | } else { 682 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 683 | NOT_CONNECTED); 684 | service.traceError("subscribe", NOT_CONNECTED); 685 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 686 | } 687 | } 688 | 689 | public void subscribe(String[] topicFilters, int[] qos, String invocationContext, String activityToken, IMqttMessageListener[] messageListeners) { 690 | service.traceDebug(TAG, "subscribe({" + Arrays.toString(topicFilters) + "}," + Arrays.toString(qos) + ",{" 691 | + invocationContext + "}, {" + activityToken + "}"); 692 | final Bundle resultBundle = new Bundle(); 693 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.SUBSCRIBE_ACTION); 694 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, activityToken); 695 | resultBundle.putString(MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, invocationContext); 696 | if((myClient != null) && (myClient.isConnected())){ 697 | IMqttActionListener listener = new MqttConnectionListener(resultBundle); 698 | try { 699 | 700 | myClient.subscribe(topicFilters, qos,messageListeners); 701 | } catch (Exception e){ 702 | handleException(resultBundle, e); 703 | } 704 | } else { 705 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, NOT_CONNECTED); 706 | service.traceError("subscribe", NOT_CONNECTED); 707 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 708 | } 709 | } 710 | 711 | /** 712 | * Unsubscribe from a topic 713 | * 714 | * @param topic 715 | * a possibly wildcarded topic name 716 | * @param invocationContext 717 | * arbitrary data to be passed back to the application 718 | * @param activityToken 719 | * arbitrary identifier to be passed back to the Activity 720 | */ 721 | void unsubscribe(final String topic, String invocationContext, 722 | String activityToken) { 723 | service.traceDebug(TAG, "unsubscribe({" + topic + "},{" 724 | + invocationContext + "}, {" + activityToken + "})"); 725 | final Bundle resultBundle = new Bundle(); 726 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 727 | MqttServiceConstants.UNSUBSCRIBE_ACTION); 728 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 729 | activityToken); 730 | resultBundle.putString( 731 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 732 | invocationContext); 733 | if ((myClient != null) && (myClient.isConnected())) { 734 | IMqttActionListener listener = new MqttConnectionListener( 735 | resultBundle); 736 | try { 737 | myClient.unsubscribe(topic, invocationContext, listener); 738 | } catch (Exception e) { 739 | handleException(resultBundle, e); 740 | } 741 | } else { 742 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 743 | NOT_CONNECTED); 744 | 745 | service.traceError("subscribe", NOT_CONNECTED); 746 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 747 | } 748 | } 749 | 750 | /** 751 | * Unsubscribe from one or more topics 752 | * 753 | * @param topic 754 | * a list of possibly wildcarded topic names 755 | * @param invocationContext 756 | * arbitrary data to be passed back to the application 757 | * @param activityToken 758 | * arbitrary identifier to be passed back to the Activity 759 | */ 760 | void unsubscribe(final String[] topic, String invocationContext, 761 | String activityToken) { 762 | service.traceDebug(TAG, "unsubscribe({" + Arrays.toString(topic) + "},{" 763 | + invocationContext + "}, {" + activityToken + "})"); 764 | final Bundle resultBundle = new Bundle(); 765 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 766 | MqttServiceConstants.UNSUBSCRIBE_ACTION); 767 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 768 | activityToken); 769 | resultBundle.putString( 770 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 771 | invocationContext); 772 | if ((myClient != null) && (myClient.isConnected())) { 773 | IMqttActionListener listener = new MqttConnectionListener( 774 | resultBundle); 775 | try { 776 | myClient.unsubscribe(topic, invocationContext, listener); 777 | } catch (Exception e) { 778 | handleException(resultBundle, e); 779 | } 780 | } else { 781 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 782 | NOT_CONNECTED); 783 | 784 | service.traceError("subscribe", NOT_CONNECTED); 785 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 786 | } 787 | } 788 | 789 | /** 790 | * Get tokens for all outstanding deliveries for a client 791 | * 792 | * @return an array (possibly empty) of tokens 793 | */ 794 | public IMqttDeliveryToken[] getPendingDeliveryTokens() { 795 | return myClient.getPendingDeliveryTokens(); 796 | } 797 | 798 | // Implement MqttCallback 799 | /** 800 | * Callback for connectionLost 801 | * 802 | * @param why 803 | * the exeception causing the break in communications 804 | */ 805 | @Override 806 | public void connectionLost(Throwable why) { 807 | service.traceDebug(TAG, "connectionLost(" + why.getMessage() + ")"); 808 | disconnected = true; 809 | try { 810 | if(!this.connectOptions.isAutomaticReconnect()) { 811 | myClient.disconnect(null, new IMqttActionListener() { 812 | 813 | @Override 814 | public void onSuccess(IMqttToken asyncActionToken) { 815 | // No action 816 | } 817 | 818 | @Override 819 | public void onFailure(IMqttToken asyncActionToken, 820 | Throwable exception) { 821 | // No action 822 | } 823 | }); 824 | } else { 825 | // Using the new Automatic reconnect functionality. 826 | // We can't force a disconnection, but we can speed one up 827 | alarmPingSender.schedule(100); 828 | 829 | } 830 | } catch (Exception e) { 831 | // ignore it - we've done our best 832 | } 833 | 834 | Bundle resultBundle = new Bundle(); 835 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 836 | MqttServiceConstants.ON_CONNECTION_LOST_ACTION); 837 | if (why != null) { 838 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 839 | why.getMessage()); 840 | if (why instanceof MqttException) { 841 | resultBundle.putSerializable( 842 | MqttServiceConstants.CALLBACK_EXCEPTION, why); 843 | } 844 | resultBundle.putString( 845 | MqttServiceConstants.CALLBACK_EXCEPTION_STACK, 846 | Log.getStackTraceString(why)); 847 | } 848 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 849 | // client has lost connection no need for wake lock 850 | releaseWakeLock(); 851 | } 852 | 853 | /** 854 | * Callback to indicate a message has been delivered (the exact meaning of 855 | * "has been delivered" is dependent on the QOS value) 856 | * 857 | * @param messageToken 858 | * the messge token provided when the message was originally sent 859 | */ 860 | @Override 861 | public void deliveryComplete(IMqttDeliveryToken messageToken) { 862 | 863 | service.traceDebug(TAG, "deliveryComplete(" + messageToken + ")"); 864 | 865 | MqttMessage message = savedSentMessages.remove(messageToken); 866 | if (message != null) { // If I don't know about the message, it's 867 | // irrelevant 868 | String topic = savedTopics.remove(messageToken); 869 | String activityToken = savedActivityTokens.remove(messageToken); 870 | String invocationContext = savedInvocationContexts 871 | .remove(messageToken); 872 | 873 | Bundle resultBundle = messageToBundle(null, topic, message); 874 | if (activityToken != null) { 875 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 876 | MqttServiceConstants.SEND_ACTION); 877 | resultBundle.putString( 878 | MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 879 | activityToken); 880 | resultBundle.putString( 881 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, 882 | invocationContext); 883 | 884 | service.callbackToActivity(clientHandle, Status.OK, 885 | resultBundle); 886 | } 887 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 888 | MqttServiceConstants.MESSAGE_DELIVERED_ACTION); 889 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 890 | } 891 | 892 | // this notification will have kept the connection alive but send the previously sechudled ping anyway 893 | } 894 | 895 | /** 896 | * Callback when a message is received 897 | * 898 | * @param topic 899 | * the topic on which the message was received 900 | * @param message 901 | * the message itself 902 | */ 903 | @Override 904 | public void messageArrived(String topic, MqttMessage message) 905 | throws Exception { 906 | 907 | service.traceDebug(TAG, 908 | "messageArrived(" + topic + ",{" + message.toString() + "})"); 909 | 910 | String messageId = service.messageStore.storeArrived(clientHandle, 911 | topic, message); 912 | 913 | Bundle resultBundle = messageToBundle(messageId, topic, message); 914 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 915 | MqttServiceConstants.MESSAGE_ARRIVED_ACTION); 916 | resultBundle.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID, 917 | messageId); 918 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 919 | 920 | } 921 | 922 | 923 | 924 | /** 925 | * Store details of sent messages so we can handle "deliveryComplete" 926 | * callbacks from the mqttClient 927 | * 928 | * @param topic 929 | * @param msg 930 | * @param messageToken 931 | * @param invocationContext 932 | * @param activityToken 933 | */ 934 | private void storeSendDetails(final String topic, final MqttMessage msg, 935 | final IMqttDeliveryToken messageToken, 936 | final String invocationContext, final String activityToken) { 937 | savedTopics.put(messageToken, topic); 938 | savedSentMessages.put(messageToken, msg); 939 | savedActivityTokens.put(messageToken, activityToken); 940 | savedInvocationContexts.put(messageToken, invocationContext); 941 | } 942 | 943 | /** 944 | * Acquires a partial wake lock for this client 945 | */ 946 | private void acquireWakeLock() { 947 | if (wakelock == null) { 948 | PowerManager pm = (PowerManager) service 949 | .getSystemService(Service.POWER_SERVICE); 950 | wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 951 | wakeLockTag); 952 | } 953 | wakelock.acquire(); 954 | 955 | } 956 | 957 | /** 958 | * Releases the currently held wake lock for this client 959 | */ 960 | private void releaseWakeLock() { 961 | if(wakelock != null && wakelock.isHeld()){ 962 | wakelock.release(); 963 | } 964 | } 965 | 966 | 967 | 968 | /** 969 | * General-purpose IMqttActionListener for the Client context 970 | *

971 | * Simply handles the basic success/failure cases for operations which don't 972 | * return results 973 | * 974 | */ 975 | private class MqttConnectionListener implements IMqttActionListener { 976 | 977 | private final Bundle resultBundle; 978 | 979 | private MqttConnectionListener(Bundle resultBundle) { 980 | this.resultBundle = resultBundle; 981 | } 982 | 983 | @Override 984 | public void onSuccess(IMqttToken asyncActionToken) { 985 | service.callbackToActivity(clientHandle, Status.OK, resultBundle); 986 | } 987 | 988 | @Override 989 | public void onFailure(IMqttToken asyncActionToken, Throwable exception) { 990 | resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 991 | exception.getLocalizedMessage()); 992 | 993 | resultBundle.putSerializable( 994 | MqttServiceConstants.CALLBACK_EXCEPTION, exception); 995 | 996 | service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); 997 | } 998 | } 999 | 1000 | /** 1001 | * Receive notification that we are offline
1002 | * if cleanSession is true, we need to regard this as a disconnection 1003 | */ 1004 | void offline() { 1005 | 1006 | if (!disconnected && !cleanSession) { 1007 | Exception e = new Exception("Android offline"); 1008 | connectionLost(e); 1009 | } 1010 | } 1011 | 1012 | /** 1013 | * Reconnect
1014 | * Only appropriate if cleanSession is false and we were connected. 1015 | * Declare as synchronized to avoid multiple calls to this method to send connect 1016 | * multiple times 1017 | */ 1018 | synchronized void reconnect() { 1019 | 1020 | if (myClient == null) { 1021 | service.traceError(TAG,"Reconnect myClient = null. Will not do reconnect"); 1022 | return; 1023 | } 1024 | 1025 | if (isConnecting) { 1026 | service.traceDebug(TAG, "The client is connecting. Reconnect return directly."); 1027 | return ; 1028 | } 1029 | 1030 | if(!service.isOnline()){ 1031 | service.traceDebug(TAG, 1032 | "The network is not reachable. Will not do reconnect"); 1033 | return; 1034 | } 1035 | 1036 | if(connectOptions.isAutomaticReconnect()){ 1037 | //The Automatic reconnect functionality is enabled here 1038 | Log.i(TAG, "Requesting Automatic reconnect using New Java AC"); 1039 | final Bundle resultBundle = new Bundle(); 1040 | resultBundle.putString( 1041 | MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 1042 | reconnectActivityToken); 1043 | resultBundle.putString( 1044 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null); 1045 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 1046 | MqttServiceConstants.CONNECT_ACTION); 1047 | try { 1048 | myClient.reconnect(); 1049 | } catch (MqttException ex){ 1050 | Log.e(TAG, "Exception occurred attempting to reconnect: " + ex.getMessage()); 1051 | setConnectingState(false); 1052 | handleException(resultBundle, ex); 1053 | } 1054 | } else if (disconnected && !cleanSession) { 1055 | // use the activityToke the same with action connect 1056 | service.traceDebug(TAG,"Do Real Reconnect!"); 1057 | final Bundle resultBundle = new Bundle(); 1058 | resultBundle.putString( 1059 | MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, 1060 | reconnectActivityToken); 1061 | resultBundle.putString( 1062 | MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null); 1063 | resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, 1064 | MqttServiceConstants.CONNECT_ACTION); 1065 | 1066 | try { 1067 | 1068 | IMqttActionListener listener = new MqttConnectionListener(resultBundle) { 1069 | @Override 1070 | public void onSuccess(IMqttToken asyncActionToken) { 1071 | // since the device's cpu can go to sleep, acquire a 1072 | // wakelock and drop it later. 1073 | service.traceDebug(TAG,"Reconnect Success!"); 1074 | service.traceDebug(TAG,"DeliverBacklog when reconnect."); 1075 | doAfterConnectSuccess(resultBundle); 1076 | } 1077 | 1078 | @Override 1079 | public void onFailure(IMqttToken asyncActionToken, Throwable exception) { 1080 | resultBundle.putString( 1081 | MqttServiceConstants.CALLBACK_ERROR_MESSAGE, 1082 | exception.getLocalizedMessage()); 1083 | resultBundle.putSerializable( 1084 | MqttServiceConstants.CALLBACK_EXCEPTION, 1085 | exception); 1086 | service.callbackToActivity(clientHandle, Status.ERROR, 1087 | resultBundle); 1088 | 1089 | doAfterConnectFail(resultBundle); 1090 | 1091 | } 1092 | }; 1093 | 1094 | myClient.connect(connectOptions, null, listener); 1095 | setConnectingState(true); 1096 | } catch (MqttException e) { 1097 | service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage()); 1098 | setConnectingState(false); 1099 | handleException(resultBundle, e); 1100 | } catch (Exception e){ 1101 | /* TODO: Added Due to: https://github.com/eclipse/paho.mqtt.android/issues/101 1102 | For some reason in a small number of cases, myClient is null here and so 1103 | a NullPointer Exception is thrown. This is a workaround to pass the exception 1104 | up to the application. myClient should not be null so more investigation is 1105 | required. 1106 | */ 1107 | service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage()); 1108 | setConnectingState(false); 1109 | MqttException newEx = new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR, e.getCause()); 1110 | handleException(resultBundle, newEx); 1111 | } 1112 | } 1113 | } 1114 | 1115 | /** 1116 | * 1117 | * @param isConnecting 1118 | */ 1119 | private synchronized void setConnectingState(boolean isConnecting){ 1120 | this.isConnecting = isConnecting; 1121 | } 1122 | 1123 | /** 1124 | * Sets the DisconnectedBufferOptions for this client 1125 | * @param bufferOpts 1126 | */ 1127 | public void setBufferOpts(DisconnectedBufferOptions bufferOpts) { 1128 | this.bufferOpts = bufferOpts; 1129 | myClient.setBufferOpts(bufferOpts); 1130 | } 1131 | 1132 | public int getBufferedMessageCount(){ 1133 | return myClient.getBufferedMessageCount(); 1134 | } 1135 | 1136 | public MqttMessage getBufferedMessage(int bufferIndex){ 1137 | return myClient.getBufferedMessage(bufferIndex); 1138 | } 1139 | 1140 | public void deleteBufferedMessage(int bufferIndex){ 1141 | myClient.deleteBufferedMessage(bufferIndex); 1142 | } 1143 | } 1144 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttDeliveryTokenAndroid.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | import org.eclipse.paho.client.mqttv3.IMqttActionListener; 16 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 17 | import org.eclipse.paho.client.mqttv3.MqttException; 18 | import org.eclipse.paho.client.mqttv3.MqttMessage; 19 | 20 | /** 21 | *

22 | * Implementation of the IMqttDeliveryToken interface for use from within the 23 | * MqttAndroidClient implementation 24 | */ 25 | class MqttDeliveryTokenAndroid extends MqttTokenAndroid 26 | implements IMqttDeliveryToken { 27 | 28 | // The message which is being tracked by this token 29 | private MqttMessage message; 30 | 31 | MqttDeliveryTokenAndroid(MqttAndroidClient client, 32 | Object userContext, IMqttActionListener listener, MqttMessage message) { 33 | super(client, userContext, listener); 34 | this.message = message; 35 | } 36 | 37 | /** 38 | * @see org.eclipse.paho.client.mqttv3.IMqttDeliveryToken#getMessage() 39 | */ 40 | @Override 41 | public MqttMessage getMessage() throws MqttException { 42 | return message; 43 | } 44 | 45 | void setMessage(MqttMessage message) { 46 | this.message = message; 47 | } 48 | 49 | void notifyDelivery(MqttMessage delivered) { 50 | message = delivered; 51 | super.notifyComplete(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttService.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2016 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | * 13 | * Contributors: 14 | * James Sutton - isOnline Null Pointer (bug 473775) 15 | */ 16 | package com.itfitness.mqttlibrary; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.app.Service; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.IntentFilter; 24 | import android.net.ConnectivityManager; 25 | import android.net.NetworkInfo; 26 | import android.os.Build; 27 | import android.os.Bundle; 28 | import android.os.IBinder; 29 | import android.os.PowerManager; 30 | import android.os.PowerManager.WakeLock; 31 | 32 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 33 | 34 | import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; 35 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 36 | import org.eclipse.paho.client.mqttv3.IMqttMessageListener; 37 | import org.eclipse.paho.client.mqttv3.MqttClientPersistence; 38 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 39 | import org.eclipse.paho.client.mqttv3.MqttException; 40 | import org.eclipse.paho.client.mqttv3.MqttMessage; 41 | import org.eclipse.paho.client.mqttv3.MqttPersistenceException; 42 | import org.eclipse.paho.client.mqttv3.MqttSecurityException; 43 | 44 | import java.util.Map; 45 | import java.util.concurrent.ConcurrentHashMap; 46 | 47 | /** 48 | *

49 | * The android service which interfaces with an MQTT client implementation 50 | *

51 | *

52 | * The main API of MqttService is intended to pretty much mirror the 53 | * IMqttAsyncClient with appropriate adjustments for the Android environment.
54 | * These adjustments usually consist of adding two parameters to each method :- 55 | *

56 | *
    57 | *
  • invocationContext - a string passed from the application to identify the 58 | * context of the operation (mainly included for support of the javascript API 59 | * implementation)
  • 60 | *
  • activityToken - a string passed from the Activity to relate back to a 61 | * callback method or other context-specific data
  • 62 | *
63 | *

64 | * To support multiple client connections, the bulk of the MQTT work is 65 | * delegated to MqttConnection objects. These are identified by "client 66 | * handle" strings, which is how the Activity, and the higher-level APIs refer 67 | * to them. 68 | *

69 | *

70 | * Activities using this service are expected to start it and bind to it using 71 | * the BIND_AUTO_CREATE flag. The life cycle of this service is based on this 72 | * approach. 73 | *

74 | *

75 | * Operations are highly asynchronous - in most cases results are returned to 76 | * the Activity by broadcasting one (or occasionally more) appropriate Intents, 77 | * which the Activity is expected to register a listener for.
78 | * The Intents have an Action of 79 | * {@link MqttServiceConstants#CALLBACK_TO_ACTIVITY 80 | * MqttServiceConstants.CALLBACK_TO_ACTIVITY} which allows the Activity to 81 | * register a listener with an appropriate IntentFilter.
82 | * Further data is provided by "Extra Data" in the Intent, as follows :- 83 | *

84 | * 85 | * 86 | * 87 | * 88 | * 89 | * 90 | * 91 | * 92 | * 95 | * 96 | * 98 | * 99 | * 100 | * 101 | * 103 | * 104 | * 106 | * 107 | * 108 | * 109 | * 112 | * 113 | * 114 | * 115 | * 116 | * 117 | * 120 | * 121 | * 123 | * 124 | * 125 | * 126 | * 128 | * 129 | * 169 | * 170 | * 171 | * 172 | * 176 | * 178 | * 179 | * 180 | * 181 | * 185 | * 187 | * 188 | * 189 | * 190 | * 193 | * 194 | * 195 | * 196 | * 197 | * 198 | * 201 | * 202 | * 205 | * 206 | * 207 | * 208 | * 212 | * 213 | * 214 | * 215 | * 216 | * 219 | * 220 | * 222 | * 223 | * 224 | *
NameData TypeValueOperations used for
93 | * {@link MqttServiceConstants#CALLBACK_CLIENT_HANDLE 94 | * MqttServiceConstants.CALLBACK_CLIENT_HANDLE}StringThe clientHandle identifying the client which 97 | * initiated this operationAll operations
{@link MqttServiceConstants#CALLBACK_STATUS 102 | * MqttServiceConstants.CALLBACK_STATUS}SerializableAn {@link Status} value indicating success or 105 | * otherwise of the operationAll operations
110 | * {@link MqttServiceConstants#CALLBACK_ACTIVITY_TOKEN 111 | * MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN}Stringthe activityToken passed into the operationAll operations
118 | * {@link MqttServiceConstants#CALLBACK_INVOCATION_CONTEXT 119 | * MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT}Stringthe invocationContext passed into the operation 122 | * All operations
{@link MqttServiceConstants#CALLBACK_ACTION 127 | * MqttServiceConstants.CALLBACK_ACTION}Stringone of 130 | * 131 | * 132 | * 134 | * 135 | * 136 | * 139 | * 140 | * 141 | * 143 | * 144 | * 145 | * 147 | * 148 | * 149 | * 151 | * 152 | * 153 | * 156 | * 157 | * 158 | * 161 | * 162 | * 163 | * 166 | * 167 | *
{@link MqttServiceConstants#SEND_ACTION 133 | * MqttServiceConstants.SEND_ACTION}
137 | * {@link MqttServiceConstants#UNSUBSCRIBE_ACTION 138 | * MqttServiceConstants.UNSUBSCRIBE_ACTION}
{@link MqttServiceConstants#SUBSCRIBE_ACTION 142 | * MqttServiceConstants.SUBSCRIBE_ACTION}
{@link MqttServiceConstants#DISCONNECT_ACTION 146 | * MqttServiceConstants.DISCONNECT_ACTION}
{@link MqttServiceConstants#CONNECT_ACTION 150 | * MqttServiceConstants.CONNECT_ACTION}
154 | * {@link MqttServiceConstants#MESSAGE_ARRIVED_ACTION 155 | * MqttServiceConstants.MESSAGE_ARRIVED_ACTION}
159 | * {@link MqttServiceConstants#MESSAGE_DELIVERED_ACTION 160 | * MqttServiceConstants.MESSAGE_DELIVERED_ACTION}
164 | * {@link MqttServiceConstants#ON_CONNECTION_LOST_ACTION 165 | * MqttServiceConstants.ON_CONNECTION_LOST_ACTION}
168 | *
All operations
173 | * {@link MqttServiceConstants#CALLBACK_ERROR_MESSAGE 174 | * MqttServiceConstants.CALLBACK_ERROR_MESSAGE} 175 | * StringA suitable error message (taken from the 177 | * relevant exception where possible)All failing operations
182 | * {@link MqttServiceConstants#CALLBACK_ERROR_NUMBER 183 | * MqttServiceConstants.CALLBACK_ERROR_NUMBER} 184 | * intA suitable error code (taken from the relevant 186 | * exception where possible)All failing operations
191 | * {@link MqttServiceConstants#CALLBACK_EXCEPTION_STACK 192 | * MqttServiceConstants.CALLBACK_EXCEPTION_STACK}StringThe stacktrace of the failing callThe Connection Lost event
199 | * {@link MqttServiceConstants#CALLBACK_MESSAGE_ID 200 | * MqttServiceConstants.CALLBACK_MESSAGE_ID}StringThe identifier for the message in the message 203 | * store, used by the Activity to acknowledge the arrival of the message, so 204 | * that the service may remove it from the storeThe Message Arrived event
209 | * {@link MqttServiceConstants#CALLBACK_DESTINATION_NAME 210 | * MqttServiceConstants.CALLBACK_DESTINATION_NAME} 211 | * StringThe topic on which the message was receivedThe Message Arrived event
217 | * {@link MqttServiceConstants#CALLBACK_MESSAGE_PARCEL 218 | * MqttServiceConstants.CALLBACK_MESSAGE_PARCEL}ParcelableThe new message encapsulated in Android 221 | * Parcelable format as a {@link ParcelableMqttMessage}The Message Arrived event
225 | */ 226 | @SuppressLint("Registered") 227 | public class MqttService extends Service implements MqttTraceHandler { 228 | 229 | // Identifier for Intents, log messages, etc.. 230 | static final String TAG = "MqttService"; 231 | 232 | // callback id for making trace callbacks to the Activity 233 | // needs to be set by the activity as appropriate 234 | private String traceCallbackId; 235 | // state of tracing 236 | private boolean traceEnabled = false; 237 | 238 | // somewhere to persist received messages until we're sure 239 | // that they've reached the application 240 | MessageStore messageStore; 241 | 242 | // An intent receiver to deal with changes in network connectivity 243 | private NetworkConnectionIntentReceiver networkConnectionMonitor; 244 | 245 | //a receiver to recognise when the user changes the "background data" preference 246 | // and a flag to track that preference 247 | // Only really relevant below android version ICE_CREAM_SANDWICH - see 248 | // android docs 249 | private BackgroundDataPreferenceReceiver backgroundDataPreferenceMonitor; 250 | private volatile boolean backgroundDataEnabled = true; 251 | 252 | // a way to pass ourself back to the activity 253 | private MqttServiceBinder mqttServiceBinder; 254 | 255 | // mapping from client handle strings to actual client connections. 256 | private Map connections = new ConcurrentHashMap<>(); 257 | 258 | public MqttService() { 259 | super(); 260 | } 261 | 262 | /** 263 | * pass data back to the Activity, by building a suitable Intent object and 264 | * broadcasting it 265 | * 266 | * @param clientHandle 267 | * source of the data 268 | * @param status 269 | * OK or Error 270 | * @param dataBundle 271 | * the data to be passed 272 | */ 273 | void callbackToActivity(String clientHandle, Status status, 274 | Bundle dataBundle) { 275 | // Don't call traceDebug, as it will try to callbackToActivity leading 276 | // to recursion. 277 | Intent callbackIntent = new Intent( 278 | MqttServiceConstants.CALLBACK_TO_ACTIVITY); 279 | if (clientHandle != null) { 280 | callbackIntent.putExtra( 281 | MqttServiceConstants.CALLBACK_CLIENT_HANDLE, clientHandle); 282 | } 283 | callbackIntent.putExtra(MqttServiceConstants.CALLBACK_STATUS, status); 284 | if (dataBundle != null) { 285 | callbackIntent.putExtras(dataBundle); 286 | } 287 | LocalBroadcastManager.getInstance(this).sendBroadcast(callbackIntent); 288 | } 289 | 290 | // The major API implementation follows :- 291 | 292 | /** 293 | * Get an MqttConnection object to represent a connection to a server 294 | * 295 | * @param serverURI specifies the protocol, host name and port to be used to connect to an MQTT server 296 | * @param clientId specifies the name by which this connection should be identified to the server 297 | * @param contextId specifies the app conext info to make a difference between apps 298 | * @param persistence specifies the persistence layer to be used with this client 299 | * @return a string to be used by the Activity as a "handle" for this 300 | * MqttConnection 301 | */ 302 | public String getClient(String serverURI, String clientId, String contextId, MqttClientPersistence persistence) { 303 | String clientHandle = serverURI + ":" + clientId+":"+contextId; 304 | if (!connections.containsKey(clientHandle)) { 305 | MqttConnection client = new MqttConnection(this, serverURI, 306 | clientId, persistence, clientHandle); 307 | connections.put(clientHandle, client); 308 | } 309 | return clientHandle; 310 | } 311 | 312 | /** 313 | * Connect to the MQTT server specified by a particular client 314 | * 315 | * @param clientHandle 316 | * identifies the MqttConnection to use 317 | * @param connectOptions 318 | * the MQTT connection options to be used 319 | * @param invocationContext 320 | * arbitrary data to be passed back to the application 321 | * @param activityToken 322 | * arbitrary identifier to be passed back to the Activity 323 | * @throws MqttSecurityException thrown if there is a security exception 324 | * @throws MqttException thrown for all other MqttExceptions 325 | */ 326 | public void connect(String clientHandle, MqttConnectOptions connectOptions, 327 | String invocationContext, String activityToken) 328 | throws MqttSecurityException, MqttException { 329 | MqttConnection client = getConnection(clientHandle); 330 | client.connect(connectOptions, null, activityToken); 331 | 332 | } 333 | 334 | /** 335 | * Request all clients to reconnect if appropriate 336 | */ 337 | void reconnect() { 338 | traceDebug(TAG, "Reconnect to server, client size=" + connections.size()); 339 | for (MqttConnection client : connections.values()) { 340 | traceDebug("Reconnect Client:", 341 | client.getClientId() + '/' + client.getServerURI()); 342 | if(this.isOnline()){ 343 | client.reconnect(); 344 | } 345 | } 346 | } 347 | 348 | /** 349 | * Close connection from a particular client 350 | * 351 | * @param clientHandle 352 | * identifies the MqttConnection to use 353 | */ 354 | public void close(String clientHandle) { 355 | MqttConnection client = getConnection(clientHandle); 356 | client.close(); 357 | } 358 | 359 | /** 360 | * Disconnect from the server 361 | * 362 | * @param clientHandle 363 | * identifies the MqttConnection to use 364 | * @param invocationContext 365 | * arbitrary data to be passed back to the application 366 | * @param activityToken 367 | * arbitrary identifier to be passed back to the Activity 368 | */ 369 | public void disconnect(String clientHandle, String invocationContext, 370 | String activityToken) { 371 | MqttConnection client = getConnection(clientHandle); 372 | client.disconnect(invocationContext, activityToken); 373 | connections.remove(clientHandle); 374 | 375 | 376 | // the activity has finished using us, so we can stop the service 377 | // the activities are bound with BIND_AUTO_CREATE, so the service will 378 | // remain around until the last activity disconnects 379 | stopSelf(); 380 | } 381 | 382 | /** 383 | * Disconnect from the server 384 | * 385 | * @param clientHandle 386 | * identifies the MqttConnection to use 387 | * @param quiesceTimeout 388 | * in milliseconds 389 | * @param invocationContext 390 | * arbitrary data to be passed back to the application 391 | * @param activityToken 392 | * arbitrary identifier to be passed back to the Activity 393 | */ 394 | public void disconnect(String clientHandle, long quiesceTimeout, 395 | String invocationContext, String activityToken) { 396 | MqttConnection client = getConnection(clientHandle); 397 | client.disconnect(quiesceTimeout, invocationContext, activityToken); 398 | connections.remove(clientHandle); 399 | 400 | // the activity has finished using us, so we can stop the service 401 | // the activities are bound with BIND_AUTO_CREATE, so the service will 402 | // remain around until the last activity disconnects 403 | stopSelf(); 404 | } 405 | 406 | /** 407 | * Get the status of a specific client 408 | * 409 | * @param clientHandle 410 | * identifies the MqttConnection to use 411 | * @return true if the specified client is connected to an MQTT server 412 | */ 413 | public boolean isConnected(String clientHandle) { 414 | MqttConnection client = getConnection(clientHandle); 415 | return client.isConnected(); 416 | } 417 | 418 | /** 419 | * Publish a message to a topic 420 | * 421 | * @param clientHandle 422 | * identifies the MqttConnection to use 423 | * @param topic 424 | * the topic to which to publish 425 | * @param payload 426 | * the content of the message to publish 427 | * @param qos 428 | * the quality of service requested 429 | * @param retained 430 | * whether the MQTT server should retain this message 431 | * @param invocationContext 432 | * arbitrary data to be passed back to the application 433 | * @param activityToken 434 | * arbitrary identifier to be passed back to the Activity 435 | * @throws MqttPersistenceException when a problem occurs storing the message 436 | * @throws MqttException if there was an error publishing the message 437 | * @return token for tracking the operation 438 | */ 439 | public IMqttDeliveryToken publish(String clientHandle, String topic, 440 | byte[] payload, int qos, boolean retained, 441 | String invocationContext, String activityToken) 442 | throws MqttPersistenceException, MqttException { 443 | MqttConnection client = getConnection(clientHandle); 444 | return client.publish(topic, payload, qos, retained, invocationContext, 445 | activityToken); 446 | } 447 | 448 | /** 449 | * Publish a message to a topic 450 | * 451 | * @param clientHandle 452 | * identifies the MqttConnection to use 453 | * @param topic 454 | * the topic to which to publish 455 | * @param message 456 | * the message to publish 457 | * @param invocationContext 458 | * arbitrary data to be passed back to the application 459 | * @param activityToken 460 | * arbitrary identifier to be passed back to the Activity 461 | * @throws MqttPersistenceException when a problem occurs storing the message 462 | * @throws MqttException if there was an error publishing the message 463 | * @return token for tracking the operation 464 | */ 465 | public IMqttDeliveryToken publish(String clientHandle, String topic, 466 | MqttMessage message, String invocationContext, String activityToken) 467 | throws MqttPersistenceException, MqttException { 468 | MqttConnection client = getConnection(clientHandle); 469 | return client.publish(topic, message, invocationContext, activityToken); 470 | } 471 | 472 | /** 473 | * Subscribe to a topic 474 | * 475 | * @param clientHandle 476 | * identifies the MqttConnection to use 477 | * @param topic 478 | * a possibly wildcarded topic name 479 | * @param qos 480 | * requested quality of service for the topic 481 | * @param invocationContext 482 | * arbitrary data to be passed back to the application 483 | * @param activityToken 484 | * arbitrary identifier to be passed back to the Activity 485 | */ 486 | public void subscribe(String clientHandle, String topic, int qos, 487 | String invocationContext, String activityToken) { 488 | MqttConnection client = getConnection(clientHandle); 489 | client.subscribe(topic, qos, invocationContext, activityToken); 490 | } 491 | 492 | /** 493 | * Subscribe to one or more topics 494 | * 495 | * @param clientHandle 496 | * identifies the MqttConnection to use 497 | * @param topic 498 | * a list of possibly wildcarded topic names 499 | * @param qos 500 | * requested quality of service for each topic 501 | * @param invocationContext 502 | * arbitrary data to be passed back to the application 503 | * @param activityToken 504 | * arbitrary identifier to be passed back to the Activity 505 | */ 506 | public void subscribe(String clientHandle, String[] topic, int[] qos, 507 | String invocationContext, String activityToken) { 508 | MqttConnection client = getConnection(clientHandle); 509 | client.subscribe(topic, qos, invocationContext, activityToken); 510 | } 511 | 512 | /** 513 | * Subscribe using topic filters 514 | * 515 | * @param clientHandle 516 | * identifies the MqttConnection to use 517 | * @param topicFilters 518 | * a list of possibly wildcarded topicfilters 519 | * @param qos 520 | * requested quality of service for each topic 521 | * @param invocationContext 522 | * arbitrary data to be passed back to the application 523 | * @param activityToken 524 | * arbitrary identifier to be passed back to the Activity 525 | * @param messageListeners a callback to handle incoming messages 526 | */ 527 | public void subscribe(String clientHandle, String[] topicFilters, int[] qos, String invocationContext, String activityToken, IMqttMessageListener[] messageListeners){ 528 | MqttConnection client = getConnection(clientHandle); 529 | client.subscribe(topicFilters, qos, invocationContext, activityToken, messageListeners); 530 | } 531 | 532 | /** 533 | * Unsubscribe from a topic 534 | * 535 | * @param clientHandle 536 | * identifies the MqttConnection 537 | * @param topic 538 | * a possibly wildcarded topic name 539 | * @param invocationContext 540 | * arbitrary data to be passed back to the application 541 | * @param activityToken 542 | * arbitrary identifier to be passed back to the Activity 543 | */ 544 | public void unsubscribe(String clientHandle, final String topic, 545 | String invocationContext, String activityToken) { 546 | MqttConnection client = getConnection(clientHandle); 547 | client.unsubscribe(topic, invocationContext, activityToken); 548 | } 549 | 550 | /** 551 | * Unsubscribe from one or more topics 552 | * 553 | * @param clientHandle 554 | * identifies the MqttConnection 555 | * @param topic 556 | * a list of possibly wildcarded topic names 557 | * @param invocationContext 558 | * arbitrary data to be passed back to the application 559 | * @param activityToken 560 | * arbitrary identifier to be passed back to the Activity 561 | */ 562 | public void unsubscribe(String clientHandle, final String[] topic, 563 | String invocationContext, String activityToken) { 564 | MqttConnection client = getConnection(clientHandle); 565 | client.unsubscribe(topic, invocationContext, activityToken); 566 | } 567 | 568 | /** 569 | * Get tokens for all outstanding deliveries for a client 570 | * 571 | * @param clientHandle 572 | * identifies the MqttConnection 573 | * @return an array (possibly empty) of tokens 574 | */ 575 | public IMqttDeliveryToken[] getPendingDeliveryTokens(String clientHandle) { 576 | MqttConnection client = getConnection(clientHandle); 577 | return client.getPendingDeliveryTokens(); 578 | } 579 | 580 | /** 581 | * Get the MqttConnection identified by this client handle 582 | * 583 | * @param clientHandle identifies the MqttConnection 584 | * @return the MqttConnection identified by this handle 585 | */ 586 | private MqttConnection getConnection(String clientHandle) { 587 | MqttConnection client = connections.get(clientHandle); 588 | if (client == null) { 589 | throw new IllegalArgumentException("Invalid ClientHandle"); 590 | } 591 | return client; 592 | } 593 | 594 | /** 595 | * Called by the Activity when a message has been passed back to the 596 | * application 597 | * 598 | * @param clientHandle identifier for the client which received the message 599 | * @param id identifier for the MQTT message 600 | * @return {@link Status} 601 | */ 602 | public Status acknowledgeMessageArrival(String clientHandle, String id) { 603 | if (messageStore.discardArrived(clientHandle, id)) { 604 | return Status.OK; 605 | } 606 | else { 607 | return Status.ERROR; 608 | } 609 | } 610 | 611 | // Extend Service 612 | 613 | /** 614 | * @see Service#onCreate() 615 | */ 616 | @Override 617 | public void onCreate() { 618 | super.onCreate(); 619 | 620 | // create a binder that will let the Activity UI send 621 | // commands to the Service 622 | mqttServiceBinder = new MqttServiceBinder(this); 623 | 624 | // create somewhere to buffer received messages until 625 | // we know that they have been passed to the application 626 | messageStore = new DatabaseMessageStore(this, this); 627 | } 628 | 629 | 630 | 631 | /** 632 | * @see Service#onDestroy() 633 | */ 634 | @Override 635 | public void onDestroy() { 636 | // disconnect immediately 637 | for (MqttConnection client : connections.values()) { 638 | client.disconnect(null, null); 639 | } 640 | 641 | // clear down 642 | if (mqttServiceBinder != null) { 643 | mqttServiceBinder = null; 644 | } 645 | 646 | unregisterBroadcastReceivers(); 647 | 648 | if (this.messageStore !=null ) 649 | this.messageStore.close(); 650 | 651 | super.onDestroy(); 652 | } 653 | 654 | /** 655 | * @see Service#onBind(Intent) 656 | */ 657 | @Override 658 | public IBinder onBind(Intent intent) { 659 | // What we pass back to the Activity on binding - 660 | // a reference to ourself, and the activityToken 661 | // we were given when started 662 | String activityToken = intent 663 | .getStringExtra(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN); 664 | mqttServiceBinder.setActivityToken(activityToken); 665 | return mqttServiceBinder; 666 | } 667 | 668 | /** 669 | * @see Service#onStartCommand(Intent,int,int) 670 | */ 671 | @Override 672 | public int onStartCommand(final Intent intent, int flags, final int startId) { 673 | // run till explicitly stopped, restart when 674 | // process restarted 675 | registerBroadcastReceivers(); 676 | 677 | return START_STICKY; 678 | } 679 | 680 | /** 681 | * Identify the callbackId to be passed when making tracing calls back into 682 | * the Activity 683 | * 684 | * @param traceCallbackId identifier to the callback into the Activity 685 | */ 686 | public void setTraceCallbackId(String traceCallbackId) { 687 | this.traceCallbackId = traceCallbackId; 688 | } 689 | 690 | /** 691 | * Turn tracing on and off 692 | * 693 | * @param traceEnabled set true to turn on tracing, false to turn off tracing 694 | */ 695 | public void setTraceEnabled(boolean traceEnabled) { 696 | this.traceEnabled = traceEnabled; 697 | } 698 | 699 | /** 700 | * Check whether trace is on or off. 701 | * 702 | * @return the state of trace 703 | */ 704 | public boolean isTraceEnabled(){ 705 | return this.traceEnabled; 706 | } 707 | 708 | /** 709 | * Trace debugging information 710 | * 711 | * @param tag 712 | * identifier for the source of the trace 713 | * @param message 714 | * the text to be traced 715 | */ 716 | @Override 717 | public void traceDebug(String tag, String message) { 718 | traceCallback(MqttServiceConstants.TRACE_DEBUG, tag, message); 719 | } 720 | 721 | /** 722 | * Trace error information 723 | * 724 | * @param tag 725 | * identifier for the source of the trace 726 | * @param message 727 | * the text to be traced 728 | */ 729 | @Override 730 | public void traceError(String tag, String message) { 731 | traceCallback(MqttServiceConstants.TRACE_ERROR, tag, message); 732 | } 733 | 734 | private void traceCallback(String severity, String tag, String message) { 735 | if ((traceCallbackId != null) && (traceEnabled)) { 736 | Bundle dataBundle = new Bundle(); 737 | dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION); 738 | dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, severity); 739 | dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag); 740 | //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId); 741 | dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message); 742 | callbackToActivity(traceCallbackId, Status.ERROR, dataBundle); 743 | } 744 | } 745 | 746 | /** 747 | * trace exceptions 748 | * 749 | * @param tag 750 | * identifier for the source of the trace 751 | * @param message 752 | * the text to be traced 753 | * @param e 754 | * the exception 755 | */ 756 | @Override 757 | public void traceException(String tag, String message, Exception e) { 758 | if (traceCallbackId != null) { 759 | Bundle dataBundle = new Bundle(); 760 | dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION); 761 | dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, MqttServiceConstants.TRACE_EXCEPTION); 762 | dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message); 763 | dataBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e); //TODO: Check 764 | dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag); 765 | //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId); 766 | callbackToActivity(traceCallbackId, Status.ERROR, dataBundle); 767 | } 768 | } 769 | 770 | @SuppressWarnings("deprecation") 771 | private void registerBroadcastReceivers() { 772 | if (networkConnectionMonitor == null) { 773 | networkConnectionMonitor = new NetworkConnectionIntentReceiver(); 774 | registerReceiver(networkConnectionMonitor, new IntentFilter( 775 | ConnectivityManager.CONNECTIVITY_ACTION)); 776 | } 777 | 778 | if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) { 779 | // Support the old system for background data preferences 780 | ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 781 | backgroundDataEnabled = cm.getBackgroundDataSetting(); 782 | if (backgroundDataPreferenceMonitor == null) { 783 | backgroundDataPreferenceMonitor = new BackgroundDataPreferenceReceiver(); 784 | registerReceiver( 785 | backgroundDataPreferenceMonitor, 786 | new IntentFilter( 787 | ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED)); 788 | } 789 | } 790 | } 791 | 792 | private void unregisterBroadcastReceivers(){ 793 | if(networkConnectionMonitor != null){ 794 | unregisterReceiver(networkConnectionMonitor); 795 | networkConnectionMonitor = null; 796 | } 797 | 798 | if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) { 799 | if(backgroundDataPreferenceMonitor != null){ 800 | unregisterReceiver(backgroundDataPreferenceMonitor); 801 | } 802 | } 803 | } 804 | 805 | /* 806 | * Called in response to a change in network connection - after losing a 807 | * connection to the server, this allows us to wait until we have a usable 808 | * data connection again 809 | */ 810 | private class NetworkConnectionIntentReceiver extends BroadcastReceiver { 811 | 812 | @Override 813 | @SuppressLint("Wakelock") 814 | public void onReceive(Context context, Intent intent) { 815 | traceDebug(TAG, "Internal network status receive."); 816 | // we protect against the phone switching off 817 | // by requesting a wake lock - we request the minimum possible wake 818 | // lock - just enough to keep the CPU running until we've finished 819 | PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); 820 | WakeLock wl = pm 821 | .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MQTT"); 822 | wl.acquire(); 823 | traceDebug(TAG,"Reconnect for Network recovery."); 824 | if (isOnline()) { 825 | traceDebug(TAG,"Online,reconnect."); 826 | // we have an internet connection - have another try at 827 | // connecting 828 | reconnect(); 829 | } else { 830 | notifyClientsOffline(); 831 | } 832 | 833 | wl.release(); 834 | } 835 | } 836 | 837 | /** 838 | * @return whether the android service can be regarded as online 839 | */ 840 | public boolean isOnline() { 841 | ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 842 | NetworkInfo networkInfo = cm.getActiveNetworkInfo(); 843 | //noinspection RedundantIfStatement 844 | if (networkInfo != null 845 | && networkInfo.isAvailable() 846 | && networkInfo.isConnected() 847 | && backgroundDataEnabled) { 848 | return true; 849 | } 850 | 851 | return false; 852 | } 853 | 854 | /** 855 | * Notify clients we're offline 856 | */ 857 | private void notifyClientsOffline() { 858 | for (MqttConnection connection : connections.values()) { 859 | connection.offline(); 860 | } 861 | } 862 | 863 | /** 864 | * Detect changes of the Allow Background Data setting - only used below 865 | * ICE_CREAM_SANDWICH 866 | */ 867 | private class BackgroundDataPreferenceReceiver extends BroadcastReceiver { 868 | 869 | @SuppressWarnings("deprecation") 870 | @Override 871 | public void onReceive(Context context, Intent intent) { 872 | ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 873 | traceDebug(TAG,"Reconnect since BroadcastReceiver."); 874 | if (cm.getBackgroundDataSetting()) { 875 | if (!backgroundDataEnabled) { 876 | backgroundDataEnabled = true; 877 | // we have the Internet connection - have another try at 878 | // connecting 879 | reconnect(); 880 | } 881 | } else { 882 | backgroundDataEnabled = false; 883 | notifyClientsOffline(); 884 | } 885 | } 886 | } 887 | 888 | /** 889 | * Sets the DisconnectedBufferOptions for this client 890 | * @param clientHandle identifier for the client 891 | * @param bufferOpts the DisconnectedBufferOptions for this client 892 | */ 893 | public void setBufferOpts(String clientHandle, DisconnectedBufferOptions bufferOpts) { 894 | MqttConnection client = getConnection(clientHandle); 895 | client.setBufferOpts(bufferOpts); 896 | } 897 | 898 | public int getBufferedMessageCount(String clientHandle){ 899 | MqttConnection client = getConnection(clientHandle); 900 | return client.getBufferedMessageCount(); 901 | } 902 | 903 | public MqttMessage getBufferedMessage(String clientHandle, int bufferIndex){ 904 | MqttConnection client = getConnection(clientHandle); 905 | return client.getBufferedMessage(bufferIndex); 906 | } 907 | 908 | public void deleteBufferedMessage(String clientHandle, int bufferIndex){ 909 | MqttConnection client = getConnection(clientHandle); 910 | client.deleteBufferedMessage(bufferIndex); 911 | } 912 | 913 | } 914 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttServiceBinder.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | import android.os.Binder; 16 | 17 | /** 18 | * What the Service passes to the Activity on binding:- 19 | *
    20 | *
  • a reference to the Service 21 | *
  • the activityToken provided when the Service was started 22 | *
23 | * 24 | */ 25 | class MqttServiceBinder extends Binder { 26 | 27 | private MqttService mqttService; 28 | private String activityToken; 29 | 30 | MqttServiceBinder(MqttService mqttService) { 31 | this.mqttService = mqttService; 32 | } 33 | 34 | /** 35 | * @return a reference to the Service 36 | */ 37 | public MqttService getService() { 38 | return mqttService; 39 | } 40 | 41 | void setActivityToken(String activityToken) { 42 | this.activityToken = activityToken; 43 | } 44 | 45 | /** 46 | * @return the activityToken provided when the Service was started 47 | */ 48 | public String getActivityToken() { 49 | return activityToken; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttServiceConstants.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2016 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | /** 16 | * Various strings used to identify operations or data in the Android MQTT 17 | * service, mainly used in Intents passed between Activities and the Service. 18 | */ 19 | interface MqttServiceConstants { 20 | 21 | /* 22 | * Version information 23 | */ 24 | 25 | String VERSION = "v0"; 26 | 27 | /* 28 | * Attributes of messages

Used for the column names in the database 29 | */ 30 | String DUPLICATE = "duplicate"; 31 | String RETAINED = "retained"; 32 | String QOS = "qos"; 33 | String PAYLOAD = "payload"; 34 | String DESTINATION_NAME = "destinationName"; 35 | String CLIENT_HANDLE = "clientHandle"; 36 | String MESSAGE_ID = "messageId"; 37 | 38 | /* Tags for actions passed between the Activity and the Service */ 39 | String SEND_ACTION = "send"; 40 | String UNSUBSCRIBE_ACTION = "unsubscribe"; 41 | String SUBSCRIBE_ACTION = "subscribe"; 42 | String DISCONNECT_ACTION = "disconnect"; 43 | String CONNECT_ACTION = "connect"; 44 | String CONNECT_EXTENDED_ACTION = "connectExtended"; 45 | String MESSAGE_ARRIVED_ACTION = "messageArrived"; 46 | String MESSAGE_DELIVERED_ACTION = "messageDelivered"; 47 | String ON_CONNECTION_LOST_ACTION = "onConnectionLost"; 48 | String TRACE_ACTION = "trace"; 49 | 50 | /* Identifies an Intent which calls back to the Activity */ 51 | String CALLBACK_TO_ACTIVITY = MqttService.TAG 52 | + ".callbackToActivity"+"."+VERSION; 53 | 54 | /* Identifiers for extra data on Intents broadcast to the Activity */ 55 | String CALLBACK_ACTION = MqttService.TAG + ".callbackAction"; 56 | String CALLBACK_STATUS = MqttService.TAG + ".callbackStatus"; 57 | String CALLBACK_CLIENT_HANDLE = MqttService.TAG + "." 58 | + CLIENT_HANDLE; 59 | String CALLBACK_ERROR_MESSAGE = MqttService.TAG 60 | + ".errorMessage"; 61 | String CALLBACK_EXCEPTION_STACK = MqttService.TAG 62 | + ".exceptionStack"; 63 | String CALLBACK_INVOCATION_CONTEXT = MqttService.TAG + "." 64 | + "invocationContext"; 65 | String CALLBACK_ACTIVITY_TOKEN = MqttService.TAG + "." 66 | + "activityToken"; 67 | String CALLBACK_DESTINATION_NAME = MqttService.TAG + '.' 68 | + DESTINATION_NAME; 69 | String CALLBACK_MESSAGE_ID = MqttService.TAG + '.' 70 | + MESSAGE_ID; 71 | String CALLBACK_RECONNECT = MqttService.TAG + ".reconnect"; 72 | String CALLBACK_SERVER_URI = MqttService.TAG + ".serverURI"; 73 | String CALLBACK_MESSAGE_PARCEL = MqttService.TAG + ".PARCEL"; 74 | String CALLBACK_TRACE_SEVERITY = MqttService.TAG 75 | + ".traceSeverity"; 76 | String CALLBACK_TRACE_TAG = MqttService.TAG + ".traceTag"; 77 | String CALLBACK_TRACE_ID = MqttService.TAG + ".traceId"; 78 | String CALLBACK_ERROR_NUMBER = MqttService.TAG 79 | + ".ERROR_NUMBER"; 80 | 81 | String CALLBACK_EXCEPTION = MqttService.TAG + ".exception"; 82 | 83 | //Intent prefix for Ping sender. 84 | String PING_SENDER = MqttService.TAG + ".pingSender."; 85 | 86 | //Constant for wakelock 87 | String PING_WAKELOCK = MqttService.TAG + ".client."; 88 | String WAKELOCK_NETWORK_INTENT = MqttService.TAG + ""; 89 | 90 | //Trace severity levels 91 | String TRACE_ERROR = "error"; 92 | String TRACE_DEBUG = "debug"; 93 | String TRACE_EXCEPTION = "exception"; 94 | 95 | 96 | //exception code for non MqttExceptions 97 | int NON_MQTT_EXCEPTION = -1; 98 | 99 | } -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttTokenAndroid.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | import org.eclipse.paho.client.mqttv3.IMqttActionListener; 16 | import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; 17 | import org.eclipse.paho.client.mqttv3.IMqttToken; 18 | import org.eclipse.paho.client.mqttv3.MqttException; 19 | import org.eclipse.paho.client.mqttv3.MqttSecurityException; 20 | import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage; 21 | 22 | /** 23 | *

24 | * Implementation of the IMqttToken interface for use from within the 25 | * MqttAndroidClient implementation 26 | */ 27 | 28 | class MqttTokenAndroid implements IMqttToken { 29 | 30 | private IMqttActionListener listener; 31 | 32 | private volatile boolean isComplete; 33 | 34 | private volatile MqttException lastException; 35 | 36 | private Object waitObject = new Object(); 37 | 38 | private MqttAndroidClient client; 39 | 40 | private Object userContext; 41 | 42 | private String[] topics; 43 | 44 | private IMqttToken delegate; // specifically for getMessageId 45 | 46 | private MqttException pendingException; 47 | 48 | /** 49 | * Standard constructor 50 | * 51 | * @param client used to pass MqttAndroidClient object 52 | * @param userContext used to pass context 53 | * @param listener optional listener that will be notified when the action completes. Use null if not required. 54 | */ 55 | MqttTokenAndroid(MqttAndroidClient client, 56 | Object userContext, IMqttActionListener listener) { 57 | this(client, userContext, listener, null); 58 | } 59 | 60 | /** 61 | * Constructor for use with subscribe operations 62 | * 63 | * @param client used to pass MqttAndroidClient object 64 | * @param userContext used to pass context 65 | * @param listener optional listener that will be notified when the action completes. Use null if not required. 66 | * @param topics topics to subscribe to, which can include wildcards. 67 | */ 68 | MqttTokenAndroid(MqttAndroidClient client, 69 | Object userContext, IMqttActionListener listener, String[] topics) { 70 | this.client = client; 71 | this.userContext = userContext; 72 | this.listener = listener; 73 | this.topics = topics; 74 | } 75 | 76 | /** 77 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion() 78 | */ 79 | @Override 80 | public void waitForCompletion() throws MqttException, MqttSecurityException { 81 | synchronized (waitObject) { 82 | try { 83 | waitObject.wait(); 84 | } 85 | catch (InterruptedException e) { 86 | // do nothing 87 | } 88 | } 89 | if (pendingException != null) { 90 | throw pendingException; 91 | } 92 | } 93 | 94 | /** 95 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion(long) 96 | */ 97 | @Override 98 | public void waitForCompletion(long timeout) throws MqttException, 99 | MqttSecurityException { 100 | synchronized (waitObject) { 101 | try { 102 | waitObject.wait(timeout); 103 | } 104 | catch (InterruptedException e) { 105 | // do nothing 106 | } 107 | if (!isComplete) { 108 | throw new MqttException(MqttException.REASON_CODE_CLIENT_TIMEOUT); 109 | } 110 | if (pendingException != null) { 111 | throw pendingException; 112 | } 113 | } 114 | } 115 | 116 | /** 117 | * notify successful completion of the operation 118 | */ 119 | void notifyComplete() { 120 | synchronized (waitObject) { 121 | isComplete = true; 122 | waitObject.notifyAll(); 123 | if (listener != null) { 124 | listener.onSuccess(this); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * notify unsuccessful completion of the operation 131 | */ 132 | void notifyFailure(Throwable exception) { 133 | synchronized (waitObject) { 134 | isComplete = true; 135 | if (exception instanceof MqttException) { 136 | pendingException = (MqttException) exception; 137 | } 138 | else { 139 | pendingException = new MqttException(exception); 140 | } 141 | waitObject.notifyAll(); 142 | if (exception instanceof MqttException) { 143 | lastException = (MqttException) exception; 144 | } 145 | if (listener != null) { 146 | listener.onFailure(this, exception); 147 | } 148 | } 149 | 150 | } 151 | 152 | /** 153 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#isComplete() 154 | */ 155 | @Override 156 | public boolean isComplete() { 157 | return isComplete; 158 | } 159 | 160 | void setComplete(boolean complete) { 161 | isComplete = complete; 162 | } 163 | 164 | /** 165 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#getException() 166 | */ 167 | @Override 168 | public MqttException getException() { 169 | return lastException; 170 | } 171 | 172 | void setException(MqttException exception) { 173 | lastException = exception; 174 | } 175 | 176 | /** 177 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#getClient() 178 | */ 179 | @Override 180 | public IMqttAsyncClient getClient() { 181 | return client; 182 | } 183 | 184 | /** 185 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#setActionCallback(IMqttActionListener) 186 | */ 187 | @Override 188 | public void setActionCallback(IMqttActionListener listener) { 189 | this.listener = listener; 190 | } 191 | 192 | /** 193 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#getActionCallback() 194 | */ 195 | @Override 196 | public IMqttActionListener getActionCallback() { 197 | return listener; 198 | } 199 | 200 | /** 201 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#getTopics() 202 | */ 203 | @Override 204 | public String[] getTopics() { 205 | return topics; 206 | } 207 | 208 | /** 209 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#setUserContext(Object) 210 | */ 211 | @Override 212 | public void setUserContext(Object userContext) { 213 | this.userContext = userContext; 214 | 215 | } 216 | 217 | /** 218 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#getUserContext() 219 | */ 220 | @Override 221 | public Object getUserContext() { 222 | return userContext; 223 | } 224 | 225 | void setDelegate(IMqttToken delegate) { 226 | this.delegate = delegate; 227 | } 228 | 229 | /** 230 | * @see org.eclipse.paho.client.mqttv3.IMqttToken#getMessageId() 231 | */ 232 | @Override 233 | public int getMessageId() { 234 | return (delegate != null) ? delegate.getMessageId() : 0; 235 | } 236 | 237 | @Override 238 | public MqttWireMessage getResponse() { 239 | return delegate.getResponse(); 240 | } 241 | 242 | @Override 243 | public boolean getSessionPresent() { 244 | return delegate.getSessionPresent(); 245 | } 246 | 247 | @Override 248 | public int[] getGrantedQos() { 249 | return delegate.getGrantedQos(); 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/MqttTraceHandler.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | /** 16 | * Interface for simple trace handling, pass the trace message to trace 17 | * callback. 18 | * 19 | */ 20 | 21 | public interface MqttTraceHandler { 22 | 23 | /** 24 | * Trace debugging information 25 | * 26 | * @param tag 27 | * identifier for the source of the trace 28 | * @param message 29 | * the text to be traced 30 | */ 31 | void traceDebug(String tag, String message); 32 | 33 | /** 34 | * Trace error information 35 | * 36 | * @param tag 37 | * identifier for the source of the trace 38 | * @param message 39 | * the text to be traced 40 | */ 41 | void traceError(String tag, String message); 42 | 43 | /** 44 | * trace exceptions 45 | * 46 | * @param tag 47 | * identifier for the source of the trace 48 | * @param message 49 | * the text to be traced 50 | * @param e 51 | * the exception 52 | */ 53 | void traceException(String tag, String message, 54 | Exception e); 55 | 56 | } -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/ParcelableMqttMessage.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | import android.os.Parcel; 16 | import android.os.Parcelable; 17 | 18 | import org.eclipse.paho.client.mqttv3.MqttMessage; 19 | 20 | /** 21 | *

22 | * A way to flow MqttMessages via Bundles/Intents 23 | *

24 | * 25 | *

26 | * An application will probably use this only when receiving a message from a 27 | * Service in a Bundle - the necessary code will be something like this :- 28 | *

29 | *
 30 |  * 
 31 |  * 	private void messageArrivedAction(Bundle data) {
 32 |  * 		ParcelableMqttMessage message = (ParcelableMqttMessage) data
 33 |  * 			.getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
 34 |  *		Use the normal {@link MqttMessage} methods on the the message object.
 35 |  * 	}
 36 |  * 
 37 |  * 
 38 |  * 
39 | * 40 | *

41 | * It is unlikely that an application will directly use the methods which are 42 | * specific to this class. 43 | *

44 | */ 45 | 46 | public class ParcelableMqttMessage extends MqttMessage implements Parcelable { 47 | 48 | String messageId = null; 49 | 50 | ParcelableMqttMessage(MqttMessage original) { 51 | super(original.getPayload()); 52 | setQos(original.getQos()); 53 | setRetained(original.isRetained()); 54 | setDuplicate(original.isDuplicate()); 55 | } 56 | 57 | ParcelableMqttMessage(Parcel parcel) { 58 | super(parcel.createByteArray()); 59 | setQos(parcel.readInt()); 60 | boolean[] flags = parcel.createBooleanArray(); 61 | setRetained(flags[0]); 62 | setDuplicate(flags[1]); 63 | messageId = parcel.readString(); 64 | } 65 | 66 | /** 67 | * @return the messageId 68 | */ 69 | public String getMessageId() { 70 | return messageId; 71 | } 72 | 73 | /** 74 | * Describes the contents of this object 75 | */ 76 | @Override 77 | public int describeContents() { 78 | return 0; 79 | } 80 | 81 | /** 82 | * Writes the contents of this object to a parcel 83 | * 84 | * @param parcel 85 | * The parcel to write the data to. 86 | * @param flags 87 | * this parameter is ignored 88 | */ 89 | @Override 90 | public void writeToParcel(Parcel parcel, int flags) { 91 | parcel.writeByteArray(getPayload()); 92 | parcel.writeInt(getQos()); 93 | parcel.writeBooleanArray(new boolean[]{isRetained(), isDuplicate()}); 94 | parcel.writeString(messageId); 95 | } 96 | 97 | /** 98 | * A creator which creates the message object from a parcel 99 | */ 100 | public static final Creator CREATOR = new Creator() { 101 | 102 | /** 103 | * Creates a message from the parcel object 104 | */ 105 | @Override 106 | public ParcelableMqttMessage createFromParcel(Parcel parcel) { 107 | return new ParcelableMqttMessage(parcel); 108 | } 109 | 110 | /** 111 | * creates an array of type {@link ParcelableMqttMessage}[] 112 | * 113 | */ 114 | @Override 115 | public ParcelableMqttMessage[] newArray(int size) { 116 | return new ParcelableMqttMessage[size]; 117 | } 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /MQTTLibrary/src/main/java/com/itfitness/mqttlibrary/Status.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 1999, 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | */ 13 | package com.itfitness.mqttlibrary; 14 | 15 | /** 16 | * Enumeration representing the success or failure of an operation 17 | */ 18 | enum Status { 19 | /** 20 | * Indicates that the operation succeeded 21 | */ 22 | OK, 23 | 24 | /** 25 | * Indicates that the operation failed 26 | */ 27 | ERROR, 28 | 29 | /** 30 | * Indicates that the operation's result may be returned asynchronously 31 | */ 32 | NO_RESULT 33 | } 34 | -------------------------------------------------------------------------------- /MQTTLibrary/src/test/java/com/itfitness/mqttlibrary/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.itfitness.mqttlibrary 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | applicationId "com.itfitness.mqttandroid" 11 | minSdk 21 12 | targetSdk 31 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation 'androidx.core:core-ktx:1.7.0' 36 | implementation 'androidx.appcompat:appcompat:1.4.1' 37 | implementation 'com.google.android.material:material:1.5.0' 38 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 39 | implementation project(path: ':MQTTLibrary') 40 | testImplementation 'junit:junit:4.+' 41 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 43 | implementation 'com.blankj:utilcodex:1.31.0' 44 | 45 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/itfitness/mqttandroid/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.itfitness.mqttandroid 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.itfitness.mqttandroid", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/itfitness/mqttandroid/MQTTHelper.kt: -------------------------------------------------------------------------------- 1 | package com.itfitness.mqttandroid 2 | 3 | import android.content.Context 4 | import android.text.TextUtils 5 | import com.blankj.utilcode.util.DeviceUtils 6 | import com.blankj.utilcode.util.LogUtils 7 | import com.itfitness.mqttandroid.data.Qos 8 | import com.itfitness.mqttandroid.data.Topic 9 | import com.itfitness.mqttlibrary.MqttAndroidClient 10 | import org.eclipse.paho.client.mqttv3.* 11 | import java.util.* 12 | 13 | 14 | /** 15 | * 16 | * @Description: java类作用描述 17 | * @Author: 作者名 18 | * @CreateDate: 2022/1/26 16:59 19 | * @UpdateUser: 更新者: 20 | * @UpdateDate: 2022/1/26 16:59 21 | * @UpdateRemark: 更新说明: 22 | */ 23 | class MQTTHelper{ 24 | private val mqttClient: MqttAndroidClient 25 | private val connectOptions: MqttConnectOptions 26 | private var mqttActionListener: IMqttActionListener? = null 27 | constructor(context: Context, serverUrl:String, name:String, pass:String){ 28 | val androidId = DeviceUtils.getAndroidID() 29 | val clientId = if(!TextUtils.isEmpty(androidId)){ 30 | androidId 31 | }else{ 32 | UUID.randomUUID().toString() 33 | } 34 | mqttClient = MqttAndroidClient(context,serverUrl,clientId) 35 | connectOptions = MqttConnectOptions().apply { 36 | isCleanSession = false //是否会话持久化 37 | connectionTimeout = 30 //连接超时时间 38 | keepAliveInterval = 10 //发送心跳时间 39 | userName = name //如果设置了认证,填的用户名 40 | password = pass.toCharArray() //用户密码 41 | } 42 | } 43 | 44 | /** 45 | * 连接 46 | * @param mqttCallback 接到订阅的消息的回调 47 | * @param isFailRetry 失败是否重新连接 48 | */ 49 | fun connect(topic: Topic, qos: Qos, isFailRetry:Boolean, mqttCallback: MqttCallback){ 50 | mqttClient.setCallback(mqttCallback) 51 | if(mqttActionListener == null){ 52 | mqttActionListener = object :IMqttActionListener{ 53 | override fun onSuccess(asyncActionToken: IMqttToken?) { 54 | LogUtils.eTag("连接","连接成功") 55 | subscribe(topic,qos) 56 | } 57 | override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) { 58 | //失败重连 59 | LogUtils.eTag("连接","连接失败重试${exception?.message}") 60 | if (isFailRetry){ 61 | mqttClient.connect(connectOptions,null,mqttActionListener) 62 | } 63 | } 64 | } 65 | } 66 | mqttClient.connect(connectOptions,null,mqttActionListener) 67 | } 68 | 69 | /** 70 | * 订阅 71 | */ 72 | private fun subscribe(topic: Topic,qos:Qos){ 73 | mqttClient.subscribe(topic.value(),qos.value()) 74 | } 75 | 76 | /** 77 | * 发布 78 | */ 79 | fun publish(topic:Topic,message:String,qos:Qos){ 80 | val msg = MqttMessage() 81 | msg.isRetained = false 82 | msg.payload = message.toByteArray() 83 | msg.qos = qos.value() 84 | mqttClient.publish(topic.value(),msg) 85 | } 86 | 87 | /** 88 | * 断开连接 89 | */ 90 | fun disconnect(){ 91 | mqttClient.disconnect() 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/itfitness/mqttandroid/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.itfitness.mqttandroid 2 | 3 | import android.os.Build 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.widget.Button 7 | import android.widget.EditText 8 | import android.widget.TextView 9 | import androidx.annotation.RequiresApi 10 | import com.blankj.utilcode.util.LogUtils 11 | import com.blankj.utilcode.util.ToastUtils 12 | import com.itfitness.mqttandroid.data.Qos 13 | import com.itfitness.mqttandroid.data.Topic 14 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken 15 | import org.eclipse.paho.client.mqttv3.MqttCallback 16 | import org.eclipse.paho.client.mqttv3.MqttMessage 17 | 18 | class MainActivity : AppCompatActivity() { 19 | @RequiresApi(Build.VERSION_CODES.N) 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_main) 23 | val server = "tcp://mqtt.p2hp.com:1883" //服务端地址 24 | val mqttHelper = MQTTHelper(this,server,"123","123") 25 | mqttHelper.connect(Topic.TOPIC_MSG, Qos.QOS_TWO,false,object : MqttCallback { 26 | override fun connectionLost(cause: Throwable?) { 27 | 28 | } 29 | 30 | override fun messageArrived(topic: String?, message: MqttMessage?) { 31 | //收到消息 32 | message?.payload?.let { ToastUtils.showShort(String(it)) } 33 | LogUtils.eTag("消息", message?.payload?.let { String(it) }) 34 | } 35 | 36 | override fun deliveryComplete(token: IMqttDeliveryToken?) { 37 | 38 | 39 | 40 | } 41 | }) 42 | val etMsg = findViewById(R.id.et_msg) 43 | findViewById