I'm using Smack and Openfire for my XMPP connection/server. But I've run into the very common problem (apparently) of resource conflicts. Can anyone tell me the proper way of handling a conflict?
Openfire is set to always kick the original resource (it's a bespoke platform, not open to the public). But I still get the error and don't get a new connection. My XMPP class is below.
package com.goosesys.gaggle.services;
import java.util.Collection;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
import com.google.gson.Gson;
import com.goosesys.gaggle.Globals;
import com.goosesys.gaggle.application.AppSettings;
import com.goosesys.gaggle.application.Utility;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class BackgroundXmppConnector extends Service
{
private ConnectionConfiguration acc;
private XMPPConnection xConnection;
private final IBinder mBinder = new XmppBinder();
private final Handler mHandler = new Handler();
private final int manualReconnectionTimer = (5 * 60 * 1000);
private static int mInterval1m = (2 * 60 * 1000);
private static int mInterval5m = (5 * 60 * 1000);
private static boolean bConnecting = false;
private static final Object connectLock = new Object();
private static final Object checkLock = new Object();
private final Runnable checkConnection = new Runnable()
{
#Override
public void run()
{
synchronized(checkLock)
{
Log.d("BXC", "Handler running - Checking connection");
checkConnectionStatus();
}
}
};
private final Runnable killConnection = new Runnable()
{
#Override
public void run()
{
synchronized(checkLock)
{
Log.d("BXC", "Killing connection and restarting");
// Manually disconnect and restart the connection every 5 minutes
if(xConnection != null)
xConnection.disconnect();
destroyConnectionAndRestart();
new LoginTask().execute();
mHandler.postDelayed(this, mInterval5m);
}
}
};
#Override
public void onCreate()
{
Log.i("BXC", "BackgroundXmppConnector Service has been created");
// Checks the connection state every 1 minute //
mHandler.postDelayed(checkConnection, mInterval1m);
mHandler.postDelayed(killConnection, mInterval5m);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.d("BXC", "Xmpp Connector Started");
new LoginTask().execute();
return Service.START_STICKY;
}
private void destroyConnectionAndRestart()
{
xConnection.disconnect();
xConnection = null;
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
}
private void setupConnection()
{
Log.d("BXC", "Settting up XMPP connection");
try
{
if(!bConnecting && !Globals.backgroundXmppConnectorRunning)
{
acc = new ConnectionConfiguration(AppSettings.XMPP_SERVER_HOST,
AppSettings.XMPP_SERVER_PORT);
acc.setSecurityMode(SecurityMode.disabled);
acc.setSASLAuthenticationEnabled(false);
acc.setReconnectionAllowed(false);
acc.setSendPresence(true);
xConnection = new XMPPConnection(acc);
xConnection.addConnectionListener(new ConnectionListener()
{
#Override
public void connectionClosed()
{
Log.e("BXC", "Xmpp connection closed");
Globals.backgroundXmppConnectorRunning = false;
Globals.numberOfDisconnects += 1;
//destroyConnectionAndRestart();
Utility.writeToLog(getApplicationContext(), "Xmpp Connection closed - disconnected# (" + Globals.numberOfDisconnects + ")");
}
#Override
public void connectionClosedOnError(Exception e)
{
Log.e("BXC", "Xmpp connection closed with error: " + e);
Globals.backgroundXmppConnectorRunning = false;
Globals.numberOfDisconnectsOnError += 1;
// This is more than likely due to a conflict loop - it's best to disconnect and nullify
// our connection and let the software restart when it checks every 5 minutes
if(e.toString().toUpperCase().contains("CONFLICT"))
{
Log.e("BXC", "Conflict connection loop detected - Waiting");
}
Utility.writeToLog(getApplicationContext(), "Xmpp Connection closed with error [" + e + "] - disconnected# (" + Globals.numberOfDisconnectsOnError + ")");
}
#Override
public void reconnectingIn(int seconds)
{
Log.i("BXC", "Xmpp connection, reconnecting in " + seconds + " seconds");
Globals.backgroundXmppConnectorRunning = false;
bConnecting = true;
}
#Override
public void reconnectionFailed(Exception e)
{
Log.e("BXC", "Xmpp reconnection failed: " + e);
Globals.backgroundXmppConnectorRunning = false;
//destroyConnectionAndRestart();
Utility.writeToLog(getApplicationContext(), "Xmpp reConnection failed with error [" + e + "] - disconnected# (" + Globals.numberOfDisconnects + ")");
}
#Override
public void reconnectionSuccessful()
{
Log.i("BXC", "Xmpp reconnected successfully");
Globals.backgroundXmppConnectorRunning = true;
bConnecting = false;
}
});
}
else
{
Log.i("BXC", "Already in connecting state");
}
}
catch (Exception e)
{
Log.e("BXC", e.getMessage());
}
}
public boolean sendMessage(Intent intent)
{
if(xConnection != null && xConnection.isConnected())
{
String jsonObject;
Bundle extras = intent.getExtras();
if(extras != null)
{
jsonObject = extras.getString("MESSAGEDATA");
Message m = new Gson().fromJson(jsonObject, Message.class);
if(m != null)
{
sendMessage(m);
}
else
{
Log.e("BXC", "Message to send was/is null. Can't send.");
}
m = null;
jsonObject = null;
extras = null;
}
Log.i("BXC", "Sending Xmpp Packet");
return true;
}
return false;
}
/*
* Sends message to xmpp server - message packet in form of
*
* --------------------MESSAGE PACKET-------------------------
* TO
* -----------------------
* FROM
* -----------------------
* BODY
* TRANSACTION-------------------------------------------
* MessageType
* --------------------------------------------------
* TransactionObject
*/
private void sendMessage(Message m)
{
try
{
Log.d("BXC", "Sending transaction message to Xmpp Server");
xConnection.sendPacket(m);
//Toast.makeText(getApplicationContext(), "Packet sent to XMPP", Toast.LENGTH_LONG).show();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
private void checkConnectionStatus()
{
Log.d("BXC", "Checking Xmpp connection status");
if(xConnection == null || xConnection.isAuthenticated() == false ||
xConnection.isConnected() == false || xConnection.isSocketClosed() ||
Globals.backgroundXmppConnectorRunning == false)
{
Log.e("BXC", "Connection to server is dead. Retrying");
Toast.makeText(getApplicationContext(), "Connection dead - retrying", Toast.LENGTH_SHORT).show();
destroyConnectionAndRestart();
new LoginTask().execute();
}
else
{
Log.i("BXC", "Connection appears to be valid");
Toast.makeText(getApplicationContext(), "Connection valid", Toast.LENGTH_SHORT).show();
}
}
// BINDER ////////////////////////////////////////////////////////////////////////////////
#Override
public IBinder onBind(Intent intent)
{
return mBinder;
}
// INTERNAL CLASSES //////////////////////////////////////////////////////////////////////
public class XmppBinder extends Binder
{
public BackgroundXmppConnector getService(){
return BackgroundXmppConnector.this;
}
}
private class LoginTask extends AsyncTask<Void, Void, Void>
{
#Override
protected Void doInBackground(Void... params)
{
// First ensure we've got a connection to work with first
if(Utility.hasActiveInternetConnection(getApplicationContext()) &&
((!bConnecting) || (!Globals.backgroundXmppConnectorRunning)))
{
try
{
//bConnecting = true;
Log.d("BXC", "Beginning connection");
synchronized(connectLock)
{
setupConnection();
xConnection.connect();
Log.i("BXC", "Login credentials: " + Utility.getAndroidID(getApplicationContext()) + " " + AppSettings.XMPP_KEYSTORE_PASSWORD);
xConnection.login(Utility.getAndroidID(getApplicationContext()), AppSettings.XMPP_KEYSTORE_PASSWORD);
xConnection.getChatManager().addChatListener(new ChatManagerListener(){
#Override
public void chatCreated(final Chat chat, boolean createdLocally)
{
if(!createdLocally)
{
// add chat listener //
chat.addMessageListener(new BackgroundMessageListener(getApplicationContext()));
}
}
});
Presence p = new Presence(Presence.Type.subscribe);
p.setStatus("Out and About");
xConnection.sendPacket(p);
Roster r = xConnection.getRoster();
r.setSubscriptionMode(SubscriptionMode.accept_all);
r.createEntry(AppSettings.BOT_NAME, "AbleBot", null);
r.addRosterListener(new RosterListener(){
#Override
public void entriesAdded(Collection<String> addresses)
{
for(String s : addresses)
{
Log.d("BXC", "Entries Added: " + s);
}
}
#Override
public void entriesDeleted(Collection<String> addresses)
{
for(String s : addresses)
{
Log.d("BXC", "Entries Deleted: " + s);
}
}
#Override
public void entriesUpdated(Collection<String> addresses)
{
for(String s : addresses)
{
Log.d("BXC", "Entries updated: " + s);
}
}
#Override
public void presenceChanged(Presence presence)
{
Log.d("BXC", "PresenceChanged: " + presence.getFrom());
}
});
}
}
catch(IllegalStateException ex)
{
Log.e("BXC", "IllegalStateException -->");
if(ex.getMessage().contains("Already logged in to server"))
{
Globals.backgroundXmppConnectorRunning = true;
}
else
{
Globals.backgroundXmppConnectorRunning = false;
Utility.writeExceptionToLog(getApplicationContext(), ex);
ex.printStackTrace();
}
}
catch(XMPPException ex)
{
Log.e("BXC", "XMPPException -->");
Globals.backgroundXmppConnectorRunning = false;
Utility.writeExceptionToLog(getApplicationContext(), ex);
ex.printStackTrace();
}
catch(NullPointerException ex)
{
Log.e("BXC", "NullPointerException -->");
Globals.backgroundXmppConnectorRunning = false;
Utility.writeExceptionToLog(getApplicationContext(), ex);
ex.printStackTrace();
}
catch(Exception ex)
{
Log.e("BXC", "Exception -->");
Globals.backgroundXmppConnectorRunning = false;
Utility.writeToLog(getApplicationContext(), ex.toString());
ex.printStackTrace();
}
return null;
}
else
{
Log.i("BXC", "No active internet data connection - will retry");
}
return null;
}
#Override
protected void onPostExecute(Void ignored)
{
if(xConnection != null)
{
if(xConnection.isConnected() && (!xConnection.isSocketClosed()))
{
Log.i("BXC", "Logged in to XMPP Server");
Globals.backgroundXmppConnectorRunning = true;
mHandler.postDelayed(checkConnection, mInterval1m);
}
else
{
Log.e("BXC", "Unable to log into XMPP Server.");
Globals.backgroundXmppConnectorRunning = false;
destroyConnectionAndRestart();
}
}
else
{
Log.e("BXC", "Xmpp Connection object is null");
Globals.backgroundXmppConnectorRunning = false;
}
}
}
}
From what I've read, openfire (when set to always kick) will always kick the original resource and then allow the new login to connect, but I'm not seeing this at all. Any help is greatly appreciated. Thanks.
The proper way to deal with resource conflicts is to not use a fixed resource unless you absolutely must. If your client specifies no resource when logging in, the server should assign it a randomly generated one which will never conflict.
There are very few reasons why you'd ever need to specify a fixed resource, as most clients hide the resource of your contacts anyway, and there are other reasons why having a new resource on every connection is advantageous (like avoiding a common bug with group chats getting out of sync because the group chat's server didn't realize the connecting user is actually a new session).
A big problem with fixed resources is reconnection loops, where, if the server is configured to kick the old conflicting resource, two clients kick each other repeatedly. You should make sure you don't automatically reconnect when receiving a resource conflict error.
From what I've read, openfire (when set to always kick) will always kick the original resource and then allow the new login to connect,…
Terminating the other connection with the same resource, ie. the previous one, is one option to deal with it. Most, if not all, XMPP servers provide this policy option regarding handling resource conflicts.
I can't comment on why setting Openfire to "Always kick" doesn't work for you, but it certainly does for me. And IIRC there are no current bug reports in Openfire that tell otherwise.
There is also another trivial approach: If you get a resource conflict on login(), simply specify a different resource: login(String username, String password, String resource).
Or even more trivial: If you don't care how what the resource String is, you can have the server auto assign one to you. Simply use 'null' as resource argument: login(user, password, null).
Related
Hello we have downloaded the ejabberd from this. domain is localhost and we have set the xmppDomain as my computer's ip address. I have used the following code for connection
public static final String XMPP_DOMAIN = "localhost";
public static final String XMPP_HOST = "10.0.2.2";//kept for emaulator
//public static final String XMPP_HOST = "192.168.1.152"; //ip of my pc
public static final int XMPP_PORT = 5222;
public static final String XMPP_RESOURCE = "xmppdemo";
public static final boolean XMPP_DEBUG = true;
private void initialiseConnection() throws IOException, InterruptedException, XMPPException, SmackException {
InetAddress addr = InetAddress.getByName(Constants.XMPP_HOST);
DomainBareJid serviceName = JidCreate.domainBareFrom(Constants.XMPP_DOMAIN);
XMPPTCPConnectionConfiguration.Builder config = XMPPTCPConnectionConfiguration
.builder();
config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
config.setXmppDomain(serviceName);
config.setHostAddress(addr);
config.setPort(Constants.XMPP_PORT);
config.setDebuggerEnabled(Constants.XMPP_DEBUG);
config.setResource(Constants.XMPP_RESOURCE);
connection = new XMPPTCPConnection(config.build());
connection.addConnectionListener(mConnectionListener);
connection.addAsyncStanzaListener(mStanzaListener, new StanzaFilter() {
#Override
public boolean accept(Stanza stanza) {
//You can also return only presence packets, since we are only filtering presences
return true;
}
});
Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
roster = Roster.getInstanceFor(connection);
roster.addRosterListener(mRoasterListener);
}
public void connect() {
#SuppressLint("StaticFieldLeak") AsyncTask<Void, Void, Boolean> connectionThread = new AsyncTask<Void, Void, Boolean>() {
#Override
protected synchronized Boolean doInBackground(Void... arg0) {
//There is no point in reconnecting an already established connection. So abort, if we do
if (connection.isConnected())
return false;
//We are currently in "connection" phase, so no requests should be made while we are connecting.
isconnecting = true;
if (isToasted)
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
Toast.makeText(service, "connecting....", Toast.LENGTH_LONG).show();
}
});
if (debug) Log.d(TAG, "connecting....");
try {
connection.connect();
/**
* Set delivery receipt for every Message, so that we can confirm if message
* has been received on other end.
*
* #NOTE: This feature is not yet implemented in this example. Maybe, I'll add it later on.
* Feel free to pull request to add one.
*
* Read more about this: http://xmpp.org/extensions/xep-0184.html
**/
DeliveryReceiptManager dm = DeliveryReceiptManager.getInstanceFor(connection);
dm.setAutoReceiptMode(DeliveryReceiptManager.AutoReceiptMode.always);
dm.addReceiptReceivedListener(new ReceiptReceivedListener() {
#Override
public void onReceiptReceived(Jid fromJid, Jid toJid, String receiptId, Stanza receipt) {
}
// #Override
// public void onReceiptReceived(final String fromid,
// final String toid, final String msgid,
// final Stanza packet) {
//
// }
});
connected = true;
} catch (IOException e) {
service.onConnectionClosed();
if (isToasted)
new Handler(Looper.getMainLooper())
.post(new Runnable() {
#Override
public void run() {
Toast.makeText(service, "IOException: ", Toast.LENGTH_SHORT).show();
}
});
if (debug) Log.e(TAG, "IOException: " + e.getMessage());
} catch (SmackException e) {
service.onConnectionClosed();
if (isToasted)
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
Toast.makeText(service, "SMACKException: ", Toast.LENGTH_SHORT).show();
}
});
if (debug) Log.e(TAG, "SMACKException: " + e.getMessage());
} catch (XMPPException e) {
service.onConnectionClosed();
if (isToasted)
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
Toast.makeText(service, "XMPPException: ", Toast.LENGTH_SHORT).show();
}
});
if (debug) Log.e(TAG, "XMPPException: " + e.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
}
//Our "connection" phase is now complete. We can tell others to make requests from now on.
return isconnecting = false;
}
};
connectionThread.execute();
}
//Signup to server
public void Signup(SignupModel signupModel) {
XMPPError.Condition condition = null;
boolean errors = false;
String errorMessage = "";
String mUsername = signupModel.getUsername();
String mPassword = signupModel.getPassword();
boolean isPasswordValid = signupModel.checkPassword();
boolean areFieldsValid = signupModel.validateFields();
if (!isPasswordValid) {
errors = true;
errorMessage = Constants.SIGNUP_ERR_INVALIDPASS;
}
if (!areFieldsValid) {
errors = true;
errorMessage = Constants.SIGNUP_ERR_FIELDERR;
}
if (errors) {
service.onSignupFailed(errorMessage);
return;
}
new Thread(new Runnable() {
#Override
public void run() {
if (!connected && !isconnecting) connect();
}
}).start();
try {
// final AccountManager accountManager = AccountManager.getInstance(connection);
//
//
// accountManager.createAccount(Localpart.from(mUsername), mPassword);
AccountManager accountManager = AccountManager.getInstance(connection);
accountManager.sensitiveOperationOverInsecureConnection(true);
accountManager.createAccount(Localpart.from(mUsername), mPassword);
} catch (XMPPException | SmackException e) {
e.printStackTrace();
if (debug) Log.e(TAG, "Username: " + mUsername + ",Password: " + mPassword);
if (e instanceof XMPPException.XMPPErrorException) {
condition = ((XMPPException.XMPPErrorException) e).getXMPPError().getCondition();
}
if (condition == null) {
condition = XMPPError.Condition.internal_server_error;
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (XmppStringprepException e) {
e.printStackTrace();
}
if (condition == null) {
service.onSignupSuccess();
} else {
switch (condition) {
case conflict:
errorMessage = Constants.SIGNUP_ERR_CONFLICT;
break;
case internal_server_error:
errorMessage = Constants.SIGNUP_ERR_SERVER_ERR;
break;
default:
errorMessage = condition.toString();
break;
}
service.onSignupFailed(errorMessage);
}
}
//Login to server
public void login() {
try {
new Thread(new Runnable() {
#Override
public void run() {
if (!connected && !isconnecting) connect();
}
}).start();
if (debug) Log.i(TAG, "User " + userId + userPassword);
connection.login(userId, userPassword);
if (debug) Log.i(TAG, "Yey! We're logged in to the Xmpp server!");
service.onLoggedIn();
} catch (XMPPException | SmackException | IOException e) {
service.onLoginFailed();
if (debug) e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
When I run this code I an receiving the following exception (when tried to run from emulator)
SMACKException: The following addresses failed: '10.0.2.2:5222' failed because: /10.0.2.2 exception: java.net.SocketTimeoutException: failed to connect to /10.0.2.2 (port 5222) from /192.168.232.2 (port 34922) after 30000ms
when tried to run from my device I kept HOST_NAME as my pc's ip address, but then I am receiving the following error
E/XMPPHandler: SMACKException: The following addresses failed: '192.168.1.152:5222' failed because: /192.168.1.152 exception: java.net.SocketTimeoutException: connect timed out
When I installed ejabberd on my pc then I received the following Access Control List page
.. Should I have to change something?? Or missing something?
I had this problem too. I don't know why, but ejabberd server on windows does not works properly. I tried installing server on linux and mac, and it's working correctly. But on windows version always gets SocketTimeoutException.
I had similar problem. I have openfire serevr installed in windows 10.
My code was
DomainBareJid xmppServiceDomain = JidCreate.domainBareFrom("desktop-urvfr83");
//DomainBareJid xmppServiceDomain = JidCreate.domainBareFrom("192.168.1.3");
InetAddress addr = InetAddress.getByName("192.168.1.3");
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setUsernameAndPassword("alir", "111111")
.setHostAddress(addr)
.setResource("phonn")
.setXmppDomain(xmppServiceDomain)
.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
.setPort(5222)
.build();
but it could not connect. When I disabled the firewall it worked correctly and connected.
I'm trying to connect my device to another one via Bluetooth, but when I select the device I want to connect with, I get an IOException saying
read failed, socket might closed or timeout, read ret: -1
Just to illustrate how my app works, I have a RecyclerView populated with the devices my Bluetooth scan has found, then when I click an item the app is supposed to connect with that device.
Below is my the code for my connection thread:
private val MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
private lateinit var device: BluetoothDevice
private lateinit var onDeviceActionListener: OnDeviceActionListener
private lateinit var socket: BluetoothSocket
fun init(device: BluetoothDevice,
onDeviceActionListener: OnDeviceActionListener): ConnectionThread {
this.device = device
this.onDeviceActionListener = onDeviceActionListener
try {
socket = device.createRfcommSocketToServiceRecord(MY_UUID)
} catch (e: IOException) {
Log.e(TAG, "Error creating socket", e)
}
return this
}
override fun run() {
try {
socket.connect()
} catch (openException: IOException) {
Log.e(TAG, "Error opening connection. Trying to close...", openException)
try {
socket.close()
} catch (closeException: IOException) {
Log.e(TAG, "Error closing socket", closeException)
}
return
}
onDeviceActionListener.onDeviceConnect(device)
}
My guess is there is something wrong with my UUID. I've tried some other values but still didn't work.
Any help will be much appreciated.
Well, I don't see exactly what you are doing wrong here. However, I have done quite a bit of Bluetooth work. More recently just focused in BLE. You should be able to discover your nearby BT devices and see their UUIDs.
I have written a helper class about 3 years ago so it's a little old, but should be mostly the same code. Happy to share it with you if it helps.
public class BluetoothConnector {
private static final String TAG = Globals.SEARCH_STRING + BluetoothConnector.class.getSimpleName();
private static final String DEFAULT_SERVER_NAME_FOR_APP = "tn_bt_default_server";
private static final int DEFAULT_DISCOVERABLE_DURATION_MS = 30000;
private static final UUID DEFAULT_UUID = UUID.fromString("6534c201-039c-4e4f-89f9-5ca8cfeb9667");
public static final int ENABLE_DISCOVER_INTENT = 1002;
protected boolean mIsToastEnabled = false; //Access from calling class to enable toasting of progress to screen if necessary
private Handler mUIHandler;
private static ServerSocketThread mServerSocketThread;
private static ClientSocketThread mClientSocketThread;
private ManageConnectionThread mManageConnectionThread;
private Context mContext;
private IBluetoothDataListener mBluetoothDataListener;
public final Object ServerSocketLock = new Object();
public final Object ClientSocketLock = new Object();
public final Object ManageConnectionLock = new Object();
public BluetoothConnector(Context context, IBluetoothDataListener listener){
this(context, new Handler(Looper.getMainLooper()), listener);
}
public BluetoothConnector(Context context, Handler UIHandler, IBluetoothDataListener listener){
Log.v(TAG, "BluetoothConnector(context=" + context + ", Handler=" + UIHandler.getClass().getSimpleName() + ", IBluetoothDataListener=" + listener.getClass().getSimpleName());
mContext = context;
mUIHandler = UIHandler;
mBluetoothDataListener = listener;
}
public void makeThisDeviceDiscoverable(Activity callingActivity){
makeThisDeviceDiscoverable(callingActivity, BluetoothAdapter.getDefaultAdapter(), DEFAULT_DISCOVERABLE_DURATION_MS);
}
public void makeThisDeviceDiscoverable(Activity callingActivity, BluetoothAdapter adapter){
makeThisDeviceDiscoverable(callingActivity, adapter, DEFAULT_DISCOVERABLE_DURATION_MS);
}
public void makeThisDeviceDiscoverable(Activity callingActivity, int durationInMs){
makeThisDeviceDiscoverable(callingActivity, BluetoothAdapter.getDefaultAdapter(), durationInMs);
}
public void makeThisDeviceDiscoverable(Activity callingActivity, BluetoothAdapter adapter, int durationInMs) {
Log.v(TAG, "makeThisDeviceDiscoverable(callingActivity=" + callingActivity.getClass().getSimpleName() + ", BluetoothAdapter=" + (adapter == null ? "null" : adapter.getName()) + ", duration=" + String.valueOf(durationInMs));
if(adapter == null){
Log.v(TAG, "adapter is null");
}else if(adapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Log.v(TAG, "Launching Activity to request Discoverable Permission");
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, durationInMs);
callingActivity.startActivityForResult(discoverableIntent, ENABLE_DISCOVER_INTENT);
}else{
Log.v(TAG, "adapter is already in SCAN MODE");
}
}
public void awaitConnectionFromDevice(){
awaitConnectionFromDevice(DEFAULT_UUID, BluetoothAdapter.getDefaultAdapter());
}
public void awaitConnectionFromDevice(UUID commonKey){
awaitConnectionFromDevice(commonKey, BluetoothAdapter.getDefaultAdapter());
}
public void awaitConnectionFromDevice(BluetoothAdapter adapter){
awaitConnectionFromDevice(DEFAULT_UUID, adapter);
}
public void awaitConnectionFromDevice(UUID commonKey, BluetoothAdapter adapter){
Log.v(TAG, "awaitConnectionFromDevice for UUID: " + String.valueOf(commonKey) + ", BluetoothAdapter=" + (adapter == null ? "null" : adapter.getName()));
cancelDiscovery();
synchronized (ServerSocketLock){
if(mServerSocketThread != null){
Log.v(TAG, "Server Socket Thread was not null so canceling current Thread");
mServerSocketThread.cancel();
}
Log.v(TAG, "Attempting to Start new ServerThread");
mServerSocketThread = new ServerSocketThread(commonKey, adapter);
mServerSocketThread.start();
}
}
public void cancelAwaitingConnectionFromDevice(){
Log.v(TAG, "cancelAwaitingConnectionFromDevice");
synchronized (ServerSocketLock){
if(mServerSocketThread != null){
mServerSocketThread.cancel();
mServerSocketThread = null;
Log.v(TAG, "canceling Server Socket Thread");
}else{
Log.v(TAG, "Server Socket null, so not canceling");
}
}
}
public void startDiscovery() {
startDiscovery(BluetoothAdapter.getDefaultAdapter());
}
public void startDiscovery(BluetoothAdapter adapter){
Log.v(TAG, "startDiscovery to find list of devices in range");
adapter.startDiscovery();
}
public void cancelDiscovery() {
cancelDiscovery(BluetoothAdapter.getDefaultAdapter());
}
public void cancelDiscovery(BluetoothAdapter adapter){
Log.v(TAG, "cancelDiscovery");
adapter.cancelDiscovery();
}
public void connectToDevice(BluetoothDevice device){
connectToDevice(device, DEFAULT_UUID);
}
public void connectToDevice(BluetoothDevice device, UUID commonKey){
Log.v(TAG, "connectToDevice(BluetoothDevice=" + (device == null ? "null" : device.getName()) + ", UUID=" + String.valueOf(commonKey));
synchronized (ClientSocketLock){
if(mClientSocketThread != null){
Log.v(TAG, "Client Socket Thread was not null so canceling current Thread");
mClientSocketThread.cancel();
}else{
Log.v(TAG, "Client Socket Thread is NULL so not canceling");
}
Log.v(TAG, "ClientSocketThread Starting");
mClientSocketThread = new ClientSocketThread(device, commonKey);
mClientSocketThread.start();
}
}
public BluetoothDevice getBluetoothDeviceByMac(String mac){
Log.v(TAG, "getBluetoothDeviceByMac(mac=" + mac);
return getBluetoothDeviceByMac(mac, BluetoothAdapter.getDefaultAdapter());
}
public BluetoothDevice getBluetoothDeviceByMac(String mac, BluetoothAdapter adapter) {
Log.v(TAG, "getBluetoothDeviceByMac(mac=" + mac + ", BluetoothAdapter=" + (adapter == null ? "null" : adapter.getName()));
return adapter.getRemoteDevice(mac);
}
public ArrayList<KeyValueModel> getPairedDevices(){
return getPairedDevices(BluetoothAdapter.getDefaultAdapter());
}
public ArrayList<KeyValueModel> getPairedDevices(BluetoothAdapter adapter){
ArrayList<KeyValueModel> bondedDevices = new ArrayList<KeyValueModel>();
Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
Log.v(TAG, "getPairedDevices Found " + pairedDevices.size() + " number of paired devices");
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
bondedDevices.add(new KeyValueModel(device.getAddress(), device.getName()));
}
}
return bondedDevices;
}
public static void unpairDevice(BluetoothDevice device){
Log.v(TAG, "unpairDevice");
try{
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
}catch (Exception ex){
Log.e(TAG, "Error Unpairing Device: " + ex.getMessage());
}
}
public boolean sendDataToConnectedDevice(byte[] data){
Log.v(TAG, "sendDataToConnectedDevice");
synchronized (ManageConnectionLock){
mManageConnectionThread.write(data);
return true;
}
}
public void setBluetoothDataListener(IBluetoothDataListener listener){
mBluetoothDataListener = listener;
}
public boolean getIsConnected(){
synchronized (ManageConnectionLock) {
return mManageConnectionThread != null && mManageConnectionThread.isAlive();
}
}
private void startManageConnectionThread(BluetoothSocket socket){
Log.v(TAG, "startManageConnectionThread for Socket: " + (socket == null ? "null" : socket.getClass().getSimpleName()));
synchronized (ManageConnectionLock) {
mManageConnectionThread = new ManageConnectionThread(socket);
mManageConnectionThread.start();
}
}
private void handleDataReceivedFromConnectedDevice(final byte[] bytes){
Log.v(TAG, "handleDataReceivedFromConnectedDevice");
Log.v(TAG, "bytes to Listener: " + new String(bytes));
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if (mBluetoothDataListener != null) {
mBluetoothDataListener.onReceivedPayloadFromConnectedDevice(bytes);
}
}
});
}else{
Log.v(TAG, "UIHandler was null so skipped sending payload to listener");
}
}
private void handleConnected(){
Log.e(TAG, "handleConnected");
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onConnectedToTargetDevice();
}
}
});
}else{
Log.v(TAG, "UIHandler was null so skipped sending payload to listener");
}
}
private void handleDisconnected(){
Log.e(TAG, "handleDisconnected");
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onDisconnectedFromTargetDevice();
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleFailedToConnectAsServer(final Exception ex){
Log.e(TAG, "handleFailedToConnectAsServer ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onFailedToReceiveConnectionFromTargetDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleFailedToConnectAsClient(final Exception ex){
Log.e(TAG, "handleFailedToConnectAsClient ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onFailedToConnectToTargetDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleErrorInRetrievingData(final Exception ex){
Log.e(TAG, "handleErrorInRetrievingData ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onErrorReceivingPayloadFromConnectedDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleFailedToSendDataToConnectedDevice(final Exception ex){
Log.e(TAG, "handleFailedToSendDataToConnectedDevice ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
#Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onFailedToSendDataToConnectedDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void toastMessage(final String value){
if(!mIsToastEnabled || mUIHandler == null) {
return;
}
mUIHandler.post(new Runnable() {
#Override
public void run() {
try{
Toast.makeText(mContext, value, Toast.LENGTH_SHORT).show();
}catch(Exception ex){
Log.v(TAG, "Error Toasting, possibly bad handler, or context: " + ex.getMessage());
}
}
});
}
private class ServerSocketThread extends Thread{
private final String TAG = Globals.SEARCH_STRING + ServerSocketThread.class.getSimpleName();
private final BluetoothServerSocket mServerSocket;
public ServerSocketThread(UUID commonKey, BluetoothAdapter adapter) {
Log.v(TAG, "ServerSocketThread Constructor");
BluetoothServerSocket tmp = null;
try {
Log.v(TAG, "listening for RFComas Server: " + DEFAULT_SERVER_NAME_FOR_APP + ", and commonKey: " + String.valueOf(commonKey));
// MY_UUID is the app's UUID string, also used by the client code
tmp = adapter.listenUsingRfcommWithServiceRecord(DEFAULT_SERVER_NAME_FOR_APP, commonKey);
toastMessage("Listening for RFComm As Server on UUID: " + String.valueOf(commonKey));
} catch (IOException e) {
Log.e(TAG, "Error creating ServerSocket: " + e.getMessage());
toastMessage("Error Creating ServerSocket: " + e.getMessage());
}
mServerSocket = tmp;
}
public void run() {
Log.v(TAG, "ServerSocket run");
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (mServerSocket != null) {
try {
Log.v(TAG, "ServerSocket.accept()");
toastMessage("ServerSocket.accept()");
//Waits for Client Connection to pass Socket, then we close down
socket = mServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "ServerSocket.accept() Error: " + e.getMessage());
toastMessage("ServerSocket.accept() Error: " + e.getMessage());
handleFailedToConnectAsServer(e);
break;
}
// If a connection was accepted we don't need to keep server listening, so close unless multiple client/server connections is desired
if (socket != null) {
try{
Log.v(TAG, "ServerSocket Accepted Client Socket, Begin Listening Connect Thread");
toastMessage("ServerSocket Accepted Client Socket, Begin Listening Connect Thread");
// Do work to manage the connection (in a separate thread)
startManageConnectionThread(socket);
//mServerSocket.close();
}catch(Exception ex){
Log.e(TAG, "Exception closing Server Socket");
}
//break; //Add in Break if you want to shut down listening for connections
}else{
Log.v(TAG, "Socket wasn't accepted");
toastMessage("Socket wasn't accepted");
handleFailedToConnectAsServer(new Exception("Socket is Null"));
}
}
Log.v(TAG, "Exiting Server Accept Thread");
}
public void cancel() {
try {
Log.v(TAG, "ServerSocketThread Canceled");
mServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "ServerSocketThread Error: " + e.getMessage());
}
}
}
private class ClientSocketThread extends Thread{
private BluetoothSocket mSocket;
private final BluetoothDevice mDevice;
public ClientSocketThread(BluetoothDevice device, UUID commonKey) {
Log.v(TAG, "ClientSocketThread Constructor");
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
Log.v(TAG, "Client creating RFComm Socket to Server with UUID: " + String.valueOf(commonKey));
toastMessage("Client creating RFComm Socket to Server with UUID: " + String.valueOf(commonKey));
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(commonKey);
} catch (IOException e) {
Log.e(TAG, "Error creating Client Socket: " + e.getMessage());
toastMessage("Creating Socket Exception: " + e.getMessage());
handleFailedToConnectAsClient(e);
}
mSocket = tmp;
}
public void run() {
try {
if(mSocket == null){
Log.e(TAG, "Error Client Socket is Null, Canceling Client Thread");
return;
}
Log.v(TAG, "Client Connecting");
// Connect to the server, or timeout eventually
toastMessage("Client Connecting");
mSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and try the fallback method of reflection with port to connect
try {
Log.e("", "trying fallback...");
toastMessage("Client Connection Failed Exception: " + connectException.getMessage());
mSocket = (BluetoothSocket) mDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class}).invoke(mDevice, 1);
toastMessage("Client Connect Again Attempt 2, but with fall back Reflection and port");
Log.v(TAG, "Client Connect Again Attempt 2, but with fall back Reflection and port");
mSocket.connect();
Log.e("", "Connected");
toastMessage("Client Connected");
} catch (Exception ex) {
Log.e("", "Couldn't establish Bluetooth connection!");
toastMessage("Client Couldn't Establish Connection to Server: " + ex.getMessage());
handleFailedToConnectAsClient(ex);
return;
}
}
// Do work to manage the connection (in a separate thread)
startManageConnectionThread(mSocket);
}
public void cancel() {
try {
Log.v(TAG, "Client Socket cancel");
mSocket.close();
} catch (IOException e) {
Log.e(TAG, "Error Closing Socket");
}
}
}
private class ManageConnectionThread extends Thread {
/////////////
// MEMBERS //
/////////////
private final String TAG = Globals.SEARCH_STRING + ManageConnectionThread.class.getSimpleName();
private final BluetoothSocket mSocket;
private final InputStream mInStream;
private final OutputStream mOutStream;
//////////////////
// CONSTRUCTOR //
//////////////////
public ManageConnectionThread(BluetoothSocket socket) {
mSocket = socket;
handleConnected();
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
try {
Log.v(TAG, "ManageConnectionThread Constructor");
Log.v(TAG, "Connected to Socket = " + String.valueOf(socket.isConnected()));
toastMessage("Listening for input or output Stream");
Log.v(TAG, "Get InputStream");
tmpIn = socket.getInputStream();
Log.v(TAG, "Get OutputStream");
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "Error getting Socket Streams: " + e.getMessage());
toastMessage("Connect Thread: Error: " + e.getMessage());
handleErrorInRetrievingData(e);
}
mInStream = tmpIn;
mOutStream = tmpOut;
}
///////////////
// OVERRIDES //
///////////////
public void run() {
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
byte[] data = new byte[16384];
int nRead;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
while ((nRead = mInStream.read(data, 0, data.length)) != -1) {
//Log.v(TAG, "bytes Read: " + String.valueOf(nRead));
buffer.write(data, 0, nRead);
//TODO Find better way to find End Of Message rather than looking for }
String temp = new String(buffer.toByteArray());
//Log.v(TAG, "current Data: " + temp);
if(temp.contains("}")){
Log.v(TAG, "bytes reading complete");
handleDataReceivedFromConnectedDevice(buffer.toByteArray());
buffer.flush();
buffer = new ByteArrayOutputStream();
}else{
Log.v(TAG, "More bytes Available");
}
}
} catch (IOException e) {
Log.e(TAG, "Error reading inputStream");
handleErrorInRetrievingData(e);
break;
}
}
Log.v(TAG, "Exiting Managed Connection Thread");
handleDisconnected();
}
/////////////
// METHODS //
/////////////
public void write(byte[] bytes) {
try {
Log.v(TAG, "ManageConnectionThread write(bytes)");
mOutStream.write(bytes);
} catch (IOException e) {
Log.e(TAG, "Error Writing Stream: " + e.getMessage());
handleFailedToSendDataToConnectedDevice(e);
}
}
public void cancel() {
try {
Log.v(TAG, "ManageConnectionThread cancel");
handleDisconnected();
mSocket.close();
} catch (IOException e) {
Log.e(TAG, "Error Closing BluetoothSocket: " + e.getMessage());
}
}
}
public interface IBluetoothDataListener{
//////////////////////
// OVERRIDE METHODS //
//////////////////////
void onReceivedPayloadFromConnectedDevice(byte[] payload);
void onErrorReceivingPayloadFromConnectedDevice(Exception ex);
void onFailedToConnectToTargetDevice(Exception ex);
void onFailedToReceiveConnectionFromTargetDevice(Exception ex);
void onFailedToSendDataToConnectedDevice(Exception ex);
void onConnectedToTargetDevice();
void onDisconnectedFromTargetDevice();
}
}
Then of course you will want to make sure you have your broadcast receivers setup:
<receiver
android:name=".receivers.BluetoothChangedReceiver"
android:enabled="true" >
<intent-filter>
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
<action android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
<action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
<action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
<action android:name="android.bluetooth.device.action.FOUND" />
<action android:name="android.bluetooth.device.action.DISAPPEARED" />
</intent-filter>
</receiver>
<receiver
android:name=".receivers.BluetoothDeviceReceiver"
android:enabled="true" >
<intent-filter>
<action android:name="android.bluetooth.device.action.FOUND" />
<action android:name="android.bluetooth.device.action.DISAPPEARED" />
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
<action android:name="android.bluetooth.device.action.ACTION_ACL_DISCONNECT_REQUESTED" />
<action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED" />
<action android:name="android.bluetooth.device.action.UUID" />
</intent-filter>
</receiver>
I have been trying to incorporate MQTT into one of my Android applications. I originally had it working within an activity and have since tried to move it to a service to run in the background. Im able to connect and send messages from the service but Im not able to receive messages. My service implements MqttCallback and overrides the messageReceived() function but it never seems to get called. Can someone help me understand why the callback is not firing?
package com.example.test;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttToken;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
public class MqttService extends Service implements MqttCallback{
final static String MY_ACTION = "MqttService";
String username;
//MQTT Variables
MqttClient mySubClient;
MqttConnectOptions connOpt;
String BROKER_URL;
String PUB_TOPIC;
String SUB_TOPIC;
String PUB_CLIENT_ID;
String SUB_CLIENT_ID;
Boolean MqttConnState;
Boolean IsRunning;
static final String TOPIC_BASE = MyProperties.TOPIC_BASE;
static final String PI_BROKER_URL = MyProperties.PI_BROKER_URL; //no encryption
static final String PI_SSL_BROKER_URL = MyProperties.PI_SSL_BROKER_URL; //ssl enabled
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("log_mqtt_service", "Service onStartCommand entered");
//Get session id from previous activity
Bundle extras = intent.getExtras();
if (extras != null) {
username = extras.getString("SESSION_ID");
}
//set mqtt vars
BROKER_URL = PI_BROKER_URL;
PUB_CLIENT_ID = "My-Pub-" + username;
SUB_CLIENT_ID = "My-Sub-" + username;
SUB_TOPIC = TOPIC_BASE + username;
PUB_TOPIC = TOPIC_BASE + "test";
IsRunning = false;
MqttThread myThread = new MqttThread();
myThread.start();
return super.onStartCommand(intent, flags, startId);
}
public class MqttThread extends Thread{
#Override
public void run() {
if(IsRunning==false)
{
IsRunning = true;
connMqtt();
}
while(IsRunning){
//keep thread running
Log.i("log_mqtt_thread_send", "thread sending mqtt message");
sendMsg(PUB_TOPIC, "0x0001", "Message from " + PUB_CLIENT_ID);
}
Log.i("log_mqtt_thread", "thread stopped");
}
}
#Override
public void onDestroy()
{
super.onDestroy();
try {
mySubClient.disconnect();
} catch (MqttException e) {
Log.e ("log_mqtt_disconnect",e.toString());
}
}
#Override
public void connectionLost(Throwable cause) {
// TODO Auto-generated method stub
}
#Override
public void messageArrived(String topic, MqttMessage message)
throws Exception {
String msgStr;
msgStr = new String(message.getPayload());
Log.i("log_mqtt_Rx", "-------------------------------------------------");
Log.i("log_mqtt_Rx", "| Topic: " + topic.toString());
Log.i("log_mqtt_Rx", "| Message: " + msgStr);
Log.i("log_mqtt_Rx", "-------------------------------------------------");
Intent intent = new Intent();
intent.setAction(MY_ACTION);
intent.putExtra("RX_MESSAGE", msgStr);
sendBroadcast(intent);
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
// TODO Auto-generated method stub
}
// ------------------------------------------------------------------------
// Function to make mqtt connection and subscribe
// ------------------------------------------------------------------------
int connMqtt()
{
int result=0;
MqttClientPersistence persistence = new MqttDefaultFilePersistence(getBaseContext().getApplicationInfo().dataDir);
try{
Log.i("log_mqtt","mqtt start");
try{
// setup MQTT Client
connOpt = new MqttConnectOptions();
connOpt.setCleanSession(true);
connOpt.setKeepAliveInterval(30);
Log.i("log_mqtt","connOpt vars setup");
}catch(Exception e){
result = -1;
Log.e ("log_mqtt",e.toString());
}
// Connect to Broker for Subscriber connection
try {
mySubClient = new MqttClient(BROKER_URL, SUB_CLIENT_ID, persistence);
Log.i("log_mqtt_conn","create mqttClient");
mySubClient.connect(connOpt);
Log.i("log_mqtt_conn","MQTT client connected to " + BROKER_URL);
} catch (MqttException e) {
result = -2;
Log.i ("log_mqtt_conn","BROKER: " + BROKER_URL);
Log.i ("log_mqtt_conn","SUB_CLIENT_ID: " + SUB_CLIENT_ID);
Log.e ("log_mqtt_conn",e.toString());
}
try {
int subQoS = 0;
mySubClient.subscribe(SUB_TOPIC, subQoS);
Log.i("log_mqtt_sub","mqtt client subscribed to \"" + SUB_TOPIC + "\"");
} catch (Exception e) {
result = -3;
Log.e ("log_mqtt_sub",e.toString());
}
}catch(Exception e){
result = -4;
Log.e ("log_mqtt",e.toString());
}
return result;
}
// ------------------------------------------------------------------------
// Function to send mqtt message to a topic
// ------------------------------------------------------------------------
public void sendMsg(String sendTopic, String msgid, String msg)
{
String timeStamp = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
String pubMsg = "{\"msgid\":\"" + msgid + "\",\"time\":\"" + timeStamp + "\", \"message\":\"" + msg + "\"}";
int pubQoS = 0;
MqttMessage message = new MqttMessage(pubMsg.getBytes());
message.setQos(pubQoS);
message.setRetained(false);
//Topic for publisher
MqttTopic pubTopic = mySubClient.getTopic(sendTopic);
// Publish the message
Log.i("log_mqtt_send","Publishing to topic \"" + pubTopic + "\" qos " + pubQoS);
Log.i("log_mqtt_msg", pubMsg);
MqttDeliveryToken token = null;
try {
// Publish message to broker then disconnect
token = pubTopic.publish(message);
// Wait until the message has been delivered to the broker
token.waitForCompletion();
Thread.sleep(1000);
} catch (Exception ex) {
Log.e("log_mqtt_error",ex.toString());
}
}
}
You have to add Callback on your 'mySubClient.setCallBack' then only your callback methods will get called.
Bit of an odd one, but I'm having issues with monitoring the connection status of an XMPP connection. I'm using Smack (3.4.1) and Openfire (3.8.2)
Openfire reports that the client has disconnected. As does my remote client BOT, which is responsible for middle-man server requests. However, the devices themselves still believe they're connected. So something has abruptly killed the connection.
I can replicate the scenario, by closing the client connection manually via Openfire admin console. After doing this, the client still reports that it's connected, with the following details:
04-15 09:48:23.349: D/BXC(18166): Checking Xmpp connection status
04-15 09:48:23.349: I/BXC(18166): Xmpp Connection still valid
04-15 09:48:23.349: D/BXC(18166): isNull=false
04-15 09:48:23.349: D/BXC(18166): isConnected()=true
04-15 09:48:23.349: D/BXC(18166): isAuthenticated()=true
04-15 09:48:23.349: D/BXC(18166): xmppConnectorRunning=true
04-15 09:48:23.349: D/BXC(18166): socketIsClosed()=false
04-15 09:48:23.349: D/BXC(18166): Are we logged in?
04-15 09:48:23.349: I/BXC(18166): Yes - Logged in.
From the code:
Log.d("BXC", "isNull=" + (xConnection == null));
Log.d("BXC", "isConnected()=" + xConnection.isConnected());
Log.d("BXC", "isAuthenticated()=" + xConnection.isAuthenticated());
Log.d("BXC", "xmppConnectorRunning=" + Globals.backgroundXmppConnectorRunning);
Log.d("BXC", "socketIsClosed()=" + xConnection.isSocketClosed());
The trouble is, that the connection will remain in this state until I restart the app. What is the best value/data/variable/method call to use to check the actual status of an XMPP connection?
Here's my BackgroundXmppConnector Service code (I've quite possibly borked something along the way, but I can't see it):
package com.goosesys.gaggle.services;
import java.util.Collection;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import com.google.gson.Gson;
import com.goosesys.gaggle.Globals;
import com.goosesys.gaggle.application.AppSettings;
import com.goosesys.gaggle.application.Utility;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
public class BackgroundXmppConnector extends Service
{
private ConnectionConfiguration acc;
private XMPPConnection xConnection;
private final IBinder mBinder = new XmppBinder();
private final Handler mHandler = new Handler();
private static int mInterval = (1 * 60 * 1000);
private static boolean bConnecting = false;
private static final Object connectLock = new Object();
private final Runnable checkConnection = new Runnable()
{
#Override
public void run()
{
checkConnectionStatus();
}
};
#Override
public void onCreate()
{
Log.i("BXC", "BackgroundXmppConnector Service has been created");
// Checks the connection state every 1 minute //
mHandler.postDelayed(checkConnection, mInterval);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// SmackAndroid.init(getApplicationContext());
Log.d("BXC", "Xmpp Connector Started");
if(xConnection == null)
{
checkConnectionStatus();
}
return Service.START_STICKY;
}
private void setupConnection()
{
try
{
acc = new ConnectionConfiguration(AppSettings.XMPP_SERVER_HOST,
AppSettings.XMPP_SERVER_PORT);
acc.setSecurityMode(SecurityMode.disabled);
acc.setSASLAuthenticationEnabled(false);
acc.setReconnectionAllowed(true);
xConnection = new XMPPConnection(acc);
xConnection.addConnectionListener(new ConnectionListener()
{
#Override
public void connectionClosed()
{
Log.e("BXC", "Xmpp connection closed");
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
}
#Override
public void connectionClosedOnError(Exception e)
{
Log.e("BXC", "Xmpp connection closed with error: " + e);
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
}
#Override
public void reconnectingIn(int seconds)
{
Log.i("BXC", "Xmpp connection, reconnecting in " + seconds + " seconds");
bConnecting = true;
}
#Override
public void reconnectionFailed(Exception e)
{
Log.e("BXC", "Xmpp reconnection failed: " + e);
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
}
#Override
public void reconnectionSuccessful()
{
Log.i("BXC", "Xmpp reconnected successfully");
Globals.backgroundXmppConnectorRunning = true;
bConnecting = false;
}
});
}
catch (Exception e)
{
Log.e("BXC", e.getMessage());
}
finally
{
// Schedule another check in 1 minute //
mHandler.postDelayed(checkConnection, mInterval);
}
if(xConnection.isAuthenticated() == false)
new LoginTask().execute();
}
public boolean sendMessage(Intent intent)
{
if(xConnection != null && xConnection.isConnected())
{
String jsonObject;
Bundle extras = intent.getExtras();
if(extras != null)
{
jsonObject = extras.getString("MESSAGEDATA");
Message m = new Gson().fromJson(jsonObject, Message.class);
if(m != null)
{
sendMessage(m);
}
else
{
Log.e("BXC", "Message to send was/is null. Can't send.");
}
m = null;
jsonObject = null;
extras = null;
}
Log.i("BXC", "Sending Xmpp Packet");
return true;
}
return false;
}
/*
* Sends message to xmpp server - message packet in form of
*
* --------------------MESSAGE PACKET-------------------------
* TO
* -----------------------
* FROM
* -----------------------
* BODY
* TRANSACTION-------------------------------------------
* MessageType
* --------------------------------------------------
* TransactionObject
*/
private void sendMessage(Message m)
{
try
{
Log.d("BXC", "Sending transaction message to Xmpp Server");
xConnection.sendPacket(m);
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
public boolean isConnected()
{
return xConnection.isConnected();
}
private void checkConnectionStatus()
{
Log.d("BXC", "Checking Xmpp connection status");
if(!bConnecting)
{
// if connection object is null - re-create
if(xConnection == null) // || xConnection.isConnected() == false
{
// The connection has stalled for some reason - attempt a reconnect
Log.e("BXC", "No connection. Attempting to connect.");
setupConnection();
}
else
{
Log.i("BXC", "Xmpp Connection still valid");
}
Log.d("BXC", "isNull=" + (xConnection == null));
Log.d("BXC", "isConnected()=" + xConnection.isConnected());
Log.d("BXC", "isAuthenticated()=" + xConnection.isAuthenticated());
Log.d("BXC", "xmppConnectorRunning=" + Globals.backgroundXmppConnectorRunning);
Log.d("BXC", "socketIsClosed()=" + xConnection.isSocketClosed());
if(xConnection != null && xConnection.isConnected() == true)
{
Log.d("BXC", "Are we logged in?");
if(xConnection.isAuthenticated() == false)
{
Log.e("BXC", "Nope. Logging in...");
new LoginTask().execute();
}
else
{
Log.i("BXC", "Yes - Logged in.");
}
}
else if(xConnection == null || xConnection.isConnected() == false || xConnection.isAuthenticated() == false)
{
Log.e("BXC", "Disconnected. Attempting reconnect");
new LoginTask().execute();
}
else
{
Log.e("BXC", "Couldn't log in because connection failed.");
}
}
else
{
Log.e("BXC", "Already checking.");
}
// Schedule again for 5 minutes.
mHandler.postDelayed(checkConnection, mInterval);
}
// BINDER ////////////////////////////////////////////////////////////////////////////////
#Override
public IBinder onBind(Intent intent)
{
return mBinder;
}
// INTERNAL CLASSES //////////////////////////////////////////////////////////////////////
public class XmppBinder extends Binder
{
public BackgroundXmppConnector getService(){
return BackgroundXmppConnector.this;
}
}
private class LoginTask extends AsyncTask<Void, Void, Void>
{
#Override
protected Void doInBackground(Void... params)
{
try
{
bConnecting = true;
synchronized(connectLock)
{
if(xConnection != null && (xConnection.isSocketClosed() || !xConnection.isConnected()))
{
xConnection.connect();
Log.i("BXC", "Login Credentials: " + Utility.getAndroidID(getApplicationContext()) + " / " + AppSettings.XMPP_KEYSTORE_PASSWORD);
xConnection.login(Utility.getAndroidID(getApplicationContext()), AppSettings.XMPP_KEYSTORE_PASSWORD);
xConnection.getChatManager().addChatListener(new ChatManagerListener(){
#Override
public void chatCreated(final Chat chat, boolean createdLocally)
{
if(!createdLocally)
{
// add chat listener //
chat.addMessageListener(new BackgroundMessageListener(getApplicationContext()));
}
}
});
Presence p = new Presence(Presence.Type.subscribe);
p.setStatus("Out and About");
xConnection.sendPacket(p);
Roster r = xConnection.getRoster();
r.setSubscriptionMode(SubscriptionMode.accept_all);
r.createEntry(AppSettings.BOT_NAME, "AbleBot", null);
r.addRosterListener(new RosterListener(){
#Override
public void entriesAdded(Collection<String> addresses)
{
for(String s : addresses)
{
Log.d("BXC", "Entries Added: " + s);
}
}
#Override
public void entriesDeleted(Collection<String> addresses)
{
for(String s : addresses)
{
Log.d("BXC", "Entries Deleted: " + s);
}
}
#Override
public void entriesUpdated(Collection<String> addresses)
{
for(String s : addresses)
{
Log.d("BXC", "Entries updated: " + s);
}
}
#Override
public void presenceChanged(Presence presence)
{
Log.d("BXC", "PresenceChanged: " + presence.getFrom());
}
});
}
}
}
catch(IllegalStateException ex)
{
Log.e("BXC", "IllegalStateException -->");
Globals.backgroundXmppConnectorRunning = false;
ex.printStackTrace();
}
catch(XMPPException ex)
{
Log.e("BXC", "XMPPException -->");
Globals.backgroundXmppConnectorRunning = false;
ex.printStackTrace();
}
catch(NullPointerException ex)
{
Log.e("BXC", "NullPointerException -->");
Globals.backgroundXmppConnectorRunning = false;
ex.printStackTrace();
}
catch(Exception ex)
{
Log.e("BXC", "Exception -->");
Globals.backgroundXmppConnectorRunning = false;
ex.printStackTrace();
}
return null;
//}
}
#Override
protected void onPostExecute(Void ignored)
{
if(xConnection != null)
{
if(xConnection.isConnected() && (!xConnection.isSocketClosed()))
{
Log.i("BXC", "Logged in to XMPP Server");
Globals.backgroundXmppConnectorRunning = true;
bConnecting = false;
}
else
{
Log.e("BXC", "Unable to log into XMPP Server.");
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
}
}
else
{
Log.e("BXC", "Xmpp Connection object is null");
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
}
}
}
}
There was no actual way around this as the internal variables/members within the XMPPConnector class, are always true (except for initial startup), regardless of connection state.
How I got around this was to create a global static variable, which I set true/false, depending on what the connection status is, defined by the callback methods, such as:
connectionClosed, connectionClosedOnError etc.
I am looking for and example of casting an image to chromecast in android. Oddly enough it doesn't seem like this is covered in the googlecast sample repositories. Does anyone have a simple implementation of this? I basically would like to click on an image in my app's photo gallery on my android device and have it cast to the screen.
One side question is, does the image need to be at a url? or is it possible to stream the image to the device? I appreciate the help in advance.
I've solved this without the CastCompanionLibrary, but based on google's CastHelloText-android sample. Basically what I did was:
encode an image into a base64 string and send it as a message to a custom receiver
modify the sample's receiver to receive a base64 string and set it as the image source.
upload and register my receiver and have the application use the generated application id
This is the code for the receiver:
<!DOCTYPE html>
<html>
<head>
<style>
img#androidImage {
height:auto;
width:100%;
}
</style>
<title>Cast Hello Text</title>
</head>
<body>
<img id="androidImage" src="" />
<script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
<script type="text/javascript">
window.onload = function() {
cast.receiver.logger.setLevelValue(0);
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
console.log('Starting Receiver Manager');
// handler for the 'ready' event
castReceiverManager.onReady = function(event) {
console.log('Received Ready event: ' + JSON.stringify(event.data));
window.castReceiverManager.setApplicationState("Application status is ready...");
};
// handler for 'senderconnected' event
castReceiverManager.onSenderConnected = function(event) {
console.log('Received Sender Connected event: ' + event.data);
console.log(window.castReceiverManager.getSender(event.data).userAgent);
};
// handler for 'senderdisconnected' event
castReceiverManager.onSenderDisconnected = function(event) {
console.log('Received Sender Disconnected event: ' + event.data);
if (window.castReceiverManager.getSenders().length == 0) {
window.close();
}
};
// handler for 'systemvolumechanged' event
castReceiverManager.onSystemVolumeChanged = function(event) {
console.log('Received System Volume Changed event: ' + event.data['level'] + ' ' +
event.data['muted']);
};
// create a CastMessageBus to handle messages for a custom namespace
window.messageBus =
window.castReceiverManager.getCastMessageBus(
'urn:x-cast:com.google.cast.sample.helloworld');
// handler for the CastMessageBus message event
window.messageBus.onMessage = function(event) {
console.log('Message recieved');
var obj = JSON.parse(event.data)
console.log('Message type: ' + obj.type);
if (obj.type == "text") {
console.log('Skipping message: ' + obj.data);
}
if (obj.type == "image") {
var source = 'data:image/png;base64,'.concat(obj.data)
displayImage(source);
}
// inform all senders on the CastMessageBus of the incoming message event
// sender message listener will be invoked
window.messageBus.send(event.senderId, event.data);
}
// initialize the CastReceiverManager with an application status message
window.castReceiverManager.start({statusText: "Application is starting"});
console.log('Receiver Manager started');
};
function displayImage(source) {
console.log('received image');
document.getElementById("androidImage").src=source;
window.castReceiverManager.setApplicationState('image source changed');
};
</script>
</body>
</html>
Below is the modified MainActivity.java code. Don't forget to modify the app_id in string.xml once your receiver application is registered.
2 notes:
The sent messages are wrapped in a JSON object so I can filter out
the text messages.
The ENCODED_IMAGE_STRING variable isn't defined in this
example, you'll have to find an image and convert it to a base64 string yourself.
MainActivity.java:
package com.example.casthelloworld;
import java.io.IOException;
import java.util.ArrayList;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
import com.google.android.gms.cast.Cast.MessageReceivedCallback;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
/**
* Main activity to send messages to the receiver.
*/
public class MainActivity extends ActionBarActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int REQUEST_CODE = 1;
private MediaRouter mMediaRouter;
private MediaRouteSelector mMediaRouteSelector;
private MediaRouter.Callback mMediaRouterCallback;
private CastDevice mSelectedDevice;
private GoogleApiClient mApiClient;
private Cast.Listener mCastListener;
private ConnectionCallbacks mConnectionCallbacks;
private ConnectionFailedListener mConnectionFailedListener;
private HelloWorldChannel mHelloWorldChannel;
private boolean mApplicationStarted;
private boolean mWaitingForReconnect;
private String mSessionId;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.setBackgroundDrawable(new ColorDrawable(
android.R.color.transparent));
// When the user clicks on the button, use Android voice recognition to
// get text
Button voiceButton = (Button) findViewById(R.id.voiceButton);
voiceButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startVoiceRecognitionActivity();
}
});
// When the user clicks on the button, use Android voice recognition to
// get text
Button yarrButton = (Button) findViewById(R.id.tmpButton);
yarrButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
castImage();
}
});
// Configure Cast device discovery
mMediaRouter = MediaRouter.getInstance(getApplicationContext());
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(
CastMediaControlIntent.categoryForCast(getResources()
.getString(R.string.app_id))).build();
mMediaRouterCallback = new MyMediaRouterCallback();
}
private void castImage()
{
Log.d(TAG, "castImage()");
String image_string = createJsonMessage(MessageType.image, ENCODED_IMAGE_STRING);
sendMessage(image_string);
}
/**
* Android voice recognition
*/
private void startVoiceRecognitionActivity() {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
getString(R.string.message_to_cast));
startActivityForResult(intent, REQUEST_CODE);
}
/*
* Handle the voice recognition response
*
* #see android.support.v4.app.FragmentActivity#onActivityResult(int, int,
* android.content.Intent)
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
ArrayList<String> matches = data
.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (matches.size() > 0) {
Log.d(TAG, matches.get(0));
String message = createJsonMessage(MessageType.text, matches.get(0));
sendMessage(message);
}
}
super.onActivityResult(requestCode, resultCode, data);
}
#Override
protected void onResume() {
super.onResume();
// Start media router discovery
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}
#Override
protected void onPause() {
if (isFinishing()) {
// End media router discovery
mMediaRouter.removeCallback(mMediaRouterCallback);
}
super.onPause();
}
#Override
public void onDestroy() {
teardown();
super.onDestroy();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main, menu);
MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat
.getActionProvider(mediaRouteMenuItem);
// Set the MediaRouteActionProvider selector for device discovery.
mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
return true;
}
/**
* Callback for MediaRouter events
*/
private class MyMediaRouterCallback extends MediaRouter.Callback {
#Override
public void onRouteSelected(MediaRouter router, RouteInfo info) {
Log.d(TAG, "onRouteSelected");
// Handle the user route selection.
mSelectedDevice = CastDevice.getFromBundle(info.getExtras());
launchReceiver();
}
#Override
public void onRouteUnselected(MediaRouter router, RouteInfo info) {
Log.d(TAG, "onRouteUnselected: info=" + info);
teardown();
mSelectedDevice = null;
}
}
/**
* Start the receiver app
*/
private void launchReceiver() {
try {
mCastListener = new Cast.Listener() {
#Override
public void onApplicationDisconnected(int errorCode) {
Log.d(TAG, "application has stopped");
teardown();
}
};
// Connect to Google Play services
mConnectionCallbacks = new ConnectionCallbacks();
mConnectionFailedListener = new ConnectionFailedListener();
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
.builder(mSelectedDevice, mCastListener);
mApiClient = new GoogleApiClient.Builder(this)
.addApi(Cast.API, apiOptionsBuilder.build())
.addConnectionCallbacks(mConnectionCallbacks)
.addOnConnectionFailedListener(mConnectionFailedListener)
.build();
mApiClient.connect();
} catch (Exception e) {
Log.e(TAG, "Failed launchReceiver", e);
}
}
/**
* Google Play services callbacks
*/
private class ConnectionCallbacks implements
GoogleApiClient.ConnectionCallbacks {
#Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected");
if (mApiClient == null) {
// We got disconnected while this runnable was pending
// execution.
return;
}
try {
if (mWaitingForReconnect) {
mWaitingForReconnect = false;
// Check if the receiver app is still running
if ((connectionHint != null)
&& connectionHint
.getBoolean(Cast.EXTRA_APP_NO_LONGER_RUNNING)) {
Log.d(TAG, "App is no longer running");
teardown();
} else {
// Re-create the custom message channel
try {
Cast.CastApi.setMessageReceivedCallbacks(
mApiClient,
mHelloWorldChannel.getNamespace(),
mHelloWorldChannel);
} catch (IOException e) {
Log.e(TAG, "Exception while creating channel", e);
}
}
} else {
// Launch the receiver app
Cast.CastApi
.launchApplication(mApiClient,
getString(R.string.app_id), false)
.setResultCallback(
new ResultCallback<Cast.ApplicationConnectionResult>() {
#Override
public void onResult(
ApplicationConnectionResult result) {
Status status = result.getStatus();
Log.d(TAG,
"ApplicationConnectionResultCallback.onResult: statusCode "
+ status.getStatusCode());
if (status.isSuccess()) {
ApplicationMetadata applicationMetadata = result
.getApplicationMetadata();
mSessionId = result
.getSessionId();
String applicationStatus = result
.getApplicationStatus();
boolean wasLaunched = result
.getWasLaunched();
Log.d(TAG,
"application name: "
+ applicationMetadata
.getName()
+ ", status: "
+ applicationStatus
+ ", sessionId: "
+ mSessionId
+ ", wasLaunched: "
+ wasLaunched);
mApplicationStarted = true;
// Create the custom message
// channel
mHelloWorldChannel = new HelloWorldChannel();
try {
Cast.CastApi
.setMessageReceivedCallbacks(
mApiClient,
mHelloWorldChannel
.getNamespace(),
mHelloWorldChannel);
} catch (IOException e) {
Log.e(TAG,
"Exception while creating channel",
e);
}
// set the initial instructions
// on the receiver
String message = createJsonMessage(MessageType.text, getString(R.string.instructions));
sendMessage(message);
} else {
Log.e(TAG,
"application could not launch");
teardown();
}
}
});
}
} catch (Exception e) {
Log.e(TAG, "Failed to launch application", e);
}
}
#Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended");
mWaitingForReconnect = true;
}
}
/**
* Google Play services callbacks
*/
private class ConnectionFailedListener implements
GoogleApiClient.OnConnectionFailedListener {
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.e(TAG, "onConnectionFailed ");
teardown();
}
}
/**
* Tear down the connection to the receiver
*/
private void teardown() {
Log.d(TAG, "teardown");
if (mApiClient != null) {
if (mApplicationStarted) {
if (mApiClient.isConnected() || mApiClient.isConnecting()) {
try {
Cast.CastApi.stopApplication(mApiClient, mSessionId);
if (mHelloWorldChannel != null) {
Cast.CastApi.removeMessageReceivedCallbacks(
mApiClient,
mHelloWorldChannel.getNamespace());
mHelloWorldChannel = null;
}
} catch (IOException e) {
Log.e(TAG, "Exception while removing channel", e);
}
mApiClient.disconnect();
}
mApplicationStarted = false;
}
mApiClient = null;
}
mSelectedDevice = null;
mWaitingForReconnect = false;
mSessionId = null;
}
/**
* Send a text message to the receiver
*
* #param message
*/
private void sendMessage(String message) {
if (mApiClient != null && mHelloWorldChannel != null) {
try {
Cast.CastApi.sendMessage(mApiClient,
mHelloWorldChannel.getNamespace(), message)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status result) {
if (!result.isSuccess()) {
Log.e(TAG, "Sending message failed");
}
}
});
} catch (Exception e) {
Log.e(TAG, "Exception while sending message", e);
}
} else {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Custom message channel
*/
class HelloWorldChannel implements MessageReceivedCallback {
/**
* #return custom namespace
*/
public String getNamespace() {
return getString(R.string.namespace);
}
/*
* Receive message from the receiver app
*/
#Override
public void onMessageReceived(CastDevice castDevice, String namespace,
String message) {
Log.d(TAG, "onMessageReceived: " + message);
}
}
enum MessageType {
text,
image,
}
public static Bitmap getBitmapFromView(View view) {
//Define a bitmap with the same size as the view
Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
//Bind a canvas to it
Canvas canvas = new Canvas(returnedBitmap);
//Get the view's background
Drawable bgDrawable =view.getBackground();
if (bgDrawable!=null)
//has background drawable, then draw it on the canvas
bgDrawable.draw(canvas);
else
//does not have background drawable, then draw white background on the canvas
canvas.drawColor(Color.WHITE);
// draw the view on the canvas
view.draw(canvas);
//return the bitmap
return returnedBitmap;
}
private static String createJsonMessage(MessageType type, String message)
{
return String.format("{\"type\":\"%s\", \"data\":\"%s\"}", type.toString(), message);
}
}
Since on Chromecast your application is running inside a web browser, you need to have an <img/> tag show the image. The src attribute of that tag should point to the image that you want to see and it has to be a url, so if your image is residing on your phone's local storage, you need to start a small web server in your mobile application to serve that image and communicate with the receiver what url it should point at (which would be the url at which your server is serving that image). these are all doable and you can use the CastCompanionLibrary, if you want, to communicate with your custom receiver; simply use the DataCastManager class instead of VideoCastManager.
May be my answer will be helpful for other developers, because I also did'nt found good solution and done it by myself.
For showing image via Google Cast on your device screen from your app you can create and start simply web server from your app which will process http requests with selected image name or id in URL.
Example:
public class MyWebServer {
private Activity activity;
private static ServerSocket httpServerSocket;
private static boolean isWebServerSunning;
public static final String drawableDelimiter = "pic-"
public MyWebServer(Activity activity) {
this.activity = activity;
}
public void stopWebServer() {
isWebServerSunning = false;
try {
if (httpServerSocket != null) {
httpServerSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void startWebServer() {
isWebServerSunning = true;
Thread webServerThread = new Thread(() -> {
Socket socket;
HttpResponseThread httpResponseThread;
try {
httpServerSocket = new ServerSocket(5050);
while (isWebServerSunning) {
socket = httpServerSocket.accept();
httpResponseThread = new HttpResponseThread(socket);
httpResponseThread.start();
}
} catch (Exception e) {
e.printStackTrace();
}
});
webServerThread.start();
}
private class HttpResponseThread extends Thread {
Socket clientSocket;
HttpResponseThread(Socket socket) {
this.clientSocket = socket;
}
#Override
public void run() {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
OutputStream outputStream = clientSocket.getOutputStream();
) {
String input = bufferedReader.readLine();
if (input != null && !input.isEmpty() && input.contains("/") && input.contains(" ")) {
if (input.contains(drawableDelimiter)) {
String imageId = input.substring(input.indexOf("/") + 1, input.lastIndexOf(" ")).trim().split(drawableDelimiter)[1];
Bitmap bitmap = BitmapFactory.decodeResource(activity.getResources(), Integer.parseInt(imageId));
if (bitmap != null) {
ByteArrayOutputStream bitmapBytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bitmapBytes);
outputStream.write("HTTP/1.0 200 OK\r\n".getBytes());
outputStream.write("Server: Apache/0.8.4\r\n".getBytes());
outputStream.write(("Content-Length: " + bitmapBytes.toByteArray().length + "\r\n").getBytes());
outputStream.write("\r\n".getBytes());
outputStream.write(bitmapBytes.toByteArray());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
And just start or stop your web server at when Google Cast will be useable or stopped.
MyWebServer myWebServer = new MyWebServer(this); // pass your activity here
myWebServer.startWebServer();
myWebServer.stopWebServer();