From: snickersun Date: Tue, 18 Aug 2020 15:32:48 +0000 (+0800) Subject: 添加mqtt库文件及service,为C5的协议开发做准备。 X-Git-Url: http://git.snicker.com.cn/sitemap.xml?a=commitdiff_plain;h=2886c9185c3edf447d3d2b22d5e000b274e7c2c0;p=protocol_srv.git 添加mqtt库文件及service,为C5的协议开发做准备。 --- diff --git a/app/app.iml b/app/app.iml index 0bdf7ccf..2ccae5de 100644 --- a/app/app.iml +++ b/app/app.iml @@ -113,6 +113,8 @@ + + diff --git a/app/build.gradle b/app/build.gradle index abf01cee..140dfff2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -107,7 +107,7 @@ dependencies { compile files ('libs/mail.jar' ) compile files ('libs/activation.jar','libs/additionnal.jar' ) - + implementation 'se.wetcat.qatja:core:1.0.4' // implementation "androidx.recyclerview:recyclerview:1.1.0 } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ff91061f..8fc65348 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -161,4 +161,5 @@ -keep class com.enqualcomm.** { *; } -keep class com.sina.util.dnscache.** { *; } -keep class rx.internal.** { *; } --keep class java.util.concurrent.** { *; } \ No newline at end of file +-keep class java.util.concurrent.** { *; } +-keep class se.wetcat.qatia.** { *; } \ No newline at end of file diff --git a/app/src/main/java/se/wetcat/qatja/android/MQTTConnectionConstants.java b/app/src/main/java/se/wetcat/qatja/android/MQTTConnectionConstants.java new file mode 100644 index 00000000..192c18b3 --- /dev/null +++ b/app/src/main/java/se/wetcat/qatja/android/MQTTConnectionConstants.java @@ -0,0 +1,94 @@ +package se.wetcat.qatja.android; + +/* + * Copyright (C) 2012 Andreas Goransson, David Cuartielles + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Connection constants class. + *

