package com.example.android.geofence; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; import com.google.android.gms.location.LocationClient; import com.google.android.gms.location.LocationStatusCodes; import com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import java.util.Arrays; import java.util.List; /** * Class for connecting to Location Services and removing geofences. * <p> * <b> * Note: Clients must ensure that Google Play services is available before removing geofences. * </b> Use GooglePlayServicesUtil.isGooglePlayServicesAvailable() to check. * <p> * To use a GeofenceRemover, instantiate it, then call either RemoveGeofencesById() or * RemoveGeofencesByIntent(). Everything else is done automatically. * */ public class GeofenceRemover implements ConnectionCallbacks, OnConnectionFailedListener, OnRemoveGeofencesResultListener { // Storage for a context from the calling client private Context mContext; // Stores the current list of geofences private List<String> mCurrentGeofenceIds; // Stores the current instantiation of the location client private LocationClient mLocationClient; // The PendingIntent sent in removeGeofencesByIntent private PendingIntent mCurrentIntent; /* * Record the type of removal. This allows continueRemoveGeofences to call the appropriate * removal request method. */ private GeofenceUtils.REMOVE_TYPE mRequestType; /* * Flag that indicates whether an add or remove request is underway. Check this * flag before attempting to start a new request. */ private boolean mInProgress; /** * Construct a GeofenceRemover for the current Context * * @param context A valid Context */ public GeofenceRemover(Context context) { // Save the context mContext = context; // Initialize the globals to null mCurrentGeofenceIds = null; mLocationClient = null; mInProgress = false; } /** * Set the "in progress" flag from a caller. This allows callers to re-set a * request that failed but was later fixed. * * @param flag Turn the in progress flag on or off. */ public void setInProgressFlag(boolean flag) { // Set the "In Progress" flag. mInProgress = flag; } /** * Get the current in progress status. * * @return The current value of the in progress flag. */ public boolean getInProgressFlag() { return mInProgress; } /** * Remove the geofences in a list of geofence IDs. To remove all current geofences associated * with a request, you can also call removeGeofencesByIntent. * <p> * <b>Note: The List must contain at least one ID, otherwise an Exception is thrown</b> * * @param geofenceIds A List of geofence IDs */ public void removeGeofencesById(List<String> geofenceIds) throws IllegalArgumentException, UnsupportedOperationException { // If the List is empty or null, throw an error immediately if ((null == geofenceIds) || (geofenceIds.size() == 0)) { throw new IllegalArgumentException(); // Set the request type, store the List, and request a location client connection. } else { // If a removal request is not already in progress, continue if (!mInProgress) { mRequestType = GeofenceUtils.REMOVE_TYPE.LIST; mCurrentGeofenceIds = geofenceIds; requestConnection(); // If a removal request is in progress, throw an exception } else { throw new UnsupportedOperationException(); } } } /** * Remove the geofences associated with a PendIntent. The PendingIntent is the one used * in the request to add the geofences; all geofences in that request are removed. To remove * a subset of those geofences, call removeGeofencesById(). * * @param requestIntent The PendingIntent used to request the geofences */ public void removeGeofencesByIntent(PendingIntent requestIntent) { // If a removal request is not in progress, continue if (!mInProgress) { // Set the request type, store the List, and request a location client connection. mRequestType = GeofenceUtils.REMOVE_TYPE.INTENT; mCurrentIntent = requestIntent; requestConnection(); // If a removal request is in progress, throw an exception } else { throw new UnsupportedOperationException(); } } /** * Once the connection is available, send a request to remove the Geofences. The method * signature used depends on which type of remove request was originally received. */ private void continueRemoveGeofences() { switch (mRequestType) { // If removeGeofencesByIntent was called case INTENT : mLocationClient.removeGeofences(mCurrentIntent, this); break; // If removeGeofencesById was called case LIST : mLocationClient.removeGeofences(mCurrentGeofenceIds, this); break; } } /** * Request a connection to Location Services. This call returns immediately, * but the request is not complete until onConnected() or onConnectionFailure() is called. */ private void requestConnection() { getLocationClient().connect(); } /** * Get the current location client, or create a new one if necessary. * * @return A LocationClient object */ private GooglePlayServicesClient getLocationClient() { if (mLocationClient == null) { mLocationClient = new LocationClient(mContext, this, this); } return mLocationClient; } /** * When the request to remove geofences by PendingIntent returns, handle the result. * * @param statusCode the code returned by Location Services * @param requestIntent The Intent used to request the removal. */ @Override public void onRemoveGeofencesByPendingIntentResult(int statusCode, PendingIntent requestIntent) { // Create a broadcast Intent that notifies other components of success or failure Intent broadcastIntent = new Intent(); // If removing the geofences was successful if (statusCode == LocationStatusCodes.SUCCESS) { // In debug mode, log the result Log.d(GeofenceUtils.APPTAG, mContext.getString(R.string.remove_geofences_intent_success)); // Set the action and add the result message broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCES_REMOVED); broadcastIntent.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, mContext.getString(R.string.remove_geofences_intent_success)); // If removing the geocodes failed } else { // Always log the error Log.e(GeofenceUtils.APPTAG, mContext.getString(R.string.remove_geofences_intent_failure, statusCode)); // Set the action and add the result message broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR); broadcastIntent.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, mContext.getString(R.string.remove_geofences_intent_failure, statusCode)); } // Broadcast the Intent to all components in this app LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadcastIntent); // Disconnect the location client requestDisconnection(); } /** * When the request to remove geofences by IDs returns, handle the result. * * @param statusCode The code returned by Location Services * @param geofenceRequestIds The IDs removed */ @Override public void onRemoveGeofencesByRequestIdsResult(int statusCode, String[] geofenceRequestIds) { // Create a broadcast Intent that notifies other components of success or failure Intent broadcastIntent = new Intent(); // Temp storage for messages String msg; // If removing the geocodes was successful if (LocationStatusCodes.SUCCESS == statusCode) { // Create a message containing all the geofence IDs removed. msg = mContext.getString(R.string.remove_geofences_id_success, Arrays.toString(geofenceRequestIds)); // In debug mode, log the result Log.d(GeofenceUtils.APPTAG, msg); // Create an Intent to broadcast to the app broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCES_REMOVED) .addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES) .putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, msg); } else { // If removing the geocodes failed /* * Create a message containing the error code and the list * of geofence IDs you tried to remove */ msg = mContext.getString( R.string.remove_geofences_id_failure, statusCode, Arrays.toString(geofenceRequestIds) ); // Log an error Log.e(GeofenceUtils.APPTAG, msg); // Create an Intent to broadcast to the app broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR) .addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES) .putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, msg); } // Broadcast whichever result occurred LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadcastIntent); // Disconnect the location client requestDisconnection(); } /** * Get a location client and disconnect from Location Services */ private void requestDisconnection() { // A request is no longer in progress mInProgress = false; getLocationClient().disconnect(); /* * If the request was done by PendingIntent, cancel the Intent. This prevents problems if * the client gets disconnected before the disconnection request finishes; the location * updates will still be cancelled. */ if (mRequestType == GeofenceUtils.REMOVE_TYPE.INTENT) { mCurrentIntent.cancel(); } } /* * Called by Location Services once the location client is connected. * * Continue by removing the requested geofences. */ @Override public void onConnected(Bundle arg0) { // If debugging, log the connection Log.d(GeofenceUtils.APPTAG, mContext.getString(R.string.connected)); // Continue the request to remove the geofences continueRemoveGeofences(); } /* * Called by Location Services if the connection is lost. */ @Override public void onDisconnected() { // A request is no longer in progress mInProgress = false; // In debug mode, log the disconnection Log.d(GeofenceUtils.APPTAG, mContext.getString(R.string.disconnected)); // Destroy the current location client mLocationClient = null; } /* * Implementation of OnConnectionFailedListener.onConnectionFailed * If a connection or disconnection request fails, report the error * connectionResult is passed in from Location Services */ @Override public void onConnectionFailed(ConnectionResult connectionResult) { // A request is no longer in progress mInProgress = false; /* * Google Play services can resolve some errors it detects. * If the error has a resolution, try sending an Intent to * start a Google Play services activity that can resolve * error. */ if (connectionResult.hasResolution()) { try { // Start an Activity that tries to resolve the error connectionResult.startResolutionForResult((Activity) mContext, GeofenceUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST); /* * Thrown if Google Play services canceled the original * PendingIntent */ } catch (SendIntentException e) { // Log the error e.printStackTrace(); } /* * If no resolution is available, put the error code in * an error Intent and broadcast it back to the main Activity. * The Activity then displays an error dialog. * is out of date. */ } else { Intent errorBroadcastIntent = new Intent(GeofenceUtils.ACTION_CONNECTION_ERROR); errorBroadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES) .putExtra(GeofenceUtils.EXTRA_CONNECTION_ERROR_CODE, connectionResult.getErrorCode()); LocalBroadcastManager.getInstance(mContext).sendBroadcast(errorBroadcastIntent); } } }