I am trying to use the non-blocking FusedLocationProvider API as a blocking one. Here's what I am doing (roughly):
- Start an AsyncTask
- In the background thread, connect to the PlayServices/FusedProvider API. There is a blocking method with a timeout available for this.
- Using the above API, request a location update. The API will calculate a the location using the device and trigger a callback which executes on the caller thread.
- Wait for the callback using
countDownLatch.await(timeOut,timeUnit)
- When the Fused API triggers the callback, retrieve the location and do a
countDownLatch.countdown()
, this will release the latch. - Return the location.
I need a way to block since I may need to calculate the location and work with it, at any point of time. The above approach is working but I am seeking a better way since the CountDownLatch is being triggered from the same thread it is blocking. I don't know whether I am committing sacrilege/sheer dumbery by doing this.
Here's my code:
public class BlockingLocationProvider implements ConnectionCallbacks,
OnConnectionFailedListener, LocationListener {
private static final String TAG = "BlockingLocationProvider";
private LocationInfo mLocInfoRow;
private long mCallTime;
private Double mDel;
private GoogleApiClient mGoogleApiClient;
private Location mLocation;
private LocationRequest locationRequest;
private boolean mLocationCalculated = false;
private CountDownLatch latch;
private Context context;
private ConnectionResult gApiConnResult;
@Override
public void onConnectionFailed(ConnectionResult arg0) {
Logger.error(TAG, "Google play services error while getting location.");
mLocInfoRow = new LocationInfo().timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_G_PLAY).statusCode(arg0.getErrorCode());
mLocation = null;
mLocationCalculated = true;
latch.countDown();
}
@Override
public void onConnected(Bundle arg0) {
locationRequest = new LocationRequest();
locationRequest.setInterval(0).setNumUpdates(1)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setExpirationDuration(2000);
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, locationRequest, this);
}
@Override
public void onConnectionSuspended(int arg0) {
Logger.warning(TAG, "Can't handle suspension.");
mLocInfoRow = new LocationInfo().timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_UNKNOWN).statusCode(-200);
latch.countDown();
}
@Override
public void onLocationChanged(Location arg0) {
mLocation = arg0;
if (mLocation == null) {
Logger.debug(TAG, "Fused provider returned null");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_FUSED_NULL).statusCode(-3);
} else {
Logger.debug(TAG, "Got a location from fused provider.");
mLocInfoRow = new LocationInfo().timestamp(mLocation.getTime())
.lat(mLocation.getLatitude()).lng(mLocation.getLongitude())
.travelSpeed(mLocation.getSpeed())
.altitude(mLocation.getAltitude())
.acc(mLocation.getAccuracy()).provider("fused")
.status("OK").statusCode(0);
}
mLocationCalculated = true;
latch.countDown();
}
public LocationInfo getLocationInfo(Context ctx) {
this.context = ctx;
calcLocation();
return mLocInfoRow;
}
public Location getLocation(Context ctx) {
this.context = ctx;
calcLocation();
return mLocation;
}
// This should block
public void calcLocation() {
if (Thread.currentThread().getName().equals("main")) {
throw new RuntimeException("Cannot run " + TAG + " on UI thread.");
}
latch = new CountDownLatch(1);
/* To figure how long it takes to calc location */
mCallTime = System.currentTimeMillis();
Logger.verbose(TAG, "Checking play services.");
// First check play services
int playServicesAvailable = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
// whoopsie!
if (playServicesAvailable != ConnectionResult.SUCCESS) {
Logger.error(TAG,
"Google play services error while getting location.");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_G_PLAY)
.statusCode(playServicesAvailable);
mLocation = null;
return;
}
Logger.verbose(TAG, "Checking GPS.");
// Then check GPS enabled or not
if (!isGpsEnabled(context)) {
Logger.error(TAG,
"User has disabled GPS. Unable to provide location updates.");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_NO_GPS).statusCode(-2);
mLocation = null;
return;
}
Logger.verbose(TAG, "Connecting to play services.");
// Then connect to play services client
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
if (!(mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting())) {
gApiConnResult = mGoogleApiClient.blockingConnect(5,
TimeUnit.SECONDS);
}
boolean timeout = false;
if (gApiConnResult.isSuccess()) {
try {
Logger.warning(TAG, "Waiting latch on location request...");
timeout = !latch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (timeout) {
Logger.warning(TAG, "Latch timeout!");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_TIMEOUT).statusCode(-100);
} else {
Logger.debug(TAG, "Latch triggered!");
}
} else {
Logger.warning(TAG, "gApi connect timeout!");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_TIMEOUT).statusCode(-300);
}
mDel = (Double.valueOf(System.currentTimeMillis() - mCallTime) / 1000);
Logger.debug(TAG, "Took " + mDel + " seconds for location update.");
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
private static boolean isGpsEnabled(final Context ctx) {
LocationManager lm = (LocationManager) ctx
.getSystemService(Context.LOCATION_SERVICE);
return lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
}
Is there any strategy apart from a latch on an infinite loop & flags to block in this case? Thanks.