I am building an app that needs to send the user's location to a remote server (Pusher in this case). The goal is to update their location on a map in near real-time but only when they are on a job, otherwise the app will not need to track their location.
I need the location updates to remain active if they leave the activity where they accepted the job (and therefore were placed on the map) and if they leave the app altogether. Once they have reached their destination, I wish to stop this background tracking.
I have been looking at Android's Service
component but I am not sure if it is what I need for this. The updates should occur in the background indefinitely but only while the user is assigned to a Job (updates start when they accept a job, end when they reach their destination).
Would a Bound service be the best for this? If so, some code as it relates to this problem would be GREATLY appreciated, as much of what I can find is generic and does things like return random integers.
Here is the code I wish to execute in the background:
package com.example.locationtester;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.pusher.client.Pusher;
import com.pusher.client.PusherOptions;
import com.pusher.client.channel.PrivateChannel;
import com.pusher.client.channel.PrivateChannelEventListener;
import com.pusher.client.connection.ConnectionEventListener;
import com.pusher.client.connection.ConnectionState;
import com.pusher.client.connection.ConnectionStateChange;
import com.pusher.client.util.HttpAuthorizer;
import org.json.JSONException;
import org.json.JSONObject;
public class MainActivity extends AppCompatActivity {
private TextView mLatLabel;
private TextView mLongLabel;
private TextView mAccuracy;
private Double mLat;
private Double mLong;
private boolean isSubscribed = false;
private Pusher mPusher;
private PrivateChannel mChannel;
private static final long MIN_TIME_BW_UPDATES = 1000 * 60;
private static final String TAG = "GPSTest";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLatLabel = (TextView) findViewById(R.id.latLabel);
mLongLabel = (TextView) findViewById(R.id.longLabel);
mAccuracy = (TextView) findViewById(R.id.accuracyLabel);
HttpAuthorizer authorizer = new HttpAuthorizer("http://example.com");
PusherOptions options = new PusherOptions().setAuthorizer(authorizer).setEncrypted(true);
mPusher = new Pusher("PUSHER_API_KEY", options);
mPusher.connect(new ConnectionEventListener() {
@Override
public void onConnectionStateChange(ConnectionStateChange change) {
Log.d(TAG, "State changed to " + change.getCurrentState() +
" from " + change.getPreviousState());
}
@Override
public void onError(String message, String code, Exception e) {
Log.d(TAG, "There was a problem connecting! " + e.toString());
}
}, ConnectionState.ALL);
mChannel = mPusher.subscribePrivate("private-location", new PrivateChannelEventListener() {
@Override
public void onAuthenticationFailure(String message, Exception e) {
Log.e(TAG, "Error " + message);
}
@Override
public void onSubscriptionSucceeded(String channelName) {
Log.d(TAG, "Subscribed to " + channelName);
isSubscribed = true;
}
@Override
public void onEvent(String channelName, String eventName, String data) {
}
});
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
handleLocationUpdate(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Toast.makeText(getApplicationContext(), "GPS Status Changed " + provider, Toast.LENGTH_LONG).show();
}
@Override
public void onProviderEnabled(String provider) {
Toast.makeText(getApplicationContext(), "GPS Provider Enabled " + provider, Toast.LENGTH_LONG).show();
}
@Override
public void onProviderDisabled(String provider) {
}
};
// Register the listener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, 0, locationListener);
}
private void handleLocationUpdate(Location location) {
mLat = location.getLatitude();
mLong = location.getLongitude();
mLatLabel.setText("Long: " + location.getLongitude());
mLongLabel.setText("Lat: " + location.getLatitude());
mAccuracy.setText("Accuracy: " + location.getAccuracy() + " at " + location.getTime());
Log.d(TAG, mLat + "");
if (isSubscribed)
{
JSONObject json = new JSONObject();
try {
json.put("lat", mLat);
json.put("long", mLong);
json.put("time", location.getTime());
json.put("accuracy", location.getAccuracy());
mChannel.trigger("client-location-changed", json.toString());
} catch (JSONException e) {
Log.e(TAG, "Problem adding JSON");
}
}
}
}
UPDATE
This is what I came up with after switching to Google Play Service's Location API. I have tested leaving this activity (and the app in general) and everything keeps running smoothly, proving location updates until I click on the button to make them stop.
Feedback would be appreciated on this code:
public class MainActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
public static final String TAG = MainActivity.class.getSimpleName();
public static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
private boolean isSubscribed = false;
private Pusher mPusher;
private PrivateChannel mChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startTracking = (Button) findViewById(R.id.btnStartTracking);
Button stopTracking = (Button) findViewById(R.id.btnStopTracking);
final Button startActivity = (Button) findViewById(R.id.btnStartActivity);
HttpAuthorizer authorizer = new HttpAuthorizer("http://example.com");
PusherOptions options = new PusherOptions().setAuthorizer(authorizer).setEncrypted(true);
mPusher = new Pusher("API_KEY", options);
mPusher.connect(new ConnectionEventListener() {
@Override
public void onConnectionStateChange(ConnectionStateChange change) {
Log.d(TAG, "State changed to " + change.getCurrentState() +
" from " + change.getPreviousState());
}
@Override
public void onError(String message, String code, Exception e) {
Log.d(TAG, "There was a problem connecting! " + e.toString());
}
}, ConnectionState.ALL);
mChannel = mPusher.subscribePrivate("private-location", new PrivateChannelEventListener() {
@Override
public void onAuthenticationFailure(String message, Exception e) {
Log.e(TAG, "Error " + message);
}
@Override
public void onSubscriptionSucceeded(String channelName) {
Log.d(TAG, "Subscribed to " + channelName);
isSubscribed = true;
}
@Override
public void onEvent(String channelName, String eventName, String data) {
}
});
// Build the Google Api Client and set the API for LocationServices
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
// Create the LocationRequest object
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(20 * 1000) // 3 seconds, in MS
.setFastestInterval(1000); // 1 second, in MS
startTracking.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mGoogleApiClient.isConnected())
{
mGoogleApiClient.connect();
}
}
});
stopTracking.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mGoogleApiClient.isConnected())
{
mGoogleApiClient.disconnect();
}
}
});
startActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), Activity2.class);
startActivity(intent);
}
});
}
@Override
protected void onResume()
{
super.onResume();
if (!mGoogleApiClient.isConnected())
{
mGoogleApiClient.connect();
}
}
@Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Location services connected");
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}
private void handleNewLocation(Location location) {
Log.d(TAG, location.toString());
double currentLatitude = location.getLatitude();
double currentLongitude = location.getLongitude();
LatLng latLng = new LatLng(currentLatitude, currentLongitude);
int speed = (int) (location.getSpeed() * 2.2369);
if (isSubscribed) {
JSONObject json = new JSONObject();
try {
json.put("lat", currentLatitude);
json.put("long", currentLongitude);
json.put("time", location.getTime());
json.put("accuracy", location.getAccuracy());
json.put("speed", speed);
json.put("latLng", latLng);
mChannel.trigger("client-location-changed", json.toString());
} catch (JSONException e) {
Log.e(TAG, "Problem adding JSON");
}
}
}
@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Location services suspended. Please reconnect");
}
@Override
public void onLocationChanged(Location location) {
handleNewLocation(location);
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} else {
Log.i(TAG, "Location services connection failed with code " + connectionResult.getErrorCode());
}
}
}
One option is to ditch the LocationManager API and shift to the FusedLocationProviderAPI.
The FusedLocationProviderAPI allows you to request intermittent Location Updates while making location requests in the most efficient manager.
Some code that could help "point" you in the right direction would look like this:
And then you would have a Background Service that you would register in the above GPS Plotter:
}