package io.nlopez.smartlocation.geofencing.providers;
import android.Manifest;
import android.app.Activity;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
import com.google.android.gms.location.LocationServices;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.nlopez.smartlocation.OnGeofencingTransitionListener;
import io.nlopez.smartlocation.geofencing.GeofencingProvider;
import io.nlopez.smartlocation.geofencing.GeofencingStore;
import io.nlopez.smartlocation.geofencing.model.GeofenceModel;
import io.nlopez.smartlocation.geofencing.utils.TransitionGeofence;
import io.nlopez.smartlocation.utils.GooglePlayServicesListener;
import io.nlopez.smartlocation.utils.Logger;
/**
* Created by mrm on 3/1/15.
*/
public class GeofencingGooglePlayServicesProvider implements GeofencingProvider, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<Status> {
public static final int RESULT_CODE = 10003;
public static final String BROADCAST_INTENT_ACTION = GeofencingGooglePlayServicesProvider.class.getCanonicalName() + ".GEOFENCE_TRANSITION";
public static final String GEOFENCES_EXTRA_ID = "geofences";
public static final String TRANSITION_EXTRA_ID = "transition";
public static final String LOCATION_EXTRA_ID = "location";
private final List<Geofence> geofencesToAdd = Collections.synchronizedList(new ArrayList<Geofence>());
private final List<String> geofencesToRemove = Collections.synchronizedList(new ArrayList<String>());
private GoogleApiClient client;
private Logger logger;
private OnGeofencingTransitionListener listener;
private GeofencingStore geofencingStore;
private Context context;
private PendingIntent pendingIntent;
private boolean stopped = false;
private final GooglePlayServicesListener googlePlayServicesListener;
public GeofencingGooglePlayServicesProvider() {
this(null);
}
public GeofencingGooglePlayServicesProvider(GooglePlayServicesListener playServicesListener) {
googlePlayServicesListener = playServicesListener;
}
@Override
public void init(@NonNull Context context, Logger logger) {
this.context = context;
this.logger = logger;
geofencingStore = new GeofencingStore(context);
this.client = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
client.connect();
pendingIntent = PendingIntent.getService(context, 0, new Intent(context, GeofencingService.class),
PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void addGeofence(GeofenceModel geofence) {
List<GeofenceModel> wrapperList = new ArrayList<>();
wrapperList.add(geofence);
addGeofences(wrapperList);
}
@Override
public void addGeofences(List<GeofenceModel> geofenceList) {
List<Geofence> convertedGeofences = new ArrayList<>();
for (GeofenceModel geofenceModel : geofenceList) {
geofencingStore.put(geofenceModel.getRequestId(), geofenceModel);
convertedGeofences.add(geofenceModel.toGeofence());
}
if (client.isConnected()) {
if (geofencesToAdd.size() > 0) {
convertedGeofences.addAll(geofencesToAdd);
geofencesToAdd.clear();
}
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationServices.GeofencingApi.addGeofences(client, convertedGeofences, pendingIntent);
} else {
for (GeofenceModel geofenceModel : geofenceList) {
geofencesToAdd.add(geofenceModel.toGeofence());
}
}
}
@Override
public void removeGeofence(String geofenceId) {
List<String> wrapperList = new ArrayList<>();
wrapperList.add(geofenceId);
removeGeofences(wrapperList);
}
@Override
public void removeGeofences(List<String> geofenceIds) {
for (String id : geofenceIds) {
geofencingStore.remove(id);
}
if (client.isConnected()) {
if (geofencesToRemove.size() > 0) {
geofenceIds.addAll(geofencesToRemove);
geofencesToRemove.clear();
}
LocationServices.GeofencingApi.removeGeofences(client, geofenceIds);
} else {
geofencesToRemove.addAll(geofenceIds);
}
}
@Override
public void start(OnGeofencingTransitionListener listener) {
this.listener = listener;
IntentFilter intentFilter = new IntentFilter(BROADCAST_INTENT_ACTION);
context.registerReceiver(geofencingReceiver, intentFilter);
if (!client.isConnected()) {
logger.d("still not connected - scheduled start when connection is ok");
} else if (stopped) {
client.connect();
stopped = false;
}
}
@Override
public void stop() {
logger.d("stop");
if (client.isConnected()) {
client.disconnect();
}
try {
context.unregisterReceiver(geofencingReceiver);
} catch (IllegalArgumentException e) {
logger.d("Silenced 'receiver not registered' stuff (calling stop more times than necessary did this)");
}
stopped = true;
}
@Override
public void onConnected(Bundle bundle) {
logger.d("onConnected");
// TODO wait until the connection is done and retry
if (client.isConnected()) {
if (geofencesToAdd.size() > 0) {
if (ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationServices.GeofencingApi.addGeofences(client, geofencesToAdd, pendingIntent);
geofencesToAdd.clear();
}
if (geofencesToRemove.size() > 0) {
LocationServices.GeofencingApi.removeGeofences(client, geofencesToRemove);
geofencesToRemove.clear();
}
}
if (googlePlayServicesListener != null) {
googlePlayServicesListener.onConnected(bundle);
}
}
@Override
public void onConnectionSuspended(int i) {
logger.d("onConnectionSuspended " + i);
if (googlePlayServicesListener != null) {
googlePlayServicesListener.onConnectionSuspended(i);
}
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
logger.d("onConnectionFailed");
if (googlePlayServicesListener != null) {
googlePlayServicesListener.onConnectionFailed(connectionResult);
}
}
private BroadcastReceiver geofencingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (BROADCAST_INTENT_ACTION.equals(intent.getAction()) && intent.hasExtra(GEOFENCES_EXTRA_ID)) {
logger.d("Received geofencing event");
final int transitionType = intent.getIntExtra(TRANSITION_EXTRA_ID, -1);
final List<String> geofencingIds = intent.getStringArrayListExtra(GEOFENCES_EXTRA_ID);
for (final String geofenceId : geofencingIds) {
// Get GeofenceModel
GeofenceModel geofenceModel = geofencingStore.get(geofenceId);
if (geofenceModel != null) {
listener.onGeofenceTransition(new TransitionGeofence(geofenceModel, transitionType));
} else {
logger.w("Tried to retrieve geofence " + geofenceId + " but it was not in the store");
}
}
}
}
};
public static class GeofencingService extends IntentService {
public GeofencingService() {
super(GeofencingService.class.getSimpleName());
}
@Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent != null && !geofencingEvent.hasError()) {
int transition = geofencingEvent.getGeofenceTransition();
// Broadcast an intent containing the geofencing info
Intent geofenceIntent = new Intent(BROADCAST_INTENT_ACTION);
geofenceIntent.putExtra(TRANSITION_EXTRA_ID, transition);
geofenceIntent.putExtra(LOCATION_EXTRA_ID, geofencingEvent.getTriggeringLocation());
ArrayList<String> geofencingIds = new ArrayList<>();
for (Geofence geofence : geofencingEvent.getTriggeringGeofences()) {
geofencingIds.add(geofence.getRequestId());
}
geofenceIntent.putStringArrayListExtra(GEOFENCES_EXTRA_ID, geofencingIds);
sendBroadcast(geofenceIntent);
}
}
}
@Override
public void onResult(@NonNull Status status) {
if (status.isSuccess()) {
logger.d("Geofencing update request successful");
} else if (status.hasResolution() && context instanceof Activity) {
logger.w(
"Unable to register, but we can solve this - will startActivityForResult expecting result code " + RESULT_CODE + " (if received, please try again)");
try {
status.startResolutionForResult((Activity) context, RESULT_CODE);
} catch (IntentSender.SendIntentException e) {
logger.e(e, "problem with startResolutionForResult");
}
} else {
// No recovery. Weep softly or inform the user.
logger.e("Registering failed: " + status.getStatusMessage());
}
}
}