package org.commcare.android.javarosa;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.ContextCompat;
import org.commcare.CommCareApplication;
import org.commcare.preferences.CommCarePreferences;
import org.commcare.utils.GeoUtils;
import java.util.ArrayList;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* Singleton that controls location acquisition for Poll Sensor XForm extension
*
* @author Phillip Mates (pmates@dimagi.com)
*/
@SuppressWarnings("ResourceType")
public enum PollSensorController implements LocationListener {
INSTANCE;
private LocationManager mLocationManager;
private final ArrayList<PollSensorAction> actions = new ArrayList<>();
private Timer timeoutTimer = new Timer();
void startLocationPolling(PollSensorAction action) {
synchronized (actions) {
actions.add(action);
}
resetTimeoutTimer();
// LocationManager needs to be dealt with in the main UI thread, so
// wrap GPS-checking logic in a Handler
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Start requesting GPS updates
Context context = CommCareApplication.instance();
mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
Set<String> providers = GeoUtils.evaluateProviders(mLocationManager);
if (providers.isEmpty()) {
context.registerReceiver(
new ProvidersChangedHandler(),
new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
);
// This thread can't take action on the UI, so instead send
// a message that actual activities notice and then display
// a dialog asking user to enable location access
Intent noGPSIntent = new Intent(GeoUtils.ACTION_CHECK_GPS_ENABLED);
context.sendStickyBroadcast(noGPSIntent);
}
requestLocationUpdates(providers);
}
});
}
private void resetTimeoutTimer() {
timeoutTimer.cancel();
timeoutTimer.purge();
timeoutTimer = new Timer();
}
/**
* Start polling for location, based on whatever providers are given, and set up a timeout
*
* @param providers Set of String objects that may contain
* LocationManager.GPS_PROVDER and/or LocationManager.NETWORK_PROVIDER
*/
private void requestLocationUpdates(Set<String> providers) {
if (providers.isEmpty()) {
stopLocationPolling();
} else {
for (String provider : providers) {
if (hasLocationPerms()) {
mLocationManager.requestLocationUpdates(provider, 0, 0, this);
}
}
// Cancel polling after maximum time is exceeded
timeoutTimer.schedule(new PollingTimeoutTask(),
CommCarePreferences.getGpsAutoCaptureTimeoutInMilliseconds());
}
}
/**
* If this action has a target node, update its value with the given location.
*/
@Override
public void onLocationChanged(Location location) {
synchronized (actions) {
if (location != null) {
for (PollSensorAction action : actions) {
action.updateReference(location);
}
if (location.getAccuracy() <= CommCarePreferences.getGpsAutoCaptureAccuracy()) {
stopLocationPolling();
}
}
}
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
private class ProvidersChangedHandler extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (mLocationManager == null) {
mLocationManager =
(LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
}
Set<String> providers = GeoUtils.evaluateProviders(mLocationManager);
requestLocationUpdates(providers);
}
}
private class PollingTimeoutTask extends TimerTask {
@Override
public void run() {
stopLocationPolling();
}
}
public void stopLocationPolling() {
synchronized (actions) {
actions.clear();
}
resetTimeoutTimer();
if (hasLocationPerms() && mLocationManager != null) {
mLocationManager.removeUpdates(this);
mLocationManager = null;
}
}
private static boolean hasLocationPerms() {
Context context = CommCareApplication.instance().getApplicationContext();
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
}