I've a MQTT client runnning in Android, and a MQTT Broker in the server. My problem is where we will use the app we have some connections drop so my web app needs to know the current state of the client.
So what we are doing right now is:
1 - The server sends a random number to the clients (each client will receive a different random number)
2 - The android client receives the number and send to a web service
3 - The web service writes in SQL db
4 - The server wait 4 secs to the response from android client and if the random number sent by the server == to the number in the db , the client is connected.
But now the problem is when, multi-users sends the random number the only one that will be write in the db is the last one so it's huge design fault.
In order to fix the only good solution is to get a direct response from the MQTT client and have to be unique per client but i don't know if is possible or if is the best way to go.
Some draw to better understand:
Flow
Here is my android code:
public class MQTTService extends Service implements MqttCallback {
public static final String DEBUG_TAG = "MqttService"; // Debug TAG
private static final String MQTT_THREAD_NAME = "MqttService[" + DEBUG_TAG + "]"; // Handler
// Thread
// ID
private String MQTT_BROKER = ""; // Broker URL
// or IP
// Address
private static final int MQTT_PORT = 1883; // Broker Port
public static final int MQTT_QOS_0 = 0; // QOS Level 0 ( Delivery Once no
// confirmation )
public static final int MQTT_QOS_1 = 1; // QOS Level 1 ( Delivery at least
// Once with confirmation )
public static final int MQTT_QOS_2 = 2; // QOS Level 2 ( Delivery only once
// with confirmation with handshake
// )
private static final int MQTT_KEEP_ALIVE = 30000; // KeepAlive Interval in
// MS
private static final String MQTT_KEEP_ALIVE_TOPIC_FORMAT = "/users/%s/keepalive"; // Topic
// format
// for
// KeepAlives
private static final byte[] MQTT_KEEP_ALIVE_MESSAGE = { 0 }; // Keep Alive
// message
// to send
private static final int MQTT_KEEP_ALIVE_QOS = MQTT_QOS_2; // Default
// Keepalive QOS
private static final boolean MQTT_CLEAN_SESSION = false; // Start a clean
// session?
private static final String MQTT_URL_FORMAT = "tcp://%s:%d"; // URL Format
// normally
// don't
// change
public static final String ACTION_START = DEBUG_TAG + ".START"; // Action
// to
// start
public static final String ACTION_STOP = DEBUG_TAG + ".STOP"; // Action to
// stop
public static final String ACTION_KEEPALIVE = DEBUG_TAG + ".KEEPALIVE"; // Action
// to
// keep
// alive
// used
// by
// alarm
// manager
private static final String ACTION_RECONNECT = DEBUG_TAG + ".RECONNECT"; // Action
// to
// reconnect
// private final String DEVICE_ID_FORMAT = "andr_%s"; // Device ID
// Format, add
// any prefix
// you'd like
// Note: There
// is a 23
// character
// limit you
// will get
// An NPE if you
// go over that
// limit
private boolean mStarted = false; // Is the Client started?
private String user_ID; // Device ID, Secure.ANDROID_ID
private Handler mConnHandler; // Seperate Handler thread for networking
private MqttDefaultFilePersistence mDataStore; // Defaults to FileStore
private MemoryPersistence mMemStore; // On Fail reverts to MemoryStore
private MqttConnectOptions mOpts; // Connection Options
private MqttTopic mKeepAliveTopic; // Instance Variable for Keepalive topic
private MqttClient mClient; // Mqtt Client
private AlarmManager mAlarmManager; // Alarm manager to perform repeating
// tasks
private ConnectivityManager mConnectivityManager; // To check for
// connectivity changes
public static final String TAG_CONNECTED = "CONNECTED";
public static final String TAG_ASSIGNED = "ASSIGNED";
public static final String TAG_REFRESH = "REFRESH";
public String TOPIC_CONNECTED = null;
public String TOPIC_ASSIGNED = null;
public String TOPIC_REFRESH = null;
private Intent intent;
private PendingIntent alarmIntent;
private AppMaintenance appStatus;
/**
* Initializes the DeviceId and most instance variables Including the
* Connection Handler, Datastore, Alarm Manager and ConnectivityManager.
*/
#Override
public void onCreate() {
super.onCreate();
// mDeviceId = String.format(DEVICE_ID_FORMAT,
// Secure.getString(getContentResolver(), Secure.ANDROID_ID));
android.os.Debug.waitForDebugger(); // Debugger
appStatus = (AppMaintenance) getApplicationContext();
ExceptionHandler.register(this, appStatus.getException_URL());
HandlerThread thread = new HandlerThread(MQTT_THREAD_NAME);
thread.start();
mConnHandler = new Handler(thread.getLooper());
try {
mDataStore = new MqttDefaultFilePersistence(getCacheDir().getAbsolutePath());
} catch (Exception e) {
// writeToFile("Exception - onCreate()");
e.printStackTrace();
mDataStore = null;
mMemStore = new MemoryPersistence();
}
mOpts = new MqttConnectOptions();
mOpts.setCleanSession(MQTT_CLEAN_SESSION);
// Do not set keep alive interval on mOpts we keep track of it with
// alarm's
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
/**
* Start MQTT Client
*
* #param Context
* context to start the service with
* #return void
*/
public static void actionStart(Context ctx) {
Intent i = new Intent(ctx, MQTTService.class);
i.setAction(ACTION_START);
ctx.startService(i);
}
/**
* Stop MQTT Client
*
* #param Context
* context to start the service with
* #return void
*/
public static void actionStop(Context ctx) {
Intent i = new Intent(ctx, MQTTService.class);
i.setAction(ACTION_STOP);
ctx.startService(i);
}
/**
* Send a KeepAlive Message
*
* #param Context
* context to start the service with
* #return void
*/
public static void actionKeepalive(Context ctx) {
Intent i = new Intent(ctx, MQTTService.class);
i.setAction(ACTION_KEEPALIVE);
ctx.startService(i);
}
/**
* Service onStartCommand Handles the action passed via the Intent
*
* #return START_REDELIVER_INTENT
*/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
this.intent = intent;
SharedPreferences myPrefs = getSharedPreferences("UserPreferences", MODE_PRIVATE);
MQTT_BROKER = myPrefs.getString("broker", "");
user_ID = myPrefs.getString("userID", "");
String action = intent.getAction();
TOPIC_CONNECTED = user_ID + "\\" + TAG_CONNECTED;
TOPIC_ASSIGNED = user_ID + "\\" + TAG_ASSIGNED;
TOPIC_REFRESH = user_ID + "\\" + TAG_REFRESH;
Log.i(DEBUG_TAG, "Received action of " + action);
// writeToFile("Received action of " + action);
if (user_ID.isEmpty() || user_ID == null)
action = null;
if (action == null) {
Log.i(DEBUG_TAG, "Starting service with no action\n Probably from a crash");
// writeToFile("Starting service with no action\n Probably from a crash");
Toast.makeText(getApplicationContext(), getString(R.string.mqtt_warning_userid), Toast.LENGTH_LONG).show();
action = null;
} else {
if (action.equals(ACTION_START)) {
Log.i(DEBUG_TAG, "Received ACTION_START");
// writeToFile("Received ACTION_START");
start();
} else if (action.equals(ACTION_STOP)) {
Log.i(DEBUG_TAG, "Received ACTION_STOP");
// writeToFile("Received ACTION_STOP");
stop();
} else if (action.equals(ACTION_KEEPALIVE)) {
Log.i(DEBUG_TAG, "Received ACTION_KEEPALIVE");
// writeToFile("Received ACTION_KEEPALIVE");
keepAlive();
} else if (action.equals(ACTION_RECONNECT)) {
Log.i(DEBUG_TAG, "Received ACTION_RECONNECT");
// writeToFile("Received ACTION_RECONNECT");
reconnectIfNecessary();
}
}
return START_NOT_STICKY;
}
/**
* Attempts connect to the Mqtt Broker and listen for Connectivity changes
* via ConnectivityManager.CONNECTVITIY_ACTION BroadcastReceiver
*/
private synchronized void start() {
if (mStarted) {
Log.i(DEBUG_TAG, "Attempt to start while already started");
// writeToFile("Attempt to start while already started");
return;
}
if (hasScheduledKeepAlives()) {
stopKeepAlives();
}
connect();
}
/**
* Attempts to stop the Mqtt client as well as halting all keep alive
* messages queued in the alarm manager
*/
private synchronized void stop() {
if (!mStarted) {
Log.i(DEBUG_TAG, "Attemt to stop connection that isn't running");
// writeToFile("Attemt to stop connection that isn't running");
return;
}
if (mClient != null) {
mConnHandler.post(new Runnable() {
#Override
public void run() {
try {
mClient.disconnect();
} catch (Exception ex) {
// writeToFile("Exception - stop() ");
ex.printStackTrace();
mClient = null;
mStarted = false;
} finally {
mClient = null;
mStarted = false;
stopKeepAlives();
}
}
});
}
}
/**
* Connects to the broker with the appropriate datastore
*/
private synchronized void connect() {
String url = String.format(Locale.US, MQTT_URL_FORMAT, MQTT_BROKER, MQTT_PORT);
Log.i(DEBUG_TAG, "Connecting with URL: " + url);
// writeToFile("Connecting with URL: " + url);
try {
if (mDataStore != null) {
Log.i(DEBUG_TAG, "Connecting with DataStore");
// writeToFile("Connecting with DataStore");
mClient = new MqttClient(url, user_ID, mDataStore);
} else {
Log.i(DEBUG_TAG, "Connecting with MemStore");
// writeToFile("Connecting with MemStore");
mClient = new MqttClient(url, user_ID, mMemStore);
}
} catch (Exception e) {
// writeToFile("Exception - connect L.343");
e.printStackTrace();
}
mConnHandler.post(new Runnable() {
#Override
public void run() {
try {
mClient.connect(mOpts);
mClient.subscribe(new String[] { TOPIC_CONNECTED, TOPIC_ASSIGNED, TOPIC_REFRESH }, new int[] { MQTT_QOS_0,
MQTT_KEEP_ALIVE_QOS, MQTT_KEEP_ALIVE_QOS });
mClient.setCallback(new MQTTPushCallback(MQTTService.this, intent, user_ID, TOPIC_CONNECTED, TOPIC_ASSIGNED,
TOPIC_REFRESH));
mStarted = true; // Service is now connected
Log.i(DEBUG_TAG, "Successfully connected and subscribed starting keep alives");
// writeToFile("Successfully connected and subscribed starting keep alives");
startKeepAlives();
} catch (Exception e) {
// writeToFile("Exception - connect L.366");
e.printStackTrace();
}
}
});
}
/**
* Schedules keep alives via a PendingIntent in the Alarm Manager
*/
private void startKeepAlives() {
Intent i = new Intent();
i.setClass(this, MQTTService.class);
i.setAction(ACTION_KEEPALIVE);
alarmIntent = PendingIntent.getService(this, 0, i, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + MQTT_KEEP_ALIVE, MQTT_KEEP_ALIVE, alarmIntent);
Log.i(DEBUG_TAG, "Started keepAlives sucessfully");
// writeToFile("Started keepAlives sucessfully");
}
/**
* Cancels the Pending Intent in the alarm manager
*/
private void stopKeepAlives() {
if (mAlarmManager != null) {
mAlarmManager.cancel(alarmIntent);
}
}
/**
* Publishes a KeepALive to the topic in the broker
*/
private synchronized void keepAlive() {
// if (isForeground()) {
if (isConnected()) {
try {
sendKeepAlive();
return;
} catch (MqttConnectivityException ex) {
// writeToFile("Exception - KeepAlive() 1");
ex.printStackTrace();
reconnectIfNecessary();
} catch (MqttPersistenceException ex) {
// writeToFile("Exception - KeepAlive() 2");
ex.printStackTrace();
stop();
restartService();
} catch (MqttException ex) {
// writeToFile("Exception - KeepAlive() 3");
ex.printStackTrace();
stop();
restartService();
} catch (Exception ex) {
// writeToFile("Exception - KeepAlive() 4");
ex.printStackTrace();
stop();
restartService();
}
}
}
/**
* Checks the current connectivity and reconnects if it is required.
*/
private synchronized void reconnectIfNecessary() {
if (!mStarted && mClient == null)
start();
}
/**
* Query's the NetworkInfo via ConnectivityManager to return the current
* connected state
*
* #return boolean true if we are connected false otherwise
*/
private boolean isNetworkAvailable() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
return info == null ? false : info.isConnected();
}
/**
* Verifies the client State with our local connected state
*
* #return true if its a match we are connected false if we aren't connected
*/
private boolean isConnected() {
if (mStarted && mClient != null && !mClient.isConnected()) {
Log.i(DEBUG_TAG, "Mismatch between what we think is connected and what is connected");
// writeToFile("Mismatch between what we think is connected and what is connected");
}
if (mClient != null) {
return mStarted && mClient.isConnected() ? true : false;
}
return false;
}
/**
* Receiver that listens for connectivity changes via ConnectivityManager
*/
private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// writeToFile("isNetworkAvailable = " + isNetworkAvailable());
if (isNetworkAvailable() && !mStarted) {
Log.i(DEBUG_TAG, "Connectivity Changed...");
// Intent i = new Intent(context, MQTTService.class);
// i.setAction(ACTION_RECONNECT);
// context.startService(i);
restartService();
} else if (!isNetworkAvailable()) {
stop();
}
}
};
/**
* Sends a Keep Alive message to the specified topic
*
* #see MQTT_KEEP_ALIVE_MESSAGE
* #see MQTT_KEEP_ALIVE_TOPIC_FORMAT
* #return MqttDeliveryToken specified token you can choose to wait for
* completion
*/
private synchronized MqttDeliveryToken sendKeepAlive() throws MqttConnectivityException, MqttPersistenceException, MqttException {
if (!isConnected())
throw new MqttConnectivityException();
if (mKeepAliveTopic == null) {
mKeepAliveTopic = mClient.getTopic(String.format(Locale.US, MQTT_KEEP_ALIVE_TOPIC_FORMAT, user_ID));
}
Log.i(DEBUG_TAG, "Sending Keepalive to " + MQTT_BROKER);
// writeToFile("Sending Keepalive to " + MQTT_BROKER);
MqttMessage message = new MqttMessage(MQTT_KEEP_ALIVE_MESSAGE);
message.setQos(MQTT_KEEP_ALIVE_QOS);
return mKeepAliveTopic.publish(message);
}
/**
* Query's the AlarmManager to check if there is a keep alive currently
* scheduled
*
* #return true if there is currently one scheduled false otherwise
*/
private synchronized boolean hasScheduledKeepAlives() {
Intent i = new Intent();
i.setClass(this, MQTTService.class);
i.setAction(ACTION_KEEPALIVE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, PendingIntent.FLAG_NO_CREATE);
return pi != null ? true : false;
}
#Override
public IBinder onBind(Intent arg0) {
return null;
}
/**
* Connectivity Lost from broker
*/
#Override
public void connectionLost(Throwable arg0) {
stopKeepAlives();
mClient = null;
if (isNetworkAvailable()) {
reconnectIfNecessary();
}
}
/**
* Publish Message Completion
*/
#Override
public void deliveryComplete(IMqttDeliveryToken arg0) {
// TODO Auto-generated method stub
}
/**
* Received Message from broker
*/
#Override
public void messageArrived(String arg0, MqttMessage arg1) throws Exception {
// Log.i(DEBUG_TAG,
// " Topic:\t" + topic.getName() + " Message:\t"
// + new String(message.getPayload()) + " QoS:\t"
// + message.getQos());
}
/**
* MqttConnectivityException Exception class
*/
private class MqttConnectivityException extends Exception {
private static final long serialVersionUID = -7385866796799469420L;
}
#Override
public void onDestroy() {
try {
mClient.unsubscribe(new String[] { TOPIC_CONNECTED, TOPIC_ASSIGNED, TOPIC_REFRESH });
mClient.disconnect(0);
} catch (Exception e) {
// writeToFile("Exception - onDestroy() 1");
e.printStackTrace();
} finally {
new WS_LOGOUT(this).execute(user_ID);
}
}
public void restartService() {
mKeepAliveTopic = null;
actionStart(getApplicationContext()); // restart the service
}
}
What sort of latency can you live with when knowing if the client is disconnected?
You can use the Last Will and Testament feature to publish a value to a topic when the server detects that the MQTT keep alive time has expired with out receiving a ping from the client.
You can set the keep alive time at connection time. But depending on your requirements (battery/network usage) you need to work out what to set it to. If I remember correctly the default is 30 seconds (might be 60)
When your client connects it can set a flag on a persitent topic to say it's online, and the LWT can set this to 0.
e.g.
on connect publish "1" to client/[uniqueid]/online
set the LWT to publish "0" to client/[uniqueid]/online
Related
I'm trying to creat a bacckground service that will have a thread that adds one to a counter every minute.
I want other apps to be able to call a methed call getBot that will retunt the value.
Right now I have a test app that has a activty that bindss to the app so it can talk to it to get the counter. When it exits it unbounds to it.
It has a button called botGert that gets the current counter value from the service.
When it unbounds to it, the service shuts down. How can I get it so it does not shut down????
service class
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
static final int MSG_BOT_GET = 2;
static final int MSG_BOT_SET = 3;
static final int MSG_BOT_STOP = 4;
static final int MSG_BOT_PAUSE = 5;
static final int MSG_GET_ORDERS = 6;
static final int MSG_GET_POINT = 7;
static final int MSG_BOT_GET_RES = 2;
static final int PRICE_STATIC =1;
static final int PRICE_PERCENTAGE =2;
static final int PRICE_ALL = 3;
static final int VOL_STATIC = 1;
static final int VOL_PERCENTAGE = 2;
static final int VOL_ALL = 3;
int counter;
boolean isStop=false; // put bot in pause ste on next 1 min update
boolean isUYpdateNextMinute=false;
/**
This is called by a client to exicute code in this service
*/
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BOT_GET:
Message resp = Message.obtain(null, MSG_BOT_GET_RES);
Bundle bResp = new Bundle();
bResp.putString("respData", Integer.toString(counter ));
resp.setData(bResp);
try {
msg.replyTo.send(resp);
} catch (Exception e)
{
}
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
#Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
//////////////////////////////////////////////////////////////////////////////
// emplanent callbacks
MessengerService parent;
#Override
public void onCreate() {
Toast.makeText(this, "Service Created", Toast.LENGTH_LONG).show();
parent=this;
}
#Override
public void onStart(Intent intent, int startid) {
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
// sgart 1 minute update thread
}
#Override
public void onDestroy() {
Toast.makeText(this, "Service Stopped", Toast.LENGTH_LONG).show();
}
///////////////////////////////////////////////////////////////
// thread code that runbs counter
class ThreadDemo extends Thread {
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
try {
do {
counter++;
Thread.sleep(1000 *60 );
counter++;
} while (true);
} catch(Exception e)
{
Toast.makeText(parent, "thread stopped", Toast.LENGTH_LONG).show();
}
} // end inner thread class
} // end class
}
/////////////////////////////////////////////////////////////////////
activity class
public class ActivityBoundMessenger extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bound_messenger);
}
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
/////////////////////////////////////////////////////////////////////////////////////////
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
////////////////////////////////////////////////////////////////////////////////
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void butSet(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_BOT_SET, 0, 0);
Bundle b = new Bundle();
b.putString("data", "json object");
msg.setData(b);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void butGet(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message
.obtain(null, MessengerService.MSG_BOT_GET);
msg.replyTo = new Messenger(new ResponseHandler());
// We pass the value
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
////////////////////////////////////////////////////////////////////////////////////////
// these metheds get called whenb this acty starts and endsa
#Override
protected void onStart() {
super.onStart();
// Bind to the service
////////////////////////////////////////////
Intent ser = new Intent(this, MessengerService.class);
startService(ser);
bindService(ser, mConnection,
Context.BIND_AUTO_CREATE);
///////////////////////////////////////////
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
// This class handles the Service response
int ted=0;
String nextUpdate=null;
class ResponseHandler extends Handler {
#Override
public void handleMessage(Message msg) {
int respCode = msg.what;
switch (respCode) {
case MessengerService.MSG_BOT_GET_RES:
String nextUpdate= msg.getData().getString("respData");
ted++;
}
}
}
}
From Android Documentation:
When the last client unbinds from the service, the system destroys the service, unless the service was also started by startService().
So, all you have to do is call this in your onCreate() method in the Service:
startService(new Intent(this, MessengerService.class))
Of course make sure to call stopSelf() when you are done with the service.
I'm building a simple chat that connects between an android client and a java server (running on my pc). The user can send and receive messages to/from the android app and the desktop server.
I'm dealing now with the question of how to run the client-socket in a different thread than the UI Thread.
I saw solutions using AsyncTask, but as the user may communicate using the app for a long sequential time, AsyncTask looks like a bad approach.
AsyncTasks should ideally be used for short operations (a few seconds at the most.) API
Because i need the client socket to consistently listen for messages from the desktop server, i thought of creating new Thread receiving a Runnable implementing class.
My questions
1. In which "thread mechanism" to place the client socket rows (Thread, IntentService)?
Socket client = new Socket(host, port);
InputStreamReader inputStreamReader = new InputStreamReader(client.getInputStream());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
while ((messageFromServer = bufferedReader.readLine()) != null) { //... }
2. How can the client socket (running from a different thread than the main thread) post the messageFromServer to an TextView?
How will i send the user messages from the app to the server (using the client-socket ofcourse), upon user entering text and clicking a button?
Thanks!
I've created a similar app and I used a Service which runs in the background.
I've copied the code from the IntentService clas and updated handleMessage(Message msg) method and removed stopSelf(msg.arg1); line. In this way you have a service that runs in the background. After it I used a Thread for the connection.
You have two choices here. Store the data into the database and the GUI refreshes itself. Or use LocalBroadcastManager.
Here you can also store the data into the db or Start the service with a special intent.
Here is my implementation. I hope you will understand the code.
public class KeepAliveService extends Service {
/**
* The source of the log message.
*/
private static final String TAG = "KeepAliveService";
private static final long INTERVAL_KEEP_ALIVE = 1000 * 60 * 4;
private static final long INTERVAL_INITIAL_RETRY = 1000 * 10;
private static final long INTERVAL_MAXIMUM_RETRY = 1000 * 60 * 2;
private ConnectivityManager mConnMan;
protected NotificationManager mNotifMan;
protected AlarmManager mAlarmManager;
private boolean mStarted;
private boolean mLoggedIn;
protected static ConnectionThread mConnection;
protected static SharedPreferences mPrefs;
private final int maxSize = 212000;
private Handler mHandler;
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(final Looper looper) {
super(looper);
}
#Override
public void handleMessage(final Message msg) {
onHandleIntent((Intent) msg.obj);
}
}
public static void actionStart(final Context context) {
context.startService(SystemHelper.createExplicitFromImplicitIntent(context, new Intent(IntentActions.KEEP_ALIVE_SERVICE_START)));
}
public static void actionStop(final Context context) {
context.startService(SystemHelper.createExplicitFromImplicitIntent(context, new Intent(IntentActions.KEEP_ALIVE_SERVICE_STOP)));
}
public static void actionPing(final Context context) {
context.startService(SystemHelper.createExplicitFromImplicitIntent(context, new Intent(IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER)));
}
#Override
public void onCreate() {
Log.i(TAG, "onCreate called.");
super.onCreate();
mPrefs = getSharedPreferences("KeepAliveService", MODE_PRIVATE);
mConnMan = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
mNotifMan = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
mHandler = new Handler();
final HandlerThread thread = new HandlerThread("IntentService[KeepAliveService]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
// If our process was reaped by the system for any reason we need to
// restore our state with merely a
// call to onCreate.
// We record the last "started" value and restore it here if necessary.
handleCrashedService();
}
#Override
public void onDestroy() {
Log.i(TAG, "Service destroyed (started=" + mStarted + ")");
if (mStarted) {
stop();
}
mServiceLooper.quit();
}
private void handleCrashedService() {
Log.i(TAG, "handleCrashedService called.");
if (isStarted()) {
// We probably didn't get a chance to clean up gracefully, so do it now.
stopKeepAlives();
// Formally start and attempt connection.
start();
}
}
/**
* Returns the last known value saved in the database.
*/
private boolean isStarted() {
return mStarted;
}
private void setStarted(final boolean started) {
Log.i(TAG, "setStarted called with value: " + started);
mStarted = started;
}
protected void setLoggedIn(final boolean value) {
Log.i(TAG, "setLoggedIn called with value: " + value);
mLoggedIn = value;
}
protected boolean isLoggedIn() {
return mLoggedIn;
}
public static boolean isConnected() {
return mConnection != null;
}
#Override
public void onStart(final Intent intent, final int startId) {
final Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
#Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
Log.i(TAG, "Service started with intent : " + intent);
onStart(intent, startId);
return START_NOT_STICKY;
}
private void onHandleIntent(final Intent intent) {
if (IntentActions.KEEP_ALIVE_SERVICE_STOP.equals(intent.getAction())) {
stop();
stopSelf();
} else if (IntentActions.KEEP_ALIVE_SERVICE_START.equals(intent.getAction())) {
start();
} else if (IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER.equals(intent.getAction())) {
keepAlive(false);
}
}
#Override
public IBinder onBind(final Intent intent) {
return null;
}
private synchronized void start() {
if (mStarted) {
Log.w(TAG, "Attempt to start connection that is already active");
setStarted(true);
return;
}
try {
registerReceiver(mConnectivityChanged, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
} catch (final Exception e) {
Log.e(TAG, "Exception occurred while trying to register the receiver.", e);
}
if (mConnection == null) {
Log.i(TAG, "Connecting...");
mConnection = new ConnectionThread(Config.PLUGIN_BASE_HOST, Config.PLUGIN_BASE_PORT);
mConnection.start();
}
}
private synchronized void stop() {
if (mConnection != null) {
mConnection.abort(true);
mConnection = null;
}
setStarted(false);
try {
unregisterReceiver(mConnectivityChanged);
} catch (final Exception e) {
Log.e(TAG, "Exception occurred while trying to unregister the receiver.", e);
}
cancelReconnect();
}
/**
* Sends the keep-alive message if the service is started and we have a
* connection with it.
*/
private synchronized void keepAlive(final Boolean forced) {
try {
if (mStarted && isConnected() && isLoggedIn()) {
mConnection.sendKeepAlive(forced);
}
} catch (final IOException e) {
Log.w(TAG, "Error occurred while sending the keep alive message.", e);
} catch (final JSONException e) {
Log.w(TAG, "JSON error occurred while sending the keep alive message.", e);
}
}
/**
* Uses the {#link android.app.AlarmManager} to start the keep alive service in every {#value #INTERVAL_KEEP_ALIVE} milliseconds.
*/
private void startKeepAlives() {
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL_KEEP_ALIVE, INTERVAL_KEEP_ALIVE, pi);
}
/**
* Removes the repeating alarm which was started by the {#link #startKeepAlives()} function.
*/
private void stopKeepAlives() {
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.cancel(pi);
}
public void scheduleReconnect(final long startTime) {
long interval = mPrefs.getLong("retryInterval", INTERVAL_INITIAL_RETRY);
final long now = System.currentTimeMillis();
final long elapsed = now - startTime;
if (elapsed < interval) {
interval = Math.min(interval * 4, INTERVAL_MAXIMUM_RETRY);
} else {
interval = INTERVAL_INITIAL_RETRY;
}
Log.i(TAG, "Rescheduling connection in " + interval + "ms.");
mPrefs.edit().putLong("retryInterval", interval).apply();
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_RECONNECT), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + interval, pi);
}
public void cancelReconnect() {
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_RECONNECT), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.cancel(pi);
}
private synchronized void reconnectIfNecessary() {
if (mStarted && !isConnected()) {
Log.i(TAG, "Reconnecting...");
mConnection = new ConnectionThread(Config.PLUGIN_BASE_HOST, Config.PLUGIN_BASE_PORT);
mConnection.start();
}
}
private final BroadcastReceiver mConnectivityChanged = new BroadcastReceiver() {
#Override
public void onReceive(final Context context, final Intent intent) {
final NetworkInfo info = mConnMan.getActiveNetworkInfo(); // (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
final boolean hasConnectivity = info != null && info.isConnected();
Log.i(TAG, "Connecting changed: connected=" + hasConnectivity);
if (hasConnectivity) {
reconnectIfNecessary();
} else if (mConnection != null) {
mConnection.abort(false);
mConnection = null;
}
}
};
protected class ConnectionThread extends Thread {
private final Socket mSocket;
private final String mHost;
private final int mPort;
private volatile boolean mAbort = false;
public ConnectionThread(final String host, final int port) {
mHost = host;
mPort = port;
mSocket = new Socket();
}
/**
* Returns whether we have an active internet connection or not.
*
* #return <code>true</code> if there is an active internet connection.
* <code>false</code> otherwise.
*/
private boolean isNetworkAvailable() {
final NetworkInfo info = mConnMan.getActiveNetworkInfo();
return info != null && info.isConnected();
}
#Override
public void run() {
final Socket s = mSocket;
final long startTime = System.currentTimeMillis();
try {
// Now we can say that the service is started.
setStarted(true);
// Connect to server.
s.connect(new InetSocketAddress(mHost, mPort), 20000);
Log.i(TAG, "Connection established to " + s.getInetAddress() + ":" + mPort);
// Start keep alive alarm.
startKeepAlives();
final DataOutputStream dos = new DataOutputStream(s.getOutputStream());
final BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
// Send the login data.
final JSONObject login = new JSONObject();
// Send the login message.
dos.write((login.toString() + "\r\n").getBytes());
// Wait until we receive something from the server.
String receivedMessage;
while ((receivedMessage = in.readLine()) != null) {
Log.i(TAG, "Received data: " + receivedMessage);
processMessagesFromServer(dos, receivedMessage);
}
if (!mAbort) {
Log.i(TAG, "Server closed connection unexpectedly.");
}
} catch (final IOException e) {
Log.e(TAG, "Unexpected I/O error.", e);
} catch (final Exception e) {
Log.e(TAG, "Exception occurred.", e);
} finally {
setLoggedIn(false);
stopKeepAlives();
if (mAbort) {
Log.i(TAG, "Connection aborted, shutting down.");
} else {
try {
s.close();
} catch (final IOException e) {
// Do nothing.
}
synchronized (KeepAliveService.this) {
mConnection = null;
}
if (isNetworkAvailable()) {
scheduleReconnect(startTime);
}
}
}
}
/**
* Sends the PING word to the server.
*
* #throws java.io.IOException if an error occurs while writing to this stream.
* #throws org.json.JSONException
*/
public void sendKeepAlive(final Boolean forced) throws IOException, JSONException {
final JSONObject ping = new JSONObject();
final Socket s = mSocket;
s.getOutputStream().write((ping.toString() + "\r\n").getBytes());
}
/**
* Aborts the connection with the server.
*/
public void abort(boolean manual) {
mAbort = manual;
try {
// Close the output stream.
mSocket.shutdownOutput();
} catch (final IOException e) {
// Do nothing.
}
try {
// Close the input stream.
mSocket.shutdownInput();
} catch (final IOException e) {
// Do nothing.
}
try {
// Close the socket.
mSocket.close();
} catch (final IOException e) {
// Do nothing.
}
while (true) {
try {
join();
break;
} catch (final InterruptedException e) {
// Do nothing.
}
}
}
}
public void processMessagesFromServer(final DataOutputStream dos, final String receivedMessage) throws IOException {
}
}
You can start the service by calling KeepAliveService.actionStart() and you can also define custom functions.
Please note that the service will be stopped only if you call KeepAliveService.actionStop(). Otherwise it will run forever. If you call e.g. KeepAliveService.actionSendMessage(String message) then the intent will be passed to the service and you can handle it easily.
EDIT:
The SystemHelper class is only a utility class which contains static methods.
public class SystemHelper {
/**
* Android Lollipop, API 21 introduced a new problem when trying to invoke implicit intent,
* "java.lang.IllegalArgumentException: Service Intent must be explicit"
*
* If you are using an implicit intent, and know only 1 target would answer this intent,
* This method will help you turn the implicit intent into the explicit form.
*
* Inspired from SO answer: http://stackoverflow.com/a/26318757/1446466
* #param context the application context
* #param implicitIntent - The original implicit intent
* #return Explicit Intent created from the implicit original intent
*/
public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
Log.i(TAG, "createExplicitFromImplicitIntent ... called with intent: " + implicitIntent);
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
Log.i(TAG, "createExplicitFromImplicitIntent ... resolveInfo is null or there are more than one element.");
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
Log.i(TAG, "createExplicitFromImplicitIntent ... found package name:" + packageName + ", class name: " + className + ".");
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
}
The Config class.
public class Config {
public static final String PACKAGE_NAME = "com.yourapp.package";
public static final String PLUGIN_BASE_HOST = "test.yoursite.com";
public static final int PLUGIN_BASE_PORT = 10000;
}
And the IntentActions class.
public class IntentActions {
public static final String KEEP_ALIVE_SERVICE_START = Config.PACKAGE_NAME + ".intent.action.KEEP_ALIVE_SERVICE_START";
public static final String KEEP_ALIVE_SERVICE_STOP = Config.PACKAGE_NAME + ".intent.action.KEEP_ALIVE_SERVICE_STOP";
public static final String KEEP_ALIVE_SERVICE_PING_SERVER = Config.PACKAGE_NAME + ".intent.action.KEEP_ALIVE_SERVICE_PING_SERVER";
}
In the AndroidManifest file the service is defined in the following way:
<service android:name="com.yourapp.package.services.KeepAliveService"
android:exported="false">
<intent-filter>
<action android:name="com.yourapp.package.intent.action.KEEP_ALIVE_SERVICE_START" />
<action android:name="com.yourapp.package.intent.action.KEEP_ALIVE_SERVICE_STOP" />
<action android:name="com.yourapp.package.intent.action.KEEP_ALIVE_SERVICE_PING_SERVER" />
</intent-filter>
</service>
I suggest you take a look at the Android documentation for background services. Personally I would use the IntentService as it's a well established pattern within Android.
http://developer.android.com/training/run-background-service/index.html
I am using Androids SaveSharedPreference to allow a user to auto login once they already logged in previously. For a user to gain access, their login information must be sent to my server using webscokets, and once a connection is made they will be able to access their accounts.
In terms of present functionality, the users can log into the app the first time and use all the features. The problem arises as the user closes out, not logs out, and attempts to access again and the error below is thrown and the app crashes:
and then
I have seen that the NullPoint error on line 207 of LoggingIn is at:
result = imService.authenticateUser(
SaveSharedPreference
.getUserName(getApplicationContext()),
SaveSharedPreference
.getPassword(getApplicationContext()));
The remaining code of the class in the onCreate method of the LoggingIn class called once app starts:
protected static final int NOT_CONNECTED_TO_SERVICE = 0;
protected static final int FILL_BOTH_USERNAME_AND_PASSWORD = 1;
public static final String AUTHENTICATION_FAILED = "0";
public static final String FRIEND_LIST = "FRIEND_LIST";
protected static final int MAKE_SURE_USERNAME_AND_PASSWORD_CORRECT = 2;
protected static final int NOT_CONNECTED_TO_NETWORK = 3;
private EditText usernameText;
private EditText passwordText;
private Manager imService;
public static final int SIGN_UP_ID = Menu.FIRST;
public static final int EXIT_APP_ID = Menu.FIRST + 1;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
imService = ((MessagingService.IMBinder) service).getService();
if (imService.isUserAuthenticated() == true) {
// Intent i = new Intent(LoggingIn.this, ListOfFriends.class);
Intent i = new Intent(LoggingIn.this, MainActivity.class);
startActivity(i);
LoggingIn.this.finish();
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
imService = null;
Toast.makeText(LoggingIn.this, R.string.local_service_stopped,
Toast.LENGTH_SHORT).show();
}
};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Start and bind the imService
*/
startService(new Intent(LoggingIn.this, MessagingService.class));
setContentView(R.layout.loggin_in);
setTitle("Login");
ImageButton loginButton = (ImageButton) findViewById(R.id.button1);
usernameText = (EditText) findViewById(R.id.username);
passwordText = (EditText) findViewById(R.id.password);
// If not logged in already
if (SaveSharedPreference.getUserName(getApplicationContext()).length() == 0) {
loginButton.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
if (imService == null) {
Toast.makeText(getApplicationContext(),
R.string.not_connected_to_service,
Toast.LENGTH_LONG).show();
// showDialog(NOT_CONNECTED_TO_SERVICE);
return;
} else if (imService.isNetworkConnected() == false) {
Toast.makeText(getApplicationContext(),
R.string.not_connected_to_network,
Toast.LENGTH_LONG).show();
// showDialog(NOT_CONNECTED_TO_NETWORK);
} else if (usernameText.length() > 0
&& passwordText.length() > 0) {
Thread loginThread = new Thread() {
private Handler handler = new Handler();
#Override
public void run() {
String result = null;
try {
result = imService.authenticateUser(
usernameText.getText().toString(),
passwordText.getText().toString());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (result == null
|| result.equals(AUTHENTICATION_FAILED)) {
/*
* Authenticatin failed, inform the user
*/
handler.post(new Runnable() {
public void run() {
Toast.makeText(
getApplicationContext(),
R.string.make_sure_username_and_password_correct,
Toast.LENGTH_LONG).show();
// showDialog(MAKE_SURE_USERNAME_AND_PASSWORD_CORRECT);
}
});
} else {
/*
* if result not equal to authentication
* failed, result is equal to friend and
* group list of the user 0: is for friends,
* 1: is for groups
*/
handler.post(new Runnable() {
public void run() {
// If log in successful, then save
// username and password to shared
// preferences:
SaveSharedPreference.setUserName(
getApplicationContext(),
usernameText.getText()
.toString());
SaveSharedPreference.setPassword(
getApplicationContext(),
passwordText.getText()
.toString());
Intent i = new Intent(
LoggingIn.this,
MainActivity.class);
startActivity(i);
LoggingIn.this.finish();
}
});
}
}
};
loginThread.start();
} else {
/*
* Username or Password is not filled, alert the user
*/
Toast.makeText(getApplicationContext(),
R.string.fill_both_username_and_password,
Toast.LENGTH_LONG).show();
// showDialog(FILL_BOTH_USERNAME_AND_PASSWORD);
}
}
});
} else {
// If already logged in, pull the information and send to server to
// auto log in
Thread loginThread = new Thread() {
private Handler handler = new Handler();
#Override
public void run() {
String result = null;
try {
result = imService.authenticateUser(
SaveSharedPreference
.getUserName(getApplicationContext()),
SaveSharedPreference
.getPassword(getApplicationContext()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (result == null || result.equals(AUTHENTICATION_FAILED)) {
/*
* Authenticatin failed, inform the user
*/
handler.post(new Runnable() {
public void run() {
Toast.makeText(
getApplicationContext(),
R.string.make_sure_username_and_password_correct,
Toast.LENGTH_LONG).show();
// showDialog(MAKE_SURE_USERNAME_AND_PASSWORD_CORRECT);
}
});
} else {
/*
* if result not equal to authentication failed, result
* is equal to friend and group list of the user 0: is
* for friends, 1: is for groups
*/
handler.post(new Runnable() {
public void run() {
Intent i = new Intent(LoggingIn.this,
MainActivity.class);
startActivity(i);
LoggingIn.this.finish();
}
});
}
}
};
loginThread.start();
}
}
How can I allow the users to automatically login with my service successfully?
I am working on Bluetooth application in android. I am writing an FindMe Server application on ICS. we have incorporated Gatt method in ICS as we are working on Low energy. I am not having FindMe profile API's currently so trying to use GATT method to complete the application. My application is registering to the GATT and it is doing service discovery as well but while doing the service discovery suddenly the bluetooth device is getting disconnected with my phone. can you please tell me what went wrong??
I have copied the code here....All the code is from a single file
public class FindMEService extends Service {
private static final String TAG = "FindMEService";
public static final int MSG_REG_GATT_SERVER_CONFIG = 300;
public static final int MSG_UNREG_GATT_SERVER_CONFIG = 301;
public static final int MSG_REG_GATT_SERVER_SUCCESS = 400;
public static final int MSG_REG_GATT_SERVER_FAILURE = 401;
public static final int MSG_UNREG_GATT_SERVER_SUCCESS = 500;
public static final int MSG_UNREG_GATT_SERVER_FAILURE = 501;
BluetoothGatt gattProfile;
private BluetoothGattAppConfiguration serverConfiguration = null;
InputStream raw = null;
public static ArrayList<Attribute> FMPHandleToAttributes;
public static int serverMinHandle = 0;
public static int serverMaxHandle = -1;
public static HashMap<String, List<Integer>> AttribTypeToHandle =
new HashMap<String, List<Integer>>();
final Messenger mMessenger = new Messenger(new handler());
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
public void onCreate() {
super.onCreate();
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
stopSelf();
return;
}
if (!mBluetoothAdapter.getProfileProxy(this, mBluetoothServiceListener,
BluetoothProfile.GATT)) {
stopSelf();
return;
}
populateFMPAttribTypeMap();
ReadXML readxml= new ReadXML();
raw = getResources().openRawResource(R.raw.fmpservice);
if (raw != null) {
readxml.parse(raw);
}
sendMessage(MSG_REG_GATT_SERVER_CONFIG,0);
}
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent,flags, startId);
return START_STICKY;
}
public void onDestroy() {
super.onDestroy();
sendMessage(MSG_UNREG_GATT_SERVER_CONFIG,0);
}
private final BluetoothProfile.ServiceListener mBluetoothServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.GATT) {
gattProfile = (BluetoothGatt) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.GATT) {
gattProfile = null;
}
}
};
private class handler extends Handler {
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_REG_GATT_SERVER_CONFIG :
registertoGATT();
break;
case MSG_UNREG_GATT_SERVER_CONFIG:
unregistertoGATT();
break;
case MSG_REG_GATT_SERVER_SUCCESS:
break;
case MSG_REG_GATT_SERVER_FAILURE:
break;
case MSG_UNREG_GATT_SERVER_SUCCESS:
break;
case MSG_UNREG_GATT_SERVER_FAILURE:
break;
}
}
}
/**
* Sending Messages to the Handler
* #param what
* #param value
*/
private void sendMessage(int what, int value) {
if (mMessenger == null) {
return;
}
try {
mMessenger.send(Message.obtain(null, what, value, 0));
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* Register to GATT
*/
private void registertoGATT() {
gattProfile.registerServerConfiguration("FMP",0xffff,bluetoothGattCallBack));
}
private void populateFMPAttribTypeMap() {
AttribTypeToHandle.put("00002800-0000-1000-8000-00805F9B34FB", new ArrayList<Integer>());
AttribTypeToHandle.put("00002803-0000-1000-8000-00805F9B34FB", new ArrayList<Integer>());
}
/**
* Callback to handle application registration, unregistration events and other
* API requests coming from the client device.
*/
private final BluetoothGattCallback bluetoothGattCallBack = new BluetoothGattCallback() {
public void onGattAppConfigurationStatusChange(BluetoothGattAppConfiguration config,
int status) {
serverConfiguration = config;
switch(status) {
case BluetoothGatt.GATT_CONFIG_REGISTRATION_SUCCESS:
sendMessage(MSG_REG_GATT_SERVER_SUCCESS, 0);
break;
case BluetoothGatt.GATT_CONFIG_REGISTRATION_FAILURE:
sendMessage(MSG_REG_GATT_SERVER_FAILURE, 0);
break;
case BluetoothGatt.GATT_CONFIG_UNREGISTRATION_SUCCESS:
sendMessage(MSG_UNREG_GATT_SERVER_SUCCESS, 0);
break;
case BluetoothGatt.GATT_CONFIG_UNREGISTRATION_FAILURE:
sendMessage(MSG_UNREG_GATT_SERVER_FAILURE, 0);
break;
}
}
public void onGattActionComplete(String action, int status) {
Log.d(TAG, "FindMEService : onGattActionComplete: " + action + "Status: " + status);
}
/**
* Processes the Discover Primary Services Request from client and sends the response
* to the client.
*/
public void onGattDiscoverPrimaryServiceRequest(BluetoothGattAppConfiguration config,
int startHandle, int endHandle, int requestHandle) {
System.out.println("FindMEService : onGattDiscoverPrimaryServiceRequest");
}
/**
* Processes the Discover Primary Services by UUID Request from client and sends the
* response to the client.
*/
public void onGattDiscoverPrimaryServiceByUuidRequest(BluetoothGattAppConfiguration config,
int startHandle, int endHandle, ParcelUuid uuid, int requestHandle) {
int j, k, hdlFoundStatus =0;
int startAttrHdl = 0, endAttrHdl = 0;
int status = BluetoothGatt.ATT_ECODE_ATTR_NOT_FOUND;
boolean retVal;
List<Integer> hndlList = null;
if(AttribTypeToHandle != null) {
for(Map.Entry<String, List<Integer>> entry : AttribTypeToHandle.entrySet()) {
if("00002800-0000-1000-8000-00805F9B34FB".
equalsIgnoreCase(entry.getKey().toString())) {
//List of primary service handles
hndlList = entry.getValue();
}
}
}
if(hndlList != null) {
for(j=0; j< hndlList.size(); j++) {
int handle = hndlList.get(j);
if(handle >= 0) {
if((handle >= startHandle) && (handle <= endHandle)){
if(FMPHandleToAttributes != null) {
for(k=0; k<FMPHandleToAttributes.size(); k++) {
if(handle ==FMPHandleToAttributes.get(k).handle) {
Attribute attr = FMPHandleToAttributes.get(k);
startAttrHdl = attr.startHandle;
endAttrHdl = attr.endHandle;
if(attr.uuid != null &&
attr.uuid.equalsIgnoreCase(uuid.toString())) {
Log.d(TAG, "Primary Handle with UUID available ::");
hdlFoundStatus = 1;
status = BluetoothGatt.GATT_SUCCESS;
break;
}
}
}
}
}
}
if(hdlFoundStatus == 1) {
Log.d(TAG, "Primary Handle found, success ::");
status = BluetoothGatt.GATT_SUCCESS;
break;
}
if(j == (hndlList.size()-1)) {
Log.d(TAG, "Primary Handle not found, failure ::");
status = BluetoothGatt.ATT_ECODE_ATTR_NOT_FOUND;
break;
}
}
}
retVal = gattProfile.discoverPrimaryServiceByUuidResponse(config, requestHandle, status,
startAttrHdl, endAttrHdl, uuid);
}
/**
* Processes the Find Included Services Request from client and sends the response
* to the client.
*/
public void onGattFindIncludedServiceRequest(BluetoothGattAppConfiguration config,
int startHandle, int endHandle, int requestHandle) {
System.out.println("FindMEService : onGattFindIncludedServiceRequest");
}
/**
* Processes the Discover Characteristic Descriptors Request from client and sends the
* response to the client.
*/
public void onGattDiscoverCharacteristicDescriptorRequest(BluetoothGattAppConfiguration
config, int startHandle, int endHandle, int requestHandle) {
System.out.println("FindMEService : onGattDiscoverCharacteristicDescriptorRequest");
}
/**
* Processes the Discover Characteristics Request from client and sends the response
* to the client.
*/
public void onGattDiscoverCharacteristicRequest(BluetoothGattAppConfiguration config,
int startHandle, int endHandle, int requestHandle) {
System.out.println("FindMEService : onGattDiscoverCharacteristicRequest");
}
/**
* Processes the Read By Attribute Type Request from client and sends the response
* to the client.
*/
public void onGattReadByTypeRequest(BluetoothGattAppConfiguration config, ParcelUuid uuid,
int startHandle, int endHandle, String authentication, int requestHandle) {
System.out.println("FindMEService : onGattReadByTypeRequest");
}
/**
* Processes the Read Request from client and sends the response
* to the client.
*/
public void onGattReadRequest(BluetoothGattAppConfiguration config, int handle,
String authentication, int requestHandle) {
System.out.println("FindMEService : onGattReadRequest");
}
/**
* Processes the Write Request from client and sends the response
* to the client.
*/
public void onGattReliableWriteRequest(BluetoothGattAppConfiguration config, int handle,
byte value[], String authentication, int sessionHandle,
int requestHandle) {
System.out.println("FindMEService : onGattReliableWriteRequest");
}
/**
* Processes the Write Request from client and sends the response
* to the client.
*/
public void onGattWriteRequest(BluetoothGattAppConfiguration config, int handle,
byte value[], String authentication) {
System.out.println("FindMEService : onGattWriteRequest");
}
public void onGattSetClientConfigDescriptor(BluetoothGattAppConfiguration config,
int handle, byte[] value, int sessionHandle) {
System.out.println("FindMEService : onGattSetClientConfigDescriptor");
}
};
// Unregister Gatt server application through Bluetooth Gatt API.
private void unregistertoGATT() {
Log.d(TAG, "FindMEService : Unregister Server config called::");
gattProfile.unregisterServerConfiguration(serverConfiguration);
}
}
Were you able to find the root cause. Since you have not posted yet so i am assumming may be you did not.
From the shared source code, it is evident that your application uses BT-Low-energy stack from one of the many vendors like qualcomm, broadcomm and CSR. It would be helpful for you to understand the real reason for disconnect by actually taking the Air Sniffed logs of the scenario.
Your FindMeService looks correct to me. however I personally would like to see Air Sniff logs.
Thanks and Cheers !!
i am getting errors/exceptions on my service which i use to stream a shoutcast.
I would like to show an error on ui thread if any exception is generated such as if music stream is not working.
How can this be done , Here is my current code.
UI Class :
private OnClickListener btnStopListener = new OnClickListener() {
public void onClick(View v) {
if (MyService.isRunning()) {
doUnbindService();
stopService(new Intent(MainActivity.this, MyService.class));
btnStop.setBackgroundResource(R.drawable.player_play);
barLoader.setVisibility(View.GONE);
} else {
Intent intent = new Intent(MainActivity.this, MyService.class);
intent.putExtra("streamUrl", strLinkUrl);
startService(intent);
doBindService();
}
}
};
void doBindService() {
bindService(new Intent(this, MyService.class), mConnection,
Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// If we have received the service, and hence registered with it,
// then now is the time to unregister.
if (mService != null) {
try {
Message msg = Message.obtain(null,
MyService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service has
// crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
Service Class :
public class MyService extends Service implements
OnPreparedListener,OnErrorListener{
public String txtBTitle, txtBChannel, txtBDescription, txtBTitleNow, txtBImageUrl ,txtBLinkUrl;private NotificationManager nm;
private static boolean isRunning = false;
private Timer timer1 = new Timer();
String strTitle,strArtist;
MediaPlayer mediaPlayer = new MediaPlayer();
ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
int mValue = 0; // Holds last value set by a client.
static final int MSG_REGISTER_CLIENT = 1;
static final int MSG_UNREGISTER_CLIENT = 2;
static final int MSG_SET_INT_VALUE = 3;
static final int MSG_SET_STRING_VALUE = 4;
String strUrl;
final Messenger mMessenger = new Messenger(new IncomingHandler()); // Target we publish for clients to send messages to IncomingHandler.
#Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
class IncomingHandler extends Handler { // Handler of incoming messages from clients.
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_INT_VALUE:
// incrementby = msg.arg1;
break;
default:
super.handleMessage(msg);
}
}
}
private void sendMessageToUI(int intvaluetosend) {
for (int i=mClients.size()-1; i>=0; i--) {
try {
// Send data as an Integer
mClients.get(i).send(Message.obtain(null, MSG_SET_INT_VALUE, intvaluetosend, 0));
//Send data as a String
Bundle b = new Bundle();
b.putString("str1",strTitle+"\n"+strArtist);
b.putString("strUrl", strUrl);
Message msg = Message.obtain(null, MSG_SET_STRING_VALUE);
msg.setData(b);
mClients.get(i).send(msg);
} catch (RemoteException e) {
// The client is dead. Remove it from the list; we are going through the list from back to front so this is safe to do inside the loop.
mClients.remove(i);
}
}
}
#Override
public void onCreate() {
super.onCreate();
Log.i("MyService", "Service Started.");
showNotification();
isRunning = true;
}
private void showNotification() {
nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.service_started);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.icon, text, System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class).putExtra("fromWhere", "fromService"), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this, getText(R.string.service_label), text, contentIntent);
// Send the notification.
// We use a layout id because it is a unique number. We use it later to cancel.
nm.notify(R.string.service_started, notification);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
strUrl = intent.getExtras().getString("streamUrl");
Log.i("ZealDeveloper", strUrl);
if(!mediaPlayer.isPlaying()){
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mediaPlayer.setDataSource(strUrl);
mediaPlayer.setOnPreparedListener(this);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mediaPlayer.prepareAsync(); // might take long! (for buffering, etc)
// mediaPlayer.start();
}
timer1.scheduleAtFixedRate(new TimerTask(){ public void run() {onMetaParser();}
}, 0, 100000L);
Log.i("MyService", "Received start id " + startId + ": " + intent);
return START_STICKY; // run until explicitly stopped.
}
public static boolean isRunning()
{
return isRunning;
}
//Meta Parser
private void onMetaParser() {
// TODO Auto-generated method stub
URL url;
try {
url = new URL(strUrl);
IcyStreamMeta icy = new IcyStreamMeta(url);
try{
strTitle = icy.getTitle();
}
catch (NullPointerException e) {
strTitle="";
}
try{
strArtist = icy.getArtist();
}
catch (NullPointerException e) {
strArtist = "";
}
System.out.println("Title : " + strTitle + strUrl);
System.gc();
// txtTitleNow.setText(T);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer.isPlaying()) {
mediaPlayer.release();
}
if (timer1 != null) {timer1.cancel();
Log.i("ZealDeveloper", "Timer 1 Cancled");}
nm.cancel(R.string.service_started); // Cancel the persistent notification.
Log.i("MyService", "Service Stopped.");
isRunning = false;
try {
timer1.cancel();
mediaPlayer.release();
mediaPlayer = null;
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onPrepared(MediaPlayer mp) {
// TODO Auto-generated method stub
mp.start();
sendMessageToUI(0);
}
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// TODO Auto-generated method stub
mediaPlayer.release();
timer1.cancel();
return false;}}
Stack Trace :
01-30 11:03:32.470: W/System.err(311): java.io.FileNotFoundException: http://xxxxxstream
01-30 11:03:32.480: E/MediaPlayer(311): error (1, -1004)
01-30 11:03:32.490: W/System.err(311): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:1162)
01-30 11:03:32.500: W/System.err(311): at com.sikhnetRadio.test.IcyStreamMeta.retreiveMetadata(IcyStreamMeta.java:91)
01-30 11:03:32.500: W/System.err(311): at com.sikhnetRadio.test.IcyStreamMeta.refreshMeta(IcyStreamMeta.java:79)
01-30 11:03:32.500: W/System.err(311): at com.sikhnetRadio.test.IcyStreamMeta.getMetadata(IcyStreamMeta.java:72)
01-30 11:03:32.500: W/System.err(311): at com.sikhnetRadio.test.IcyStreamMeta.getTitle(IcyStreamMeta.java:60)
01-30 11:03:32.500: W/System.err(311): at com.sikhnetRadio.test.MyService.onMetaParser(MyService.java:192)
01-30 11:03:32.510: W/System.err(311): at com.sikhnetRadio.test.MyService.access$0(MyService.java:182)
01-30 11:03:32.510: W/System.err(311): at com.sikhnetRadio.test.MyService$1.run(MyService.java:163)
01-30 11:03:32.510: W/System.err(311): at java.util.Timer$TimerImpl.run(Timer.java:289)
01-30 11:03:32.610: E/MediaPlayer(311): Error (1,-1004)
Two options
1) You can create traditional java type listener ( a interface that your Activity will override) and pass it to service through Ibinder interface .
2) Implement Broadcast receiver in your activity something similar to this (Here its been done for Intentservice i hope it won't be difficult for you to do the same for servie).
Timer is not thread-safe in UI. You sould use Handler instead.
As an example I would propose my utility class Clock. I use it myself in work. It is richly commented.
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import android.os.Handler;
import android.widget.TextView;
/**
* The class for creating and refreshing many different fields on different layouts,
* that can hold actual time and/or date in different formats
* The formats should be as in http://developer.android.com/reference/java/text/SimpleDateFormat.html.
* Only present and visible fields are being actualized, so there is no need to clean the clock list after closing an activity
*
* Examples of use:
*
* Clock.registerClock((TextView) findViewById(R.id.TimeField), "HH:mm");
* Clock.registerClock((TextView) findViewById(R.id.DateField), "d.M.yyyy EEE");
* Clock.start(10000L);
*
* #author Petr Gangnus
*/
public final class Clock {
/**
* the handler that works instead of timer and supports UI
*/
static private Handler handler = new Handler();
/**
* the interval of the time refreshing
*/
static private long refreshStep;
/**
* pairs TextView clockFace + time/date format
*/
private TextView clockFace;
private String format;
private Clock(TextView clockFace, String format){
this.clockFace=clockFace;
this.format=format;
}
// here is the list of views containing the visual timers that should be held actual
static private ArrayList<Clock> clocks=new ArrayList<Clock>();
/**
* fills all timer fields by actual time value, according to their formats.
*/
static private Runnable mUpdateTimeTask = new Runnable() {
public void run() {
for(Clock clock:clocks){
showActualTimeDate(clock);
}
handler.postDelayed(this,refreshStep);
}
};
//============================================ public members ====================================================================
/**
* add a clock to the list of updating clocks
* #param clockFace - the place where the time or date will be shown
* #param format - the format of the time/date
* #return
*/
public static boolean registerClock(TextView clockFace, String format){
if (clockFace==null) return false;
if(clocks.contains(clockFace)){
// old clockFace
clocks.get(clocks.indexOf(clockFace)).format=format;
} else {
// new clockFace
clocks.add(new Clock(clockFace, format));
}
return true;
}
/**
* remove a clock from the updating list
* #param clockFace
* #return
*/
public static boolean unRegisterClock(TextView clockFace){
if (clockFace==null) return false;
if(clocks.contains(clockFace)){
// found clockFace
clocks.remove(clocks.indexOf(clockFace));
} else {
// not found clockFace
return false;
}
return true;
}
/**
* put in the "place" the actual date/time in the appropriate "format"
* #param place
* #param format
*/
public static void showActualTimeDate(Clock clock){
if (clock.clockFace==null) return;
if (clock.clockFace.getVisibility()!=TextView.VISIBLE) return;
Date thisDate=new Date();
SimpleDateFormat df=new SimpleDateFormat(clock.format);
clock.clockFace.setText(df.format(thisDate));
}
/**
* start the ticking for all clocks
* #param step the tick interval
*/
public static void start(long step) {
refreshStep=step;
handler.removeCallbacks(mUpdateTimeTask);
handler.postDelayed(mUpdateTimeTask, 0);
}
/**
* Stopping ticking all clocks (not removing them)
* the calling could be put somewhere in onStop
*/
public static void stop() {
handler.removeCallbacks(mUpdateTimeTask);
}
}