+ * TODO: Consider moving this to implementation specific libs. + * + * @author Andreas Goransson + * @version 1.0.0 + * @since 2017-05-07 + */ +public class MQTTConnectionConstants { + + /** + * We're using this base number to make it less likely that any of these + * constans will overlap with constants defined in the activity or by MQTT. + */ + private static final int CONSTANTS_BASE = 846751925; + + /** + * Doing nothing... + */ + public static final int STATE_NONE = CONSTANTS_BASE + 0; + + /** + * Trying to establish connection... + */ + public static final int STATE_CONNECTING = CONSTANTS_BASE + 1; + + /** + * Connection established! + */ + public static final int STATE_CONNECTED = CONSTANTS_BASE + 2; + + /** + * Connection attempt failed + */ + public static final int STATE_CONNECTION_FAILED = CONSTANTS_BASE + 3; + + /** + * Indicate that the state of the connection has changed + */ + public static final int STATE_CHANGE = CONSTANTS_BASE + 5; + + /** + * Sent directly when the client has published, can be used if client wants to + * receive all messages it sends without subscribing. + */ + public static final int MQTT_RAW_PUBLISH = CONSTANTS_BASE + 10; + + /** + * Raw byte-array when a message has been read (any message on the stream) + */ + public static final int MQTT_RAW_READ = CONSTANTS_BASE + 11; + + + /** + * Helper to resolve a connection state name. + * + * @param state The connection state to translate + * @return The connection state name + */ + public static String resolveStateName(int state) { + switch (state) { + case STATE_NONE: + return "STATE_NONE"; + case STATE_CONNECTING: + return "STATE_CONNECTING"; + case STATE_CONNECTED: + return "STATE_CONNECTED"; + case STATE_CONNECTION_FAILED: + return "STATE_CONNECTION_FAILED"; + + default: + return "UNDEFINED STATE"; + } + } +} diff --git a/app/src/main/java/se/wetcat/qatja/android/QatjaService.java b/app/src/main/java/se/wetcat/qatja/android/QatjaService.java new file mode 100644 index 00000000..bce0b899 --- /dev/null +++ b/app/src/main/java/se/wetcat/qatja/android/QatjaService.java @@ -0,0 +1,1268 @@ +package se.wetcat.qatja.android; + +/* + * Copyright (C) 2012 Andreas Goransson, David Cuartielles + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.app.Service; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.text.TextUtils; +import android.util.Log; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Arrays; +import java.util.HashSet; + +import javax.net.ssl.SSLSocketFactory; + +import se.wetcat.qatja.MQTTException; +import se.wetcat.qatja.MQTTHelper; +import se.wetcat.qatja.MQTTIdentifierHelper; +import se.wetcat.qatja.messages.MQTTConnack; +import se.wetcat.qatja.messages.MQTTConnect; +import se.wetcat.qatja.messages.MQTTDisconnect; +import se.wetcat.qatja.messages.MQTTMessage; +import se.wetcat.qatja.messages.MQTTPingreq; +import se.wetcat.qatja.messages.MQTTPuback; +import se.wetcat.qatja.messages.MQTTPubcomp; +import se.wetcat.qatja.messages.MQTTPublish; +import se.wetcat.qatja.messages.MQTTPubrec; +import se.wetcat.qatja.messages.MQTTPubrel; +import se.wetcat.qatja.messages.MQTTSuback; +import se.wetcat.qatja.messages.MQTTSubscribe; +import se.wetcat.qatja.messages.MQTTUnsuback; +import se.wetcat.qatja.messages.MQTTUnsubscribe; + +import static se.wetcat.qatja.MQTTConstants.AT_LEAST_ONCE; +import static se.wetcat.qatja.MQTTConstants.AT_MOST_ONCE; +import static se.wetcat.qatja.MQTTConstants.CONNACK; +import static se.wetcat.qatja.MQTTConstants.CONNECT; +import static se.wetcat.qatja.MQTTConstants.CONNECTION_ACCEPTED; +import static se.wetcat.qatja.MQTTConstants.CONNECTION_REFUSED_AUTH; +import static se.wetcat.qatja.MQTTConstants.CONNECTION_REFUSED_IDENTIFIER; +import static se.wetcat.qatja.MQTTConstants.CONNECTION_REFUSED_SERVER; +import static se.wetcat.qatja.MQTTConstants.CONNECTION_REFUSED_USER; +import static se.wetcat.qatja.MQTTConstants.CONNECTION_REFUSED_VERSION; +import static se.wetcat.qatja.MQTTConstants.DISCONNECT; +import static se.wetcat.qatja.MQTTConstants.EXACTLY_ONCE; +import static se.wetcat.qatja.MQTTConstants.PINGREQ; +import static se.wetcat.qatja.MQTTConstants.PINGRESP; +import static se.wetcat.qatja.MQTTConstants.PUBACK; +import static se.wetcat.qatja.MQTTConstants.PUBCOMP; +import static se.wetcat.qatja.MQTTConstants.PUBLISH; +import static se.wetcat.qatja.MQTTConstants.PUBREC; +import static se.wetcat.qatja.MQTTConstants.PUBREL; +import static se.wetcat.qatja.MQTTConstants.SUBACK; +import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE; +import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE_FAILURE; +import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE_SUCCESS_AT_LEAST_ONCE; +import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE_SUCCESS_AT_MOST_ONCE; +import static se.wetcat.qatja.MQTTConstants.SUBSCRIBE_SUCCESS_EXACTLY_ONCE; +import static se.wetcat.qatja.MQTTConstants.UNSUBACK; +import static se.wetcat.qatja.MQTTConstants.UNSUBSCRIBE; +import static se.wetcat.qatja.android.MQTTConnectionConstants.STATE_CHANGE; +import static se.wetcat.qatja.android.MQTTConnectionConstants.STATE_CONNECTED; +import static se.wetcat.qatja.android.MQTTConnectionConstants.STATE_CONNECTING; +import static se.wetcat.qatja.android.MQTTConnectionConstants.STATE_CONNECTION_FAILED; +import static se.wetcat.qatja.android.MQTTConnectionConstants.STATE_NONE; + +/** + * A simple local service implementation of an mqtt interface for android + * applications. + *

+ * Parts of this class (prominently ConnectThread and ConnectedThread and + * methods connected to thread handling) are based on code found in Android + * Sample Application "BluetoothChat" by Google, distributed via Android SDK. + * + * @author andreas + */ +public class QatjaService extends Service { + + private static final boolean DEBUG = true; + + private static final String TAG = QatjaService.class.getSimpleName(); + + /** + * Current state of the connection + */ + private volatile int mState = STATE_NONE; + + /** + * How long to wait before a reconnect attempt is made + */ + private long mReconnectDelay = 5000; + + /** + * Thread to handle setup of connections + */ + private ConnectThread mConnectThread; + + /** + * Thread to handle communication when connection is established. + */ + private ConnectedThread mConnectedThread; + + /** + * Keepalive timer + */ + private int KEEP_ALIVE_TIMER = 3000; + + /** + * Handler used to queue PINGRESP messages, will be automatically queued when sending a message, + * or when connecting. + */ + private Handler mKeepaliveHandler; + + /** + * + */ + private Handler mHandler = null; + + /** + * The local binder object + */ + private final QatjaBinder mBinder = new QatjaBinder(); + + /** + * The default is the localhost (emulator link to + */ + private String host = "10.0.2.2"; + + /** + * The default is no SSL, turning this on before connect will enforce an SSL connection attempt. + */ + private SSLSocketFactory socketFactory = null; + + private int port = 1883; + + /* + For some reason used in interop test suites... only reason it's included here. + */ + private String protocolName = null; + + /** + * Unique identifier for this client + */ + private String clientIdentifier; + + /* + * TODO: These should be removed from here in future release, no reason to store them here, right? + */ + private String mUsername, mPassword; + + private boolean cleanSession = true; + + private boolean willFlag; + private String willTopic; + private String willMessage; + private HashSet subscribedTopics; + + // PING VARIABLES + private volatile long lastPingResp = 0L; + private volatile long lastSentMessage = 0L; + + private MQTTIdentifierHelper mMqttIdentifierHelper; + + private boolean doAutomaticReconnect = false; + private Handler reconnectHandler = new Handler(); + private Runnable recoonectRunnable = new Runnable() { + @Override + public void run() { + if (mState != STATE_CONNECTING && mState != STATE_CONNECTED) { + connect(); + } + } + }; + + private StateListener mStateListener = null; + + public interface StateListener { + void onStateChanged(int newState); + } + + public void setStateListener(StateListener stateListener) { + mStateListener = stateListener; + + if (mStateListener != null) { + mStateListener.onStateChanged(mState); + } + } + + @Override + public void onCreate() { + super.onCreate(); + + if (DEBUG) + Log.d(TAG, "onCreate"); + + subscribedTopics = new HashSet<>(); + + mMqttIdentifierHelper = new MQTTIdentifierHelper(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (DEBUG) + Log.d(TAG, "onStartCommand"); + + // Start a sticky service, the system will try to recreate this service if killed. + + return START_STICKY; + } + + @Override + public void onDestroy() { + cleanUp(); + + setState(STATE_NONE); + + if (DEBUG) + Log.d(TAG, "onDestroy"); + + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + if (DEBUG) + Log.d(TAG, "onBind"); + + return mBinder; + } + + /** + * @author andreas + */ + public class QatjaBinder extends Binder { + public QatjaService getService() { + return QatjaService.this; + } + } + + /** + * Set Handler that receives all service state messages and mqtt messages + * + * @param handler The listener + */ + public void setHandler(Handler handler) { + mHandler = handler; + } + + /** + * Send a CONNECT message to the server. + */ + private void connect(String clientIdentifier) { + + MQTTConnect connect; + if (!TextUtils.isEmpty(mUsername) && !TextUtils.isEmpty(mPassword)) { + connect = MQTTConnect.newInstance(clientIdentifier, mUsername, mPassword); + } else { + connect = MQTTConnect.newInstance(clientIdentifier); + } + + connect.setCleanSession(cleanSession); + + connect.setWillFlag(willFlag); + if (willFlag) { + connect.setWillTopic(willTopic); + connect.setWillMessage(willMessage); + } + + if (protocolName != null) + connect.setProtocolName(protocolName); + + sendMessage(connect, false); + } + + /** + * Publish a message (String) to a specified topic. + * + * @param topic Topic to publish to + * @param message Message to publish + */ + public void publish(String topic, String message) { + publish(topic, message.getBytes()); + } + + /** + * Publish a payload (byte[]) to a specified topic. + * + * @param topic Topic to publish to + * @param payload Message to publish + */ + public void publish(String topic, byte[] payload) { + publish(topic, payload, AT_MOST_ONCE); + } + + /** + * Publish a message to a specified topic. + * + * @param topic Topic to publish to + * @param message Message to publish + * @param qos Quality of service to publish as + */ + public void publish(String topic, String message, byte qos) { + publish(topic, message.getBytes(), qos); + } + + /** + * Publish a message (byte[]) to a specified topic. + * + * @param topic the topic + * @param payload the message + * @param qos the desired quality of service + */ + public void publish(String topic, byte[] payload, byte qos) { + MQTTPublish publish = MQTTPublish.newInstance(topic, payload, qos, mMqttIdentifierHelper.getIdentifier()); + sendMessage(publish); + } + + /** + * Publish a retained message to a specified topic. + * + * @param topic the topic + * @param message the message + * @param qos the desired quality of service + */ + public void publishRetain(String topic, String message, byte qos) { + publishRetain(topic, message.getBytes(), qos); + } + + /** + * Publish a retained message to a specified topic. + * + * @param topic the topic + * @param payload the message to publish + * @param qos the desired quality of service + */ + public void publishRetain(String topic, byte[] payload, byte qos) { + MQTTPublish publish = MQTTPublish.newInstance(topic, payload, qos); + publish.setRetain(true); + sendMessage(publish); + } + + /** + * Subscribe to a topic with Quality of Service (QoS) level + * {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE} + * + * @param topic Topic to subscribe to + */ + public void subscribe(String topic) { + String[] topics = {topic}; + subscribe(topics); + } + + /** + * Subscribe to multiple topics with quality of service + * {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE} + * + * @param topics Topics to subscribe to + */ + public void subscribe(String[] topics) { + byte[] qoss = new byte[topics.length]; + for (int i = 0; i < qoss.length; i++) + qoss[i] = AT_MOST_ONCE; + subscribe(topics, qoss); + } + + /** + * Subscribe to a topic + * + * @param topic Topic to subscribe to + * @param qos Quality of service, can be {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE}, + * {@link se.wetcat.qatja.MQTTConstants#AT_LEAST_ONCE}, or + * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE}. + */ + public void subscribe(String topic, byte qos) { + String[] topics = {topic}; + byte[] qoss = {qos}; + subscribe(topics, qoss); + } + + /** + * Subscribe to multiple topics + * + * @param topics Topic to subscribe to + * @param qoss Quality of service, can be {@link se.wetcat.qatja.MQTTConstants#AT_MOST_ONCE}, + * {@link se.wetcat.qatja.MQTTConstants#AT_LEAST_ONCE}, or + * {@link se.wetcat.qatja.MQTTConstants#EXACTLY_ONCE}. + */ + public void subscribe(String[] topics, byte[] qoss) { + // Ensure the subscribedTopics isn't null + if (subscribedTopics == null) + subscribedTopics = new HashSet<>(); + + for(int i = 0; i < topics.length; i++) { + HashSet newTopics = new HashSet<>(); + + if(!subscribedTopics.contains(topics[i])) { + newTopics.add(topics[i]); + } + topics = newTopics.toArray(new String[0]); + } + + if(topics.length == 0) { + return; + } + + int id = mMqttIdentifierHelper.getIdentifier(); + + MQTTSubscribe subscribe = MQTTSubscribe.newInstance(topics, qoss, id); + sendMessage(subscribe); + + subscribedTopics.addAll(Arrays.asList(topics)); + + mMqttIdentifierHelper.addSentPackage(subscribe); + } + + // public synchronized void start() { + // if (DEBUG) + // Log.d(TAG, "start"); + // + // // Cancel any thread attempting to make a connection + // if (mConnectThread != null) { + // mConnectThread.cancel(); + // mConnectThread = null; + // } + // + // // Cancel any thread currently running a connection + // if (mConnectedThread != null) { + // mConnectedThread.cancel(); + // mConnectedThread = null; + // } + // + // setState(STATE_NONE); + // + // // if (host != null) + // // connect(host, port); + // } + + private synchronized void sendMessage(MQTTMessage msg) { + sendMessage(msg, true); + } + + private synchronized void sendMessage(MQTTMessage msg, boolean mustBeConnected) { + if (DEBUG) + Log.d(TAG, "Sending message: " + MQTTHelper.decodePackageName(msg)); + + if (mustBeConnected) { + if (getState() == STATE_CONNECTED) { + new WriterTask().execute(msg); + } else { + if (DEBUG) + Log.d(TAG, "Need to be connected to send " + MQTTHelper.decodePackageName(msg)); + + // If we lost the connection, and reconnect is set, let's try to reconnect now! + if (doAutomaticReconnect) + reconnect(); + } + } else { + new WriterTask().execute(msg); + } + + +// if (mustBeConnected) { +// if (getState() == STATE_CONNECTED) { +// try { +// mConnectedThread.write(msg.get()); +// lastAction = System.currentTimeMillis(); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } catch (MQTTException e) { +// e.printStackTrace(); +// } +// } else { +// if (DEBUG) +// Log.d(TAG, +// "Need to be connected to send " +// + MQTTHelper.decodePackageName(msg)); +// } +// } else { +// try { +// mConnectedThread.write(msg.get()); +// lastAction = System.currentTimeMillis(); +// } catch (UnsupportedEncodingException e) { +// Log.e(TAG, "FAiled to send message " + MQTTHelper.decodePackageName(msg.getType()), e); +// } catch (IOException e) { +// Log.e(TAG, "FAiled to send message " + MQTTHelper.decodePackageName(msg.getType()), e); +// } catch (MQTTException e) { +// Log.e(TAG, "FAiled to send message " + MQTTHelper.decodePackageName(msg.getType()), e); +// } +// } + } + + public String[] getSubscribedTopics() { + if(subscribedTopics == null) { + return new String[]{}; + } + return subscribedTopics.toArray(new String[0]); + } + + public void setSSLSocketFactory(SSLSocketFactory socketFactory) { + this.socketFactory = socketFactory; + } + + /** + * Set MQTT broker host, can be either a domain name or an ip address + * + * @param host the host + */ + public void setHost(String host) { + this.host = host; + } + + /** + * Set MQTT broker port + * + * @param port + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Set client identifier + * + * @param clientIdentifier the identifier + */ + public void setIdentifier(String clientIdentifier) { + this.clientIdentifier = clientIdentifier; + } + + public void setUsername(String username) { this.mUsername = username; } + + public void setPassword(String password) { this.mPassword = password; } + + public void setProtocolName(String protocolName) { + this.protocolName = protocolName; + } + + /** + * Set clean session + * + * @param clean_session + */ + public void setCleanSession(boolean clean_session) { + this.cleanSession = clean_session; + } + + /** + * Set will + * + * @param topic the will topic + * @param message the will message + */ + public void setWill(String topic, String message) { + if (topic == null || message == null) { + willFlag = false; + } else { + willTopic = topic; + willMessage = message; + willFlag = true; + } + } + + /** + * Set automatic reconnect + * + * @param reconnect + */ + public void setReconnect(boolean reconnect) { + doAutomaticReconnect = reconnect; + } + + public void setReconnectDelay(long millis) { + mReconnectDelay = millis; + } + + /** + * Attempt to reconnect after {@link #mReconnectDelay} milliseconds + */ + public void reconnect() { + reconnectHandler.postDelayed(recoonectRunnable, mReconnectDelay); + } + + /** + * Attempt to reconnect after delay + * + * @param milliseconds the delay + */ + public void reconnect(long milliseconds) { + reconnectHandler.postDelayed(recoonectRunnable, milliseconds); + } + + /** + * This runnable will take care of sending a ping request, it will be automatically cancelled and + * rescheduled if another message was sent to the broker (as defined in MQTT spec 3.1.1 we're not + * required to send ping as long as another message has been sent within the specified period. + */ + private Runnable mPingSender = new Runnable() { + @Override + public void run() { + sendMessage(MQTTPingreq.newInstance()); + + // Make sure to auto-queue the runnable again when the message has been sent. + mKeepaliveHandler.postDelayed(mPingSender, KEEP_ALIVE_TIMER); + } + }; + + /** + * Start a connection attempt + */ + public synchronized void connect() { + if (DEBUG) + Log.d(TAG, "connect to: " + host); + + cleanUp(); + + // Start the thread to connect with the given device + try { + mConnectThread = new ConnectThread(host, port); + mConnectThread.start(); + + setState(STATE_CONNECTING); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + + setState(STATE_CONNECTION_FAILED); + } + } + + public synchronized void connect(boolean newSocket) { + if (DEBUG) + Log.d(TAG, "connect to: " + host); + if (newSocket) { + // Cancel any future ping requests + if (mKeepaliveHandler != null) { + mKeepaliveHandler.removeCallbacks(mPingSender); + mKeepaliveHandler = null; + } + + // Cancel any thread attempting to make a connection + if (getState() == STATE_CONNECTING) { + if (mConnectThread != null) { + mConnectThread.cancel(); + mConnectThread = null; + } + } + + // Cancel any thread currently running a connection + if (mConnectedThread != null) { + mConnectedThread.cancel(); + mConnectedThread = null; + } + + if(subscribedTopics != null) { + subscribedTopics.clear(); + } + + // Start the thread to connect with the given device + try { + mConnectThread = new ConnectThread(host, port); + mConnectThread.start(); + + setState(STATE_CONNECTING); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + + setState(STATE_CONNECTION_FAILED); + } + } else { + setState(STATE_CONNECTING); + + connect(clientIdentifier); + } + } + + /** + * Service connected + * + * @param socket the connected socket + */ + private synchronized void connected(Socket socket) { + if (DEBUG) + Log.d(TAG, "connected, Socket Type:"); + + // Cancel any future ping requests + if (mKeepaliveHandler != null) { + mKeepaliveHandler.removeCallbacks(mPingSender); + mKeepaliveHandler = null; + } + + // Cancel the thread that completed the connection + if (mConnectThread != null) { + mConnectThread.cancel(); + mConnectThread = null; + } + + // Cancel any thread currently running a connection + if (mConnectedThread != null) { + mConnectedThread.cancel(); + mConnectedThread = null; + } + + if(subscribedTopics != null) { + subscribedTopics.clear(); + } + + // Start the thread to manage the connection and perform transmissions + mConnectedThread = new ConnectedThread(socket); + mConnectedThread.start(); + + // Start the PINGREQ handler + mKeepaliveHandler = new Handler(getMainLooper()); + mKeepaliveHandler.postDelayed(mPingSender, KEEP_ALIVE_TIMER); + + Log.d(TAG, "Sending connect message"); + + // Send the connect message + connect(clientIdentifier); + + // setState(STATE_CONNECTED); + } + + /** + * Indicate that the connection attempt failed and notify the UI Activity. + */ + private void connectionFailed(Exception e) { + if (DEBUG) + Log.d(TAG, "connectionFailed", e); + + if(subscribedTopics != null) { + subscribedTopics.clear(); + } + + if (doAutomaticReconnect) + reconnect(); + + setState(STATE_CONNECTION_FAILED); + } + + /** + * Indicate that the connection was lost and notify the UI Activity. + */ + private void connectionLost() { + if (DEBUG) + Log.d(TAG, "connectionLost"); + + cleanUp(); + + setState(STATE_NONE); + + if (doAutomaticReconnect) + reconnect(); + } + + /** + * Disconnect the MQTT service + */ + public void disconnect() { + if (mState == STATE_CONNECTED) { + sendMessage(MQTTDisconnect.newInstance()); + } + } + + /** + * Clean up the connection handling. + */ + private void cleanUp() { + if (DEBUG) { + Log.d(TAG, "cleanUp()"); + } + + // Cancel any future ping requests + try { + if (mKeepaliveHandler != null) { + mKeepaliveHandler.removeCallbacks(mPingSender); + mKeepaliveHandler = null; + } + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + + // Cancel the thread that completed the connection + if (mConnectThread != null) { + mConnectThread.cancel(); + mConnectThread = null; + } + + // Cancel any thread currently running a connection + if (mConnectedThread != null) { + mConnectedThread.cancel(); + mConnectedThread = null; + } + + try { + if (subscribedTopics != null) { + subscribedTopics.clear(); + } + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + } + + /** + * Set the connection state + * + * @param state the state + */ + private synchronized void setState(int state) { + if (DEBUG) + Log.d(TAG, "setState() " + mState + " -> " + state); + + if (mState != state) { + mState = state; + + if (mHandler != null) { + if (mStateListener != null) { + mStateListener.onStateChanged(state); + } + + // Give the new state to the Handler so the UI Activity can + // update + mHandler.obtainMessage(STATE_CHANGE, state, -1).sendToTarget(); + } + } + } + + /** + * Get the connection state + * + * @return the state + */ + public synchronized int getState() { + return mState; + } + + /** + * Set keep alive timer in milliseconds + * + * @param milliseconds the time + */ + public void setKeepAlive(int milliseconds) { + KEEP_ALIVE_TIMER = milliseconds; + } + + /** + * Make sure to resend packages that haven't been successfully sent. + */ + private synchronized void resendPackages() { + for (MQTTMessage msg : mMqttIdentifierHelper.getSentPackages().values()) { + if (msg instanceof MQTTPublish) { + ((MQTTPublish) msg).setDup(); + } + sendMessage(msg); + } + + for (MQTTMessage msg : mMqttIdentifierHelper.getReceivedPackages().values()) { + if (msg instanceof MQTTPublish) { + ((MQTTPublish) msg).setDup(); + } + sendMessage(msg); + } + } + + /** + * Manages subscription messages + * + * @param msg the message to handle + */ + private synchronized void handleSubscriptions(MQTTMessage msg) { + if (msg instanceof MQTTSuback) { + MQTTSuback suback = (MQTTSuback) msg; + + MQTTSubscribe subscribe = (MQTTSubscribe) mMqttIdentifierHelper.getSentPackages().get(suback.getPackageIdentifier()); + + String[] topicFilters = subscribe.getTopicFilters(); + byte[] qoss = suback.getPayload(); + for (int i = 0; i < qoss.length; i++) { + switch (qoss[i]) { + case SUBSCRIBE_SUCCESS_AT_MOST_ONCE: + case SUBSCRIBE_SUCCESS_AT_LEAST_ONCE: + case SUBSCRIBE_SUCCESS_EXACTLY_ONCE: + Log.d(TAG, "Success subscribing to " + topicFilters[i]); + break; + + case SUBSCRIBE_FAILURE: + Log.d(TAG, "Failed subscribing to " + topicFilters[i]); + break; + } + } + } else if (msg instanceof MQTTUnsuback) { + MQTTUnsuback unsuback = (MQTTUnsuback) msg; + + MQTTUnsubscribe unsubscribe = (MQTTUnsubscribe) mMqttIdentifierHelper.getSentPackages().get(unsuback.getPackageIdentifier()); + String[] topicFilters = unsubscribe.getTopicFilters(); + for (int i = 0; i < topicFilters.length; i++) { + Log.d(TAG, "Success unsubscribing to " + topicFilters[i]); + } + } + + mHandler.obtainMessage(msg.getType(), -1, -1, msg).sendToTarget(); + } + + private class ConnectThread extends Thread { + private static final String TAG = "ConnectThread"; + + private final Socket mmSocket; + + /** + * Timeout of connection attempts (ms) + */ + private int timeout = 3000; + + private InetSocketAddress remoteAddr; + + ConnectThread(String host, int port) throws IOException { + if (DEBUG) + Log.d(TAG, "CREATE mConnectThread "); + + if (socketFactory != null) { + mmSocket = socketFactory.createSocket(); + } else { + mmSocket = new Socket(); + } + } + + public void run() { + if (DEBUG) + Log.d(TAG, "BEGIN mConnectThread"); + + setName("ConnectThread"); + + remoteAddr = new InetSocketAddress(host, port); + + // Make a connection to the Socket + try { + // This is a blocking call and will only return on a successful connection or an exception + mmSocket.connect(remoteAddr, timeout); + } catch (IOException e) { + // Close the socket + try { + mmSocket.close(); + } catch (IOException e2) { + Log.e(TAG, + "unable to close() socket during connection failure", + e2); + } + connectionFailed(e); + return; + } + + // Reset the ConnectThread because we're done + synchronized (this) { + mConnectThread = null; + } + + // Start the connected thread + connected(mmSocket); + } + + public void cancel() { + try { + mmSocket.close(); + } catch (IOException e) { + Log.e(TAG, "close() of connect socket failed", e); + } + } + } + + /** + * This thread runs during a connection with a remote device. It handles all incoming and outgoing + * transmissions. + */ + private class ConnectedThread extends Thread { + private final Socket mmSocket; + private final InputStream mmInStream; + private final OutputStream mmOutStream; + + public ConnectedThread(Socket socket) { + if (DEBUG) + Log.d(TAG, "CREATE mConnectedThread"); + + mmSocket = socket; + InputStream tmpIn = null; + OutputStream tmpOut = null; + + // Get the Socket input and output streams + try { + tmpIn = socket.getInputStream(); + tmpOut = socket.getOutputStream(); + } catch (IOException e) { + Log.e(TAG, "temp sockets not created", e); + } + + mmInStream = tmpIn; + mmOutStream = tmpOut; + + if (DEBUG) + Log.d(TAG, "BEGIN mConnectedThread"); + } + + public void run() { + int len; + byte[] buffer = new byte[16384]; + + // Keep listening to the InputStream while connected + while (!isInterrupted()) { + try { + // Read from the InputStream + len = mmInStream.read(buffer); + + if (mHandler != null) + + if (len > 0) { + byte type = MQTTHelper.decodeType(buffer); + + if (DEBUG) + Log.d(TAG, "Received " + MQTTHelper.decodePackageName(type)); + + // Handle automatic responses here + switch (type) { + case CONNECT: + // Client should never receive CONNECT message + break; + + case CONNACK: + MQTTConnack connack = MQTTConnack.fromBuffer(buffer); + + switch (connack.getReturnCode()) { + case CONNECTION_ACCEPTED: + if (DEBUG) + Log.d(TAG, "Connected"); + setState(STATE_CONNECTED); + break; + + case CONNECTION_REFUSED_VERSION: + if (DEBUG) + Log.d(TAG, "Failed to connect, unaccebtable protocol version"); + setState(STATE_CONNECTION_FAILED); + break; + + case CONNECTION_REFUSED_IDENTIFIER: + if (DEBUG) + Log.d(TAG, "Failed to connect, identifier rejected"); + setState(STATE_CONNECTION_FAILED); + break; + + case CONNECTION_REFUSED_SERVER: + if (DEBUG) + Log.d(TAG, "Failed to connect, server unavailable"); + setState(STATE_CONNECTION_FAILED); + break; + + case CONNECTION_REFUSED_USER: + if (DEBUG) + Log.d(TAG, "Failed to connect, bad username or password"); + setState(STATE_CONNECTION_FAILED); + break; + + case CONNECTION_REFUSED_AUTH: + if (DEBUG) + Log.d(TAG, "Failed to connect, not authorized"); + setState(STATE_CONNECTION_FAILED); + break; + } + break; + + case PUBLISH: + MQTTPublish publish = MQTTPublish.fromBuffer(buffer); + switch (publish.getQoS()) { + case AT_MOST_ONCE: + // Do nothing + break; + + case AT_LEAST_ONCE: + // Send PUBACK + MQTTPuback puback_ = MQTTPuback.newInstance(publish.getPackageIdentifier()); + sendMessage(puback_); + break; + + case EXACTLY_ONCE: + // Send PUBREC and store message + MQTTPubrec pubrec_ = MQTTPubrec.newInstance(publish.getPackageIdentifier()); + mMqttIdentifierHelper.addReceivedPackage(pubrec_); + sendMessage(pubrec_); + + mMqttIdentifierHelper.addSentPackage(pubrec_); + break; + } + mHandler.obtainMessage(PUBLISH, mState, -1, publish).sendToTarget(); + break; + + case PUBACK: + MQTTPuback puback = MQTTPuback.fromBuffer(buffer); + mMqttIdentifierHelper.removeSentPackage(puback); + break; + + case PUBREC: + MQTTPubrec pubrec = MQTTPubrec.fromBuffer(buffer); + int packageIdentifier = pubrec.getPackageIdentifier(); + mMqttIdentifierHelper.removeSentPackage(pubrec); + + MQTTPubrel pubrel_ = MQTTPubrel.newInstance(packageIdentifier); + sendMessage(pubrel_); + break; + + case PUBREL: + MQTTPubrel pubrel = MQTTPubrel.fromBuffer(buffer); + mMqttIdentifierHelper.removeReceivedPackage(pubrel); + + MQTTPubcomp pubcomp_ = MQTTPubcomp.newInstance(pubrel.getPackageIdentifier()); + sendMessage(pubcomp_); + break; + + case PUBCOMP: + MQTTPubcomp pubcomp = MQTTPubcomp.fromBuffer(buffer); + mMqttIdentifierHelper.removeReceivedPackage(pubcomp); + break; + + case SUBSCRIBE: + // Client doesn't receive this message + break; + + case SUBACK: + MQTTSuback suback = MQTTSuback.fromBuffer(buffer); + handleSubscriptions(suback); + break; + + case UNSUBSCRIBE: + // Client doesn't receive this message + break; + + case UNSUBACK: + MQTTUnsuback unsuback = MQTTUnsuback.fromBuffer(buffer); + handleSubscriptions(unsuback); + mMqttIdentifierHelper.removeSentPackage(unsuback); + break; + + case PINGREQ: + // Client doesn't receive this message + break; + + case PINGRESP: + lastPingResp = System.currentTimeMillis(); + break; + + case DISCONNECT: + // Client doesn't receive this message + break; + } + } + + } catch (IOException e) { + if (DEBUG) + Log.e(TAG, "disconnected (this is fine, expected almost)", e); + + connectionLost(); + + break; + } + } + } + + /** + * Write to the connected OutStream. + * + * @param buffer The bytes to write + */ + public synchronized void write(byte[] buffer) { + try { + mmOutStream.write(buffer); + } catch (IOException e) { + Log.e(TAG, "Exception during write", e); + + // Just notify the client that the connection was lost! + connectionLost(); + } + } + + void cancel() { + new DisconnectTask().execute(mmInStream, mmOutStream, mmSocket); + } + } + + private static class DisconnectTask extends AsyncTask { + @Override + protected Void doInBackground(Closeable... closeables) { + if (DEBUG) { + Log.d(TAG, "[DisconnectTask] Closing streams and socket."); + } + + for (Closeable closeable : closeables) { + try { + closeable.close(); + } catch (IOException e) { + Log.e(TAG, e.getMessage(), e); + } + } + + return null; + } + } + + private class WriterTask extends AsyncTask { + + @Override + protected MQTTMessage doInBackground(MQTTMessage... params) { + if (params.length == 0) + return null; + + try { + mConnectedThread.write(params[0].get()); + return params[0]; + } catch (MQTTException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + + return null; + } + + @Override + protected void onPostExecute(MQTTMessage sent) { + super.onPostExecute(sent); + + if (!isCancelled()) { + if (sent != null) { + lastSentMessage = System.currentTimeMillis(); + + try { + if (mKeepaliveHandler != null) { + mKeepaliveHandler.removeCallbacks(mPingSender); + mKeepaliveHandler.postDelayed(mPingSender, KEEP_ALIVE_TIMER); + } + } catch (Exception ex) { + Log.e(TAG, ex.getMessage(), ex); + } + } + } + } + } +}