So it works fine when the app is foreground and the first minute in the background. But after that the listener stops receiving gyro data. But the post thread and location listener keep working.
I tried of lot of things and I got nothing to work.
public final class Secret extends Service implements SensorEventListener, LocationListener {
private final String create = "CREATE TABLE IF NOT EXISTS data(lat float, lon float, x float, y float, z float, timestamp bigint)";
private SensorManager sensorManager;
private Database database;
private Location location;
#Override
public final void onCreate() {
/* Init sensor manager */
this.sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
}
#Override
public final int onStartCommand(final Intent intent, final int flags, final int startId) {
/* Initialize & load database */
this.database = new Database(openOrCreateDatabase("name_data.db", MODE_PRIVATE, null));
this.database.exec(this.create);
/* Init location manager */
final LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
try {
/* Register sensor change listener */
this.sensorManager.registerListener(this, this.sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), SensorManager.SENSOR_STATUS_ACCURACY_HIGH);
/* Register location change listener */
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 1, this);
} catch (final SecurityException e) {
Toast.makeText(this, "Failed to start: " + e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
}
/* Post data every 5 minutes */
final Thread thread = new Thread(new Runnable() {
#Override
public final void run() {
/* Secret instance */
final Secret secret = Secret.this;
/* 5 minutes */
final long time = 15000;
while (true) {
try {
/* Wait 5 minutes */
Thread.sleep(time);
/*
* Unregister sensor change listener,
* this way posting wont cause any database errors,
* and the sensor events wont time out when app is running in the background
*/
sensorManager.unregisterListener(secret);
/* Post database */
final int returned = post();
/* Register sensor change listener */
sensorManager.registerListener(secret, sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_STATUS_ACCURACY_HIGH);
switch (returned) {
case -1:
System.err.println("Database too small to post");
break;
case HttpURLConnection.HTTP_OK:
System.out.println("Posted database");
break;
default:
System.err.println("Post failed: " + returned);
}
} catch (final InterruptedException e) {
System.err.println("Failed to sleep");
e.printStackTrace();
}
}
}
});
/* Set thread priority and start */
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
return START_STICKY;
}
#Nullable
#Override
public final IBinder onBind(final Intent intent) {
return null;
}
#Override
public final void onSensorChanged(final SensorEvent event) {
System.out.println(System.currentTimeMillis());
/* Store data if location is known */
if (event == null || this.location == null) return;
float[] gyro = event.values;
/* Insert data into database */
this.database.exec("INSERT INTO data(lat, lon, x, y, z, timestamp) VALUES(" + this.location.getLatitude() + "," + this.location.getLongitude() + "," + gyro[0] + "," + gyro[1] + "," + gyro[2] + "," + System.currentTimeMillis() + ")");
}
#Override
public final void onAccuracyChanged(final Sensor sensor, final int accuracy) {
}
#Override
public final void onLocationChanged(final Location location) {
this.location = location;
}
#Override
public final void onStatusChanged(final String provider, final int status, final Bundle extras) {
}
#Override
public final void onProviderEnabled(final String provider) {
}
#Override
public final void onProviderDisabled(final String provider) {
}
/**
* Post database to server
*/
private final int post() {
/* Check if database is large enough (8 MB) to be pushed */
if (this.database.getFile().length() <= 8192) return -1;
/* Push database to server */
final int returned = Connection.post(this.database.getFile());
/* Check if database was pushed successfully */
if (returned == HttpURLConnection.HTTP_OK) {
/* Clear database if it was pushed to server successfully */
this.database.close();
this.database.getFile().delete();
/* Create new database */
this.database = new Database(openOrCreateDatabase("name_data.db", MODE_PRIVATE, null));
this.database.exec(this.create);
}
return returned;
}
}
OnDestroy gets triggered and then the sensor stops giving data. the Thread and location keep running.
Related
I have a Handler in my fragment that every 5 second add my location in SQLite.
What I need is to maintain this action in background when I open another fragment. How can I do this ?
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
int seconds = (int) (millis / 1000);
int minutes = seconds / 60;
seconds = seconds % 60;
Point p = ls.getPoint();
String X = Double.toString(p.getX());
String Y = Double.toString(p.getY());
db = new MySQLiteHelper(getActivity());
db.addLocation(new LocationXY(1,X,Y,"111"));
clearmap();
createPolyline();
timerHandler.postDelayed(this, 5000);
}
};
Well, what do both your fragments have in common? Their Activity! So run your handler in it.
As #Piyush pointed out, you should use an AsyncTask or a Service for background processes.
Yeah, as #Piyush and #Ricardo suggest - use a service with a Timer and a LocationListener.
Then invoke timer.scheduleAtFixedRate(yourSqliteUpdateMethod, 0, 5000); to update the database. See my service class below, which contains a lot of extra stuff, but you should be able to get what you want out of it...
public class TrackService extends Service {
/**
* Edit these values to adjust the LOCATION UPDATE polling interval
*/
private static final int LOCATION_INTERVAL_SECONDS = 5;
private static final float LOCATION_DISTANCE_METERS = 10f;
// Static timer variables
private static final long ONE_SECOND_IN_MILLIESECONDS = 1000;
// Create timer and set time as zero
private static Timer timer;
private long timeAsLong = 0;
/**
* Track and submission specific variables
*/
private List<LocationTrack> trackArrayList = new ArrayList<>();
private SubmissionSession submissionSession;
private long submissionID;
private boolean originAdded;
/**
* LocationManager and listener variables
*/
private LocationManager mLocationManager = null;
private LocationListener[] mLocationListeners = new LocationListener[]{
new LocationListener(LocationManager.GPS_PROVIDER)
};
/*private LocationListener[] mLocationListeners = new LocationListener[]{
new LocationListener(LocationManager.GPS_PROVIDER),
new LocationListener(LocationManager.PASSIVE_PROVIDER)
};*/
#Override
public void onCreate() {
//Log.d("onCreate");
timer = new Timer();
initializeLocationManager();
try {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, (LOCATION_INTERVAL_SECONDS * 1000), LOCATION_DISTANCE_METERS,
mLocationListeners[0]);
}
} catch (java.lang.SecurityException ex) {
Log.e("fail to request gps location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.e("gps provider does not exist, " + ex.getMessage());
}
/*try {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mLocationManager.requestLocationUpdates(
LocationManager.PASSIVE_PROVIDER, (LOCATION_INTERVAL_SECONDS * 1000), LOCATION_DISTANCE_METERS,
mLocationListeners[1]);
}
} catch (java.lang.SecurityException ex) {
Log.e("fail to request passive location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.e("passive provider does not exist " + ex.getMessage());
}*/
}
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
submissionID = prefs.getLong("SUBMISSION_ID", 0);
submissionSession = new SubmissionSession(submissionID);
trackArrayList = new Select().from(LocationTrack.class).where(Condition.column(LocationTrack$Table.SUBMISSIONID).eq(submissionID)).queryList();
originAdded = false;
timeAsLong = prefs.getLong("ELAPSED_TIME_AS_LONG", 0);
} else {
submissionID = intent.getLongExtra("SUB_ID", 0);
submissionSession = new SubmissionSession(submissionID);
// Stick in a reasonably sensible location to indicate to rest of app that a track's
// being captured - this is deleted on 1st real data point from GPS captured
trackArrayList.add(setLocationToLocationTrack(submissionSession.getLocation().getLocation()));
originAdded = true;
timeAsLong = intent.getLongExtra("START_TIME", 0);
}
// Start schedule at one second intervals, calling MainTask
timer.scheduleAtFixedRate(new TrackService.updateTimerAndBroadcastTask(), 0, ONE_SECOND_IN_MILLIESECONDS);
showNotification();
//super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
/**
* This method is necessary to increase the priority of the background services. It appeared that
* some devices, when running eBird Tracks and then used camera, the Tracks service was killed
* by Android due to limited resources. Upping the priority should reduce this.
*/
private void showNotification() {
final int NOTIFICATION_FLAG = 101;
final int ICON_SCALE_DIMENSION = 128;
Intent mainIntent = getPackageManager()
.getLaunchIntentForPackage(getPackageName())
.setPackage(null)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.lab_logo_square);
PendingIntent pendingMainIntent = PendingIntent.getActivity(this, 0,
mainIntent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_kiwi_bird1600)
.setLargeIcon(Bitmap.createScaledBitmap(icon, ICON_SCALE_DIMENSION, ICON_SCALE_DIMENSION, false))
.setContentText(getString(R.string.location_and_timer_services_running))
.setContentTitle(getString(R.string.app_name))
.setContentIntent(pendingMainIntent)
.setOngoing(true)
.build();
startForeground(NOTIFICATION_FLAG, notification);
}
#Override
public void onDestroy() {
// Need to call cancel AND purge when the user stops the list
timer.cancel();
timer.purge();
if (mLocationManager != null) {
for (int i = 0; i < mLocationListeners.length; i++) {
try {
mLocationManager.removeUpdates(mLocationListeners[i]);
} catch (Exception ex) {
Log.e("fail to remove location listeners, ignore", ex);
}
}
}
submissionSession.setTracks(trackArrayList);
super.onDestroy();
}
private void initializeLocationManager() {
if (mLocationManager == null) {
mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
}
}
private class LocationListener implements android.location.LocationListener {
Location mLastLocation;
public LocationListener(String provider) {
mLastLocation = new Location(provider);
}
#Override
public void onLocationChanged(Location location) {
mLastLocation.set(location);
// Code to add existing location object of site to tracks database on initiation
// to at least give one sensible point. Necessary for assigning the submission as
// an active recording - if no GPS picked up it was being overlooked in the methods
// used to display Checklist in progress throughout app. On first real capture of
// track data, it's deleted
if (originAdded) {
LocationTrack t = trackArrayList.get(0);
t.delete();
trackArrayList.remove(0);
originAdded = false;
}
trackArrayList.add(setLocationToLocationTrack(location));
sendDistanceToSubmissionActivity(Utils.routeDistance((ArrayList) trackArrayList));
}
#Override
public void onProviderDisabled(String provider) {
//Log.d("onProviderDisabled: " + provider);
}
#Override
public void onProviderEnabled(String provider) {
//Log.d("onProviderEnabled: " + provider);
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
//Log.d("onStatusChanged: " + provider);
}
}
// Send an Intent with an action named "tracks_update".
protected void sendDistanceToSubmissionActivity(double distance) {
Intent intent = new Intent("tracks_update");
intent.putExtra("distance", distance);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
/**
* Method to create Locationtrack object and save it to SQLite
*
* #param loc
* #return
*/
protected LocationTrack setLocationToLocationTrack(Location loc) {
LocationTrack l = new LocationTrack();
l.setSubmissionID(submissionID);
l.setLat(loc.getLatitude());
l.setLon(loc.getLongitude());
l.setAccuracy(loc.getAccuracy());
l.setTime(System.currentTimeMillis());
l.setValid(-1); // -1 signifies that the track is active (0 = invalid after editing, 1 = valid)
//Log.d(l.getLat() + " " + l.getLon() + " saved()");
l.save();
return l;
}
/**
* Inner main task, adds a second onto timeAsLong, then sends to SubmissionActivity
*/
private class updateTimerAndBroadcastTask extends TimerTask {
public void run() {
timeAsLong += ONE_SECOND_IN_MILLIESECONDS;
sendDistanceToSubmissionActivity(timeAsLong);
}
// Send an Intent with an action named "timer_update".
protected void sendDistanceToSubmissionActivity(long time) {
//Log.d();
Intent intent = new Intent("timer_update");
intent.putExtra("long_time", time);
LocalBroadcastManager.getInstance(TrackService.this).sendBroadcast(intent);
}
}
}
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'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
I have implemented an intent service in my application, the purpose of which is to monitor the device's shake on a continuous basis.According to the requirement,whenever a shake is detected ,this info should be sent to the app server.
When I started this implementation I had a dilemma on whether to use service or intent service but I chose the latter.Currently,I am able to detect the shake and this info is getting relayed to my app server,but sometimes from 15 min to 2 hour(post starting the app) I notice that this intent service no longer seems to detect any shakes(seems its getting killed on its own).
Here is my code:
public class TheftAlertService1 extends IntentService {
/* The connection to the hardware */
private SensorManager mySensorManager;
/* Here we store the current values of acceleration, one for each axis */
private float xAccel;
private float yAccel;
private float zAccel;
/* And here the previous ones */
private float xPreviousAccel;
private float yPreviousAccel;
private float zPreviousAccel;
private static int SyncRunningFlag = 0;
private double latitude; // latitude
private double longitude; // longitude
/* Used to suppress the first shaking */
private boolean firstUpdate = true;
/* What acceleration difference would we assume as a rapid movement? */
private final float shakeThreshold = .75f;
/* Has a shaking motion been started (one direction) */
private boolean shakeInitiated = false;
public TheftAlertService1() {
super("TheftAlertService1");
Log.d("TheftAlertService1", "inside constr");
// TODO Auto-generated constructor stub
}
#Override
protected void onHandleIntent(Intent arg0) {
mySensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); // (1)
mySensorManager.registerListener(mySensorEventListener,
mySensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL); // (2)
Log.d("TheftAlertService1", "Inside shake onHandleEvent");
}
/* The SensorEventListener lets us wire up to the real hardware events */
private final SensorEventListener mySensorEventListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent se) {
updateAccelParameters(se.values[0], se.values[1], se.values[2]); // (1)
if ((!shakeInitiated) && isAccelerationChanged()) { // (2)
shakeInitiated = true;
} else if ((shakeInitiated) && isAccelerationChanged()) { // (3)
executeShakeAction();
} else if ((shakeInitiated) && (!isAccelerationChanged())) { // (4)
shakeInitiated = false;
}
}
#Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
};
/* Store the acceleration values given by the sensor */
private void updateAccelParameters(float xNewAccel, float yNewAccel,
float zNewAccel) {
/*
* we have to suppress the first change of acceleration, it results from
* first values being initialized with 0
*/
if (firstUpdate) {
xPreviousAccel = xNewAccel;
yPreviousAccel = yNewAccel;
zPreviousAccel = zNewAccel;
firstUpdate = false;
} else {
xPreviousAccel = xAccel;
yPreviousAccel = yAccel;
zPreviousAccel = zAccel;
}
xAccel = xNewAccel;
yAccel = yNewAccel;
zAccel = zNewAccel;
}
/*
* If the values of acceleration have changed on at least two axises, we are
* probably in a shake motion
*/
private boolean isAccelerationChanged() {
float deltaX = Math.abs(xPreviousAccel - xAccel);
float deltaY = Math.abs(yPreviousAccel - yAccel);
float deltaZ = Math.abs(zPreviousAccel - zAccel);
return (deltaX > shakeThreshold && deltaY > shakeThreshold)
|| (deltaX > shakeThreshold && deltaZ > shakeThreshold)
|| (deltaY > shakeThreshold && deltaZ > shakeThreshold);
}
private void executeShakeAction() {
Log.d("TheftAlertService1", "inside executeShakeAction");
if (SyncRunningFlag == 0)
new SendTheftAlertToBackend().execute();
}
/******************************************************************************************************/
class SendTheftAlertToBackend extends AsyncTask<String, String, String> implements LocationListener{
JSONParser jsonParser = new JSONParser();
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
String device_id = tm.getDeviceId();
#Override
protected void onPreExecute() {
super.onPreExecute();
SyncRunningFlag = 1;
LocationManager locationManager;
Location location; // location
locationManager = (LocationManager) getApplicationContext().getSystemService(LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,0,0, this);
if (locationManager != null) {
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if (location != null) {
latitude = location.getLatitude();
longitude = location.getLongitude();
Log.d("TheftAlertService1", "Latitude - " +latitude + "longitude - "+longitude);
}
}
Log.d("TheftAlertService1", "Sending Theft Alert to app server");
}
protected String doInBackground(String... args) {
String theft_alert_time = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance()
.getTime());
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("device_id", device_id));
params.add(new BasicNameValuePair("theft_alert_time",theft_alert_time));
params.add(new BasicNameValuePair("theft_alert_longitude","lon -" + longitude));
params.add(new BasicNameValuePair("theft_alert_latitude","lat -" + latitude));
// getting JSON Object
JSONObject json = jsonParser.makeHttpRequest(
AppConstants.url_theft_alert, "POST", params);
try {
Log.d("TheftAlertService1,Response from server : ",
json.toString());
} catch (Exception e1) {
e1.printStackTrace();
SyncRunningFlag = 0;
}
// check for success tag
try {
int success = json.getInt(AppConstants.TAG_SUCCESS);
String tagDeviceId = json.getString(AppConstants.TAG_DEVICE_ID);
if (success == 1 && tagDeviceId.equals(device_id)) {
Log.d("TheftAlertService1",
"Theft Alert successfully logged in server");
SyncRunningFlag = 0;
} else {
Log.d("TheftAlertService1",
"Failed to log Theft Alert in server");
SyncRunningFlag = 0;
}
} catch (Exception e) {
e.printStackTrace();
SyncRunningFlag = 0;
}
return null;
}
protected void onPostExecute(String file_url) {
Log.d("TheftAlertService1", "inside onPost of async task");
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
}
#Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
}
}
This is what I have tried till now :
1) I have overridden onStartCommand and gave its return as START REDELIVER INTENT
2) I tried to make the intent service in foreground.
But nethier of these two options have 'sustained' the continuous background monitoring of shake on my device.
Following code I tried but in vain:
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
final int myID = 1234;
//The intent to launch when the user clicks the expanded notification
Intent intentService = new Intent(this, Staff.class);
intentService.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendIntent = PendingIntent.getActivity(this, 0, intentService, 0);
//This constructor is deprecated. Use Notification.Builder instead
Notification notice = new Notification(R.drawable.ic_launcher, "Ticker text", System.currentTimeMillis());
//This method is deprecated. Use Notification.Builder instead.
notice.setLatestEventInfo(this, "Title text", "Content text", pendIntent);
notice.flags |= Notification.FLAG_NO_CLEAR;
startForeground(myID, notice);
return START_REDELIVER_INTENT;
}
#Override
public void onDestroy() {
super.onDestroy();
Log.d("TheftAlertService1","Service got killed");
}
What is it that I am doing wrong? What should I do to make my intent service to run continuously in the background(and sense shakes forever).
Any help is appreciated.Thanks !
As Pankaj Kumar suggested I created my shake detection inside a service instead of an IntentService (as I was trying before,which used to fail to detect the shakes after sometime).I tested my service for 48 hours straight on devices(like nexus4,galaxy grand) and was able to detect shakes for the above tested period whenever the device was shaked.
To make the service live indefinitely I made the service foreground and returned START_STICKY as shown below.Following is the full code:
public class ShakeService extends Service {
/* The connection to the hardware */
private SensorManager mySensorManager;
/* Here we store the current values of acceleration, one for each axis */
private float xAccel;
private float yAccel;
private float zAccel;
/* And here the previous ones */
private float xPreviousAccel;
private float yPreviousAccel;
private float zPreviousAccel;
/* Used to suppress the first shaking */
private boolean firstUpdate = true;
/* What acceleration difference would we assume as a rapid movement? */
private final float shakeThreshold = .75f;
/* Has a shaking motion been started (one direction) */
private boolean shakeInitiated = false;
private BackgroundThread backGroundThread = null;
SensorEventListener mySensorEventListener;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
if (backGroundThread == null) {
backGroundThread = new BackgroundThread();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (backGroundThread == null)
backGroundThread = new BackgroundThread();
if ((backGroundThread.getState() == Thread.State.NEW) || (backGroundThread.getState() == Thread.State.TERMINATED)) {
if (backGroundThread.getState() == Thread.State.TERMINATED)
backGroundThread = new BackgroundThread();
backGroundThread.start();
Notification localNotification = new Notification(R.drawable.ic_launcher, "", System.currentTimeMillis());
localNotification.setLatestEventInfo(this,AppConstants.NOTIFICATION_NAME,AppConstants.NOTIFICATION_DESCRIPTION, null);
localNotification.flags = Notification.FLAG_NO_CLEAR;
startForeground(377982, localNotification);
mySensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mySensorManager.registerListener(mySensorEventListener,mySensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.SENSOR_DELAY_NORMAL);
Log.d("ShakeService", "Inside shake onStartCommand");
}
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
BackgroundThread.yield();
backGroundThread = null;
}
class BackgroundThread extends Thread {
#Override
public void run() {
/* The SensorEventListener lets us wire up to the real hardware events */
mySensorEventListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent se) {
updateAccelParameters(se.values[0], se.values[1], se.values[2]);
if ((!shakeInitiated) && isAccelerationChanged()) {
shakeInitiated = true;
} else if ((shakeInitiated) && isAccelerationChanged()) {
executeShakeAction();
} else if ((shakeInitiated) && (!isAccelerationChanged())) {
shakeInitiated = false;
}
}
#Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
}
/* Store the acceleration values given by the sensor */
private void updateAccelParameters(float xNewAccel, float yNewAccel,float zNewAccel) {
/*
* we have to suppress the first change of acceleration, it results from
* first values being initialized with 0
*/
if (firstUpdate) {
xPreviousAccel = xNewAccel;
yPreviousAccel = yNewAccel;
zPreviousAccel = zNewAccel;
firstUpdate = false;
} else {
xPreviousAccel = xAccel;
yPreviousAccel = yAccel;
zPreviousAccel = zAccel;
}
xAccel = xNewAccel;
yAccel = yNewAccel;
zAccel = zNewAccel;
}
/*
* If the values of acceleration have changed on at least two axises, we are
* probably in a shake motion
*/
private boolean isAccelerationChanged() {
float deltaX = Math.abs(xPreviousAccel - xAccel);
float deltaY = Math.abs(yPreviousAccel - yAccel);
float deltaZ = Math.abs(zPreviousAccel - zAccel);
return (deltaX > shakeThreshold && deltaY > shakeThreshold) || (deltaX > shakeThreshold && deltaZ > shakeThreshold) || (deltaY > shakeThreshold && deltaZ > shakeThreshold);
}
private void executeShakeAction() {
Log.d("ShakeService", "inside executeShakeAction");
// Or do something like post the shake status to app server
}
};
}
}
}
newIntent(GcmIntentService.this,TheftAlertService1.class); startService(theftAlertIntent);
replace this with
newIntent(getApplicationContext(),TheftAlertService1.class); startService(theftAlertIntent);
Have the next problem, need a program to send the coordinates any hour to my server. Im create a taskmanager and can send the coordinates, buy only Work the first three or five times and died.
This is my code please how is my problem?
My MainActivity:
public class MainActivity extends Activity {
public int ingreso =0;
double latitud=0;
double longitud=0;
boolean isGPSEnabled = false;
boolean isNetworkEnabled = false;
public double lat1=0;
public double lon1=0;enter code here
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final InstallationId Miid = new InstallationId();
Alarmas mUIUpdater = new Alarmas(new Runnable() {
#Override
public void run() {
// do stuff ...
/* Use the LocationManager class to obtain GPS locations */
LocationManager mlocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
LocationListener mlocListener = new MyLocationListener();
isGPSEnabled = mlocManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
isNetworkEnabled = mlocManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (!isGPSEnabled && !isNetworkEnabled )
{
/*NO HAY GPS*/
Toast.makeText( getApplicationContext(),"El GPS se encuentra desactivado. Favor activarlo para determinar el Centro de embellecimiento mas cercano",Toast.LENGTH_LONG ).show();
Intent intentRedirectionGPSSettings = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
intentRedirectionGPSSettings.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivityForResult(intentRedirectionGPSSettings, 0);
}
else
{
if (isGPSEnabled)
{
mlocManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 0, 0, mlocListener);
}
else
{
if (isNetworkEnabled)
{
mlocManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, 0, 0, mlocListener);
}
}
}
lat1=((MyLocationListener) mlocListener).GetLatitud();
Log.d("Enviando datos", "es " + ((MyLocationListener) mlocListener).GetLatitud());
lon1=((MyLocationListener) mlocListener).GetLongitud();
String miurl="http://casoft.com.co/evvc/registro.php?idphone=" + Miid.id(getApplicationContext()) + "&lat1=" + lat1 + "&lon1=" + lon1;
new Tareas().execute(miurl);
mlocManager.removeUpdates(mlocListener);
}
});
mUIUpdater.startUpdates();
//new Tareas().execute("http://casoft.com.co/evvc/registro.php?idphone=1&lat1=1&lon1=2");
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public class MyLocationListener implements LocationListener
{
#Override
public void onLocationChanged(Location loc)
{
loc.getLatitude();
loc.getLongitude();
latitud = loc.getLatitude();
longitud= loc.getLongitude();
Log.d("Enviando datos", latitud + "es " + longitud);
}
#Override
public void onProviderDisabled(String provider)
{
ingreso=0;
}
#Override
public void onProviderEnabled(String provider)
{
Toast.makeText( getApplicationContext(),"Gps Enabled",Toast.LENGTH_SHORT).show();
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
}
public double GetLatitud()
{
return latitud;
}
public double GetLongitud()
{
return longitud;
}
}/* End of Class MyLocationListener */
public void Sael(View view) {
Intent i = new Intent(this, navegador.class );
startActivity(i);
}
}
This is to get the idphone:
public class InstallationId {
private static String sID = null;
private static final String INSTALLATION = "INSTALLATION";
public synchronized static String id(Context context) {
if (sID == null) {
File installation = new File(context.getFilesDir(), INSTALLATION);
try {
if (!installation.exists())
writeInstallationFile(installation);
sID = readInstallationFile(installation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return sID;
}
private static String readInstallationFile(File installation) throws IOException {
RandomAccessFile f = new RandomAccessFile(installation, "r");
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
f.close();
return new String(bytes);
}
private static void writeInstallationFile(File installation) throws IOException {
FileOutputStream out = new FileOutputStream(installation);
String id = UUID.randomUUID().toString();
out.write(id.getBytes());
out.close();
}
}
And this is the Alarm:
public class Alarmas {
private Handler mHandler = new Handler(); // TODO Don't know if this is created in the UI thread
private Runnable mStatusChecker;
private int UPDATE_INTERVAL = 9000;
/**
* Creates an UIUpdater object, that can be used to
* perform UIUpdates on a specified time interval.
*
* #param uiUpdater A runnable containing the update routine.
*/
public Alarmas(final Runnable uiUpdater){
mStatusChecker = new Runnable() {
#Override
public void run() {
// Run the passed runnable
uiUpdater.run();
// Re-run it after the update interval
mHandler.postDelayed(this, UPDATE_INTERVAL);
}
};
}
/**
* The same as the default constructor, but specifying the
* intended update interval.
*
* #param uiUpdater A runnable containing the update routine.
* #param interval The interval over which the routine
* should run (milliseconds).
*/
public Alarmas(Runnable uiUpdater, int interval){
this(uiUpdater);
UPDATE_INTERVAL = interval;
}
/**
* Starts the periodical update routine (mStatusChecker
* adds the callback to the handler).
*/
public void startUpdates(){
mStatusChecker.run();
}
/**
* Stops the periodical update routine from running,
* by removing the callback.
*/
public void stopUpdates(){
mHandler.removeCallbacks(mStatusChecker);
}
}
And this is the TASK
class Tareas extends AsyncTask<String, String, String>{
#Override
protected String doInBackground(String... uri) {
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response;
String responseString = null;
Log.d("Enviando datos", "Solicitando");
try {
response = httpclient.execute(new HttpGet(uri[0]));
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() == HttpStatus.SC_OK){
ByteArrayOutputStream out = new ByteArrayOutputStream();
response.getEntity().writeTo(out);
out.close();
responseString = out.toString();
} else{
//Closes the connection.
response.getEntity().getContent().close();
throw new IOException(statusLine.getReasonPhrase());
}
} catch (ClientProtocolException e) {
//TODO Handle problems..
} catch (IOException e) {
//TODO Handle problems..
}
return responseString;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
//Do anything with response..
}
}
Thanks for your help.
Pda: Im need this program Work all time and only send me the GPS report any hour, for testing have the UPDATE_INTERVAL any 9 seconds.
buy only Work the first three or five times and died.
That would be because Android terminated your process, in all likelihood.
Please use AlarmManager for work that should occur once an hour. Getting a location fix once per hour is a bit tricky -- I have a LocationPoller library that can help with that.