diff options
Diffstat (limited to 'docs/html/training/location/geofencing.jd')
-rw-r--r-- | docs/html/training/location/geofencing.jd | 1407 |
1 files changed, 1407 insertions, 0 deletions
diff --git a/docs/html/training/location/geofencing.jd b/docs/html/training/location/geofencing.jd new file mode 100644 index 0000000..748b6ec --- /dev/null +++ b/docs/html/training/location/geofencing.jd @@ -0,0 +1,1407 @@ +page.title=Creating and Monitoring Geofences + +trainingnavtop=true +@jd:body + + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#RequestGeofences">Request Geofence Monitoring</a></li> + <li><a href="#HandleGeofenceTransitions">Handle Geofence Transitions</a></li> + <li><a href="#StopGeofenceMonitoring">Stop Geofence Monitoring</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}google/play-services/setup.html">Setup Google Play Services SDK</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/GeofenceDetection.zip" class="button">Download the sample</a> + <p class="filename">GeofenceDetection.zip</p> +</div> + +</div> +</div> +<p> + Geofencing combines awareness of the user's current location with awareness of nearby + features, defined as the user's proximity to locations that may be of interest. To mark a + location of interest, you specify its latitude and longitude. To adjust the proximity for the + location, you add a radius. The latitude, longitude, and radius define a geofence. + You can have multiple active geofences at one time. +</p> +<p> + Location Services treats a geofences as an area rather than as a points and proximity. This + allows it to detect when the user enters or exits a geofence. For each geofence, you can ask + Location Services to send you entrance events or exit events or both. You can also limit the + duration of a geofence by specifying an expiration duration in milliseconds. After the geofence + expires, Location Services automatically removes it. +</p> +<!-- + Send geofences to Location Services + --> +<h2 id="RequestGeofences">Request Geofence Monitoring</h2> +<p> + The first step in requesting geofence monitoring is to request the necessary permission. + To use geofencing, your app must request + {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. To request this + permission, add the following element as a child element of the +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> + element: +</p> +<pre> +<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> +</pre> +<!-- Check for Google Play services --> +<h3>Check for Google Play Services</h3> +<p> + Location Services is part of the Google Play services APK. Since it's hard to anticipate the + state of the user's device, you should always check that the APK is installed before you attempt + to connect to Location Services. To check that the APK is installed, call +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#isGooglePlayServicesAvailable(android.content.Context)">GooglePlayServicesUtil.isGooglePlayServicesAvailable()</a></code>, + which returns one of the + integer result codes listed in the API reference documentation. If you encounter an error, + call +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#getErrorDialog(int, android.app.Activity, int)">GooglePlayServicesUtil.getErrorDialog()</a></code> + to retrieve localized dialog that prompts users to take the correct action, then display + the dialog in a {@link android.support.v4.app.DialogFragment}. The dialog may allow the + user to correct the problem, in which case Google Play services may send a result back to your + activity. To handle this result, override the method + {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()} + +</p> +<p class="note"> + <strong>Note:</strong> To make your app compatible with + platform version 1.6 and later, the activity that displays the + {@link android.support.v4.app.DialogFragment} must subclass + {@link android.support.v4.app.FragmentActivity} instead of {@link android.app.Activity}. Using + {@link android.support.v4.app.FragmentActivity} also allows you to call + {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager + getSupportFragmentManager()} to display the {@link android.support.v4.app.DialogFragment}. +</p> +<p> + Since you usually need to check for Google Play services in more than one place in your code, + define a method that encapsulates the check, then call the method before each connection + attempt. The following snippet contains all of the code required to check for Google + Play services: +</p> +<pre> +public class MainActivity extends FragmentActivity { + ... + // Global constants + /* + * Define a request code to send to Google Play services + * This code is returned in Activity.onActivityResult + */ + private final static int + CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000; + ... + // Define a DialogFragment that displays the error dialog + public static class ErrorDialogFragment extends DialogFragment { + // Global field to contain the error dialog + private Dialog mDialog; + ... + // Default constructor. Sets the dialog field to null + public ErrorDialogFragment() { + super(); + mDialog = null; + } + ... + // Set the dialog to display + public void setDialog(Dialog dialog) { + mDialog = dialog; + } + ... + // Return a Dialog to the DialogFragment. + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return mDialog; + } + ... + } + ... + /* + * Handle results returned to the FragmentActivity + * by Google Play services + */ + @Override + protected void onActivityResult( + int requestCode, int resultCode, Intent data) { + // Decide what to do based on the original request code + switch (requestCode) { + ... + case CONNECTION_FAILURE_RESOLUTION_REQUEST : + /* + * If the result code is Activity.RESULT_OK, try + * to connect again + */ + switch (resultCode) { + ... + case Activity.RESULT_OK : + /* + * Try the request again + */ + ... + break; + } + ... + } + ... + } + ... + private boolean servicesConnected() { + // Check that Google Play services is available + int resultCode = + GooglePlayServicesUtil. + isGooglePlayServicesAvailable(this); + // If Google Play services is available + if (ConnectionResult.SUCCESS == resultCode) { + // In debug mode, log the status + Log.d("Geofence Detection", + "Google Play services is available."); + // Continue + return true; + // Google Play services was not available for some reason + } else { + // Get the error code + int errorCode = connectionResult.getErrorCode(); + // Get the error dialog from Google Play services + Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( + errorCode, + this, + CONNECTION_FAILURE_RESOLUTION_REQUEST); + + // If Google Play services can provide an error dialog + if (errorDialog != null) { + // Create a new DialogFragment for the error dialog + ErrorDialogFragment errorFragment = + new ErrorDialogFragment(); + // Set the dialog in the DialogFragment + errorFragment.setDialog(errorDialog); + // Show the error dialog in the DialogFragment + errorFragment.show( + getSupportFragmentManager(), + "Geofence Detection"); + } + } + } + ... +} +</pre> +<p> + Snippets in the following sections call this method to verify that Google Play services is + available. +</p> +<p> + To use geofencing, start by defining the geofences you want to monitor. Although you usually + store geofence data in a local database or download it from the network, you need to send + a geofence to Location Services as an instance of +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>, + which you create with +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.Builder.html">Geofence.Builder</a></code>. + Each +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> + object contains the following information: +</p> +<dl> + <dt>Latitude, longitude, and radius</dt> + <dd> + Define a circular area for the geofence. Use the latitude and longitude to mark a location + of interest, and then use the radius to adjust how close the user needs to approach the + location before the geofence is detected. The larger the radius, the more likely the + user will trigger a geofence transition alert by approaching the geofence. For example, + providing a large radius for a geofencing app that turns on lights in the user's house as + the user returns home might cause the lights to go on even if the user is simply passing by. + </dd> + <dt>Expiration time</dt> + <dd> + How long the geofence should remain active. Once the expiration time is reached, Location + Services deletes the geofence. Most of the time, you should specify an expiration time, but + you may want to keep permanent geofences for the user's home or place of work. + </dd> + <dt>Transition type</dt> + <dd> + Location Services can detect when the user steps within the radius of the geofence ("entry") + and when the user steps outside the radius of the geofence ("exit"), or both. + </dd> + <dt>Geofence ID</dt> + <dd> + A string that is stored with the geofence. You should make this unique, so that you can + use it to remove a geofence from Location Services tracking. + </dd> +</dl> +<h3>Define geofence storage</h3> +<p> + A geofencing app needs to read and write geofence data to persistent storage. You shouldn't use +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> + objects to do this; instead, use storage techniques such as databases that can store groups of + related data. +</p> +<p> + As an example of storing geofence data, the following snippet defines two classes that use + the app's {@link android.content.SharedPreferences} instance for persistent storage. The class + {@code SimpleGeofence}, analogous to a database record, stores the + data for a single +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> + object in a "flattened" form. The class {@code SimpleGeofenceStore}, analogous to a database, + reads and writes {@code SimpleGeofence} data to the + {@link android.content.SharedPreferences} instance. +</p> +<pre> +public class MainActivity extends FragmentActivity { + ... + /** + * A single Geofence object, defined by its center and radius. + */ + public class SimpleGeofence { + // Instance variables + private final String mId; + private final double mLatitude; + private final double mLongitude; + private final float mRadius; + private long mExpirationDuration; + private int mTransitionType; + + /** + * @param geofenceId The Geofence's request ID + * @param latitude Latitude of the Geofence's center. + * @param longitude Longitude of the Geofence's center. + * @param radius Radius of the geofence circle. + * @param expiration Geofence expiration duration + * @param transition Type of Geofence transition. + */ + public SimpleGeofence( + String geofenceId, + double latitude, + double longitude, + float radius, + long expiration, + int transition) { + // Set the instance fields from the constructor + this.mId = geofenceId; + this.mLatitude = latitude; + this.mLongitude = longitude; + this.mRadius = radius; + this.mExpirationDuration = expiration; + this.mTransitionType = transition; + } + // Instance field getters + public String getId() { + return mId; + } + public double getLatitude() { + return mLatitude; + } + public double getLongitude() { + return mLongitude; + } + public float getRadius() { + return mRadius; + } + public long getExpirationDuration() { + return mExpirationDuration; + } + public int getTransitionType() { + return mTransitionType; + } + /** + * Creates a Location Services Geofence object from a + * SimpleGeofence. + * + * @return A Geofence object + */ + public Geofence toGeofence() { + // Build a new Geofence object + return new Geofence.Builder() + .setRequestId(getId()) + .setTransitionTypes(mTransitionType) + .setCircularRegion( + getLatitude(), getLongitude(), getRadius()) + .setExpirationDuration(mExpirationDuration) + .build(); + } + } + ... + /** + * Storage for geofence values, implemented in SharedPreferences. + */ + public class SimpleGeofenceStore { + // Keys for flattened geofences stored in SharedPreferences + public static final String KEY_LATITUDE = + "com.example.android.geofence.KEY_LATITUDE"; + public static final String KEY_LONGITUDE = + "com.example.android.geofence.KEY_LONGITUDE"; + public static final String KEY_RADIUS = + "com.example.android.geofence.KEY_RADIUS"; + public static final String KEY_EXPIRATION_DURATION = + "com.example.android.geofence.KEY_EXPIRATION_DURATION"; + public static final String KEY_TRANSITION_TYPE = + "com.example.android.geofence.KEY_TRANSITION_TYPE"; + // The prefix for flattened geofence keys + public static final String KEY_PREFIX = + "com.example.android.geofence.KEY"; + /* + * Invalid values, used to test geofence storage when + * retrieving geofences + */ + public static final long INVALID_LONG_VALUE = -999l; + public static final float INVALID_FLOAT_VALUE = -999.0f; + public static final int INVALID_INT_VALUE = -999; + // The SharedPreferences object in which geofences are stored + private final SharedPreferences mPrefs; + // The name of the SharedPreferences + private static final String SHARED_PREFERENCES = + "SharedPreferences"; + // Create the SharedPreferences storage with private access only + public SimpleGeofenceStore(Context context) { + mPrefs = + context.getSharedPreferences( + SHARED_PREFERENCES, + Context.MODE_PRIVATE); + } + /** + * Returns a stored geofence by its id, or returns {@code null} + * if it's not found. + * + * @param id The ID of a stored geofence + * @return A geofence defined by its center and radius. See + */ + public SimpleGeofence getGeofence(String id) { + /* + * Get the latitude for the geofence identified by id, or + * INVALID_FLOAT_VALUE if it doesn't exist + */ + double lat = mPrefs.getFloat( + getGeofenceFieldKey(id, KEY_LATITUDE), + INVALID_FLOAT_VALUE); + /* + * Get the longitude for the geofence identified by id, or + * INVALID_FLOAT_VALUE if it doesn't exist + */ + double lng = mPrefs.getFloat( + getGeofenceFieldKey(id, KEY_LONGITUDE), + INVALID_FLOAT_VALUE); + /* + * Get the radius for the geofence identified by id, or + * INVALID_FLOAT_VALUE if it doesn't exist + */ + float radius = mPrefs.getFloat( + getGeofenceFieldKey(id, KEY_RADIUS), + INVALID_FLOAT_VALUE); + /* + * Get the expiration duration for the geofence identified + * by id, or INVALID_LONG_VALUE if it doesn't exist + */ + long expirationDuration = mPrefs.getLong( + getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION), + INVALID_LONG_VALUE); + /* + * Get the transition type for the geofence identified by + * id, or INVALID_INT_VALUE if it doesn't exist + */ + int transitionType = mPrefs.getInt( + getGeofenceFieldKey(id, KEY_TRANSITION_TYPE), + INVALID_INT_VALUE); + // If none of the values is incorrect, return the object + if ( + lat != GeofenceUtils.INVALID_FLOAT_VALUE && + lng != GeofenceUtils.INVALID_FLOAT_VALUE && + radius != GeofenceUtils.INVALID_FLOAT_VALUE && + expirationDuration != + GeofenceUtils.INVALID_LONG_VALUE && + transitionType != GeofenceUtils.INVALID_INT_VALUE) { + + // Return a true Geofence object + return new SimpleGeofence( + id, lat, lng, radius, expirationDuration, + transitionType); + // Otherwise, return null. + } else { + return null; + } + } + /** + * Save a geofence. + * @param geofence The SimpleGeofence containing the + * values you want to save in SharedPreferences + */ + public void setGeofence(String id, SimpleGeofence geofence) { + /* + * Get a SharedPreferences editor instance. Among other + * things, SharedPreferences ensures that updates are atomic + * and non-concurrent + */ + Editor editor = mPrefs.edit(); + // Write the Geofence values to SharedPreferences + editor.putFloat( + getGeofenceFieldKey(id, KEY_LATITUDE), + (float) geofence.getLatitude()); + editor.putFloat( + getGeofenceFieldKey(id, KEY_LONGITUDE), + (float) geofence.getLongitude()); + editor.putFloat( + getGeofenceFieldKey(id, KEY_RADIUS), + geofence.getRadius()); + editor.putLong( + getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION), + geofence.getExpirationDuration()); + editor.putInt( + getGeofenceFieldKey(id, KEY_TRANSITION_TYPE), + geofence.getTransitionType()); + // Commit the changes + editor.commit(); + } + public void clearGeofence(String id) { + /* + * Remove a flattened geofence object from storage by + * removing all of its keys + */ + Editor editor = mPrefs.edit(); + editor.remove(getGeofenceFieldKey(id, KEY_LATITUDE)); + editor.remove(getGeofenceFieldKey(id, KEY_LONGITUDE)); + editor.remove(getGeofenceFieldKey(id, KEY_RADIUS)); + editor.remove(getGeofenceFieldKey(id, + KEY_EXPIRATION_DURATION)); + editor.remove(getGeofenceFieldKey(id, KEY_TRANSITION_TYPE)); + editor.commit(); + } + /** + * Given a Geofence object's ID and the name of a field + * (for example, KEY_LATITUDE), return the key name of the + * object's values in SharedPreferences. + * + * @param id The ID of a Geofence object + * @param fieldName The field represented by the key + * @return The full key name of a value in SharedPreferences + */ + private String getGeofenceFieldKey(String id, + String fieldName) { + return KEY_PREFIX + "_" + id + "_" + fieldName; + } + } + ... +} +</pre> +<h3>Create Geofence objects</h3> +<p> + The following snippet uses the {@code SimpleGeofence} and {@code SimpleGeofenceStore} classes + gets geofence data from the UI, stores it in {@code SimpleGeofence} objects, stores these + objects in a {@code SimpleGeofenceStore} object, and then creates +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> + objects: +</p> +<pre> +public class MainActivity extends FragmentActivity { + ... + /* + * Use to set an expiration time for a geofence. After this amount + * of time Location Services will stop tracking the geofence. + */ + private static final long SECONDS_PER_HOUR = 60; + private static final long MILLISECONDS_PER_SECOND = 1000; + private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12; + private static final long GEOFENCE_EXPIRATION_TIME = + GEOFENCE_EXPIRATION_IN_HOURS * + SECONDS_PER_HOUR * + MILLISECONDS_PER_SECOND; + ... + /* + * Handles to UI views containing geofence data + */ + // Handle to geofence 1 latitude in the UI + private EditText mLatitude1; + // Handle to geofence 1 longitude in the UI + private EditText mLongitude1; + // Handle to geofence 1 radius in the UI + private EditText mRadius1; + // Handle to geofence 2 latitude in the UI + private EditText mLatitude2; + // Handle to geofence 2 longitude in the UI + private EditText mLongitude2; + // Handle to geofence 2 radius in the UI + private EditText mRadius2; + /* + * Internal geofence objects for geofence 1 and 2 + */ + private SimpleGeofence mUIGeofence1; + private SimpleGeofence mUIGeofence2; + ... + // Internal List of Geofence objects + List<Geofence> mGeofenceList; + // Persistent storage for geofences + private SimpleGeofenceStore mGeofenceStorage; + ... + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + // Instantiate a new geofence storage area + mGeofenceStorage = new SimpleGeofenceStore(this); + + // Instantiate the current List of geofences + mCurrentGeofences = new ArrayList<Geofence>(); + } + ... + /** + * Get the geofence parameters for each geofence from the UI + * and add them to a List. + */ + public void createGeofences() { + /* + * Create an internal object to store the data. Set its + * ID to "1". This is a "flattened" object that contains + * a set of strings + */ + mUIGeofence1 = new SimpleGeofence( + "1", + Double.valueOf(mLatitude1.getText().toString()), + Double.valueOf(mLongitude1.getText().toString()), + Float.valueOf(mRadius1.getText().toString()), + GEOFENCE_EXPIRATION_TIME, + // This geofence records only entry transitions + Geofence.GEOFENCE_TRANSITION_ENTER); + // Store this flat version + mGeofenceStorage.setGeofence("1", mUIGeofence1); + // Create another internal object. Set its ID to "2" + mUIGeofence2 = new SimpleGeofence( + "2", + Double.valueOf(mLatitude2.getText().toString()), + Double.valueOf(mLongitude2.getText().toString()), + Float.valueOf(mRadius2.getText().toString()), + GEOFENCE_EXPIRATION_TIME, + // This geofence records both entry and exit transitions + Geofence.GEOFENCE_TRANSITION_ENTER | + Geofence.GEOFENCE_TRANSITION_EXIT); + // Store this flat version + mGeofenceStorage.setGeofence(2, mUIGeofence2); + mGeofenceList.add(mUIGeofence1.toGeofence()); + mGeofenceList.add(mUIGeofence2.toGeofence()); + } + ... +} +</pre> +<p> + In addition to the {@link java.util.List} of +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> + objects you want to monitor, you need to provide Location Services with the + {@link android.content.Intent} that it sends to your app when it detects geofence + transitions. +<h4>Define a Intent for geofence transitions</h4> +<p> + The {@link android.content.Intent} sent from Location Services can trigger various actions in + your app, but you should <i>not</i> have it start an activity or fragment, because components + should only become visible in response to a user action. In many cases, an + {@link android.app.IntentService} is a good way to handle the intent. An + {@link android.app.IntentService} can post a notification, do long-running background work, + send intents to other services, or send a broadcast intent. The following snippet shows how + how to define a {@link android.app.PendingIntent} that starts an + {@link android.app.IntentService}: +</p> +<pre> +public class MainActivity extends FragmentActivity { + ... + /* + * Create a PendingIntent that triggers an IntentService in your + * app when a geofence transition occurs. + */ + private PendingIntent getTransitionPendingIntent() { + // Create an explicit Intent + Intent intent = new Intent(this, + ReceiveTransitionsIntentService.class); + /* + * Return the PendingIntent + */ + return PendingIntent.getService( + this, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + ... +} +</pre> +<p> + Now you have all the code you need to send a request to monitor geofences to Location + Services. +</p> +<!-- Send the monitoring request --> +<h3 id="requestmonitoring">Send the monitoring request</h3> +<p> + Sending the monitoring request requires two asynchronous operations. The first operation gets a + location client for the request, and the second makes the request using the client. In both + cases, Location Services invokes a callback method when it finishes the operation. The best way + to handle these operations is to chain together the method calls. The following snippets + demonstrate how to set up an activity, define the methods, and call them in the proper order. +</p> +<p> + First, modify the activity's class definition to implement the necessary callback interfaces. + Add the following interfaces: +</p> +<dl> + <dt> +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html">ConnectionCallbacks</a></code> + </dt> + <dd> + Specifies methods that Location Services calls when a location client is connected or + disconnected. + </dd> + <dt> +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.OnConnectionFailedListener.html">OnConnectionFailedListener</a></code> + </dt> + <dd> + Specifies a method that Location Services calls if an error occurs while attempting to + connect the location client. + </dd> + <dt> +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html">OnAddGeofencesResultListener</a></code> + </dt> + <dd> + Specifies a method that Location Services calls once it has added the geofences. + </dd> +</dl> +<p> + For example: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... +} +</pre> +<h4>Start the request process</h4> +<p> + Next, define a method that starts the request process by connecting to Location Services. + Mark this as a request to add a geofence by setting a global variable. This allows you to + use the callback +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code> + to add geofences and to remove them, as described in succeeding sections. +</p> +<p> +<p> + To guard against race conditions that might arise if your app tries to start another request + before the first one finishes, define a boolean flag that tracks the state of the current + request: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + // Holds the location client + private LocationClient mLocationClient; + // Stores the PendingIntent used to request geofence monitoring + private PendingIntent mGeofenceRequestIntent; + // Defines the allowable request types. + public enum REQUEST_TYPE = {ADD} + private REQUEST_TYPE mRequestType; + // Flag that indicates if a request is underway. + private boolean mInProgress; + ... + @Override + protected void onCreate(Bundle savedInstanceState) { + ... + // Start with the request flag set to false + mInProgress = false; + ... + } + ... + /** + * Start a request for geofence monitoring by calling + * LocationClient.connect(). + */ + public void addGeofences() { + // Start a request to add geofences + mRequestType = ADD; + /* + * Test for Google Play services after setting the request type. + * If Google Play services isn't present, the proper request + * can be restarted. + */ + if (!servicesConnected()) { + return; + } + /* + * Create a new location client object. Since the current + * activity class implements ConnectionCallbacks and + * OnConnectionFailedListener, pass the current activity object + * as the listener for both parameters + */ + mLocationClient = new LocationClient(this, this, this) + // If a request is not already underway + if (!mInProgress) { + // Indicate that a request is underway + mInProgress = true; + // Request a connection from the client to Location Services + mLocationClient.connect(); + } else { + /* + * A request is already underway. You can handle + * this situation by disconnecting the client, + * re-setting the flag, and then re-trying the + * request. + */ + } + } + ... +} +</pre> +<h4>Send a request to add the geofences</h4> +<p> + In your implementation of +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code>, + call +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#addGeofences(java.util.List<com.google.android.gms.location.Geofence>, android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener)">LocationClient.addGeofences()</a></code>. + Notice that if the connection fails, +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code> + isn't called, and the request stops. +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /* + * Provide the implementation of ConnectionCallbacks.onConnected() + * Once the connection is available, send a request to add the + * Geofences + */ + @Override + private void onConnected(Bundle dataBundle) { + ... + switch (mRequestType) { + case ADD : + // Get the PendingIntent for the request + mTransitionPendingIntent = + getTransitionPendingIntent(); + // Send a request to add the current geofences + mLocationClient.addGeofences( + mCurrentGeofences, pendingIntent, this); + ... + } + } + ... +} +</pre> +<p> + Notice that +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#addGeofences(java.util.List<com.google.android.gms.location.Geofence>, android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener)">addGeofences()</a></code> + returns immediately, but the status of the request is indeterminate until Location Services + calls +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code> + Once this method is called, you can determine if the request was successful or not. +</p> +<h4>Check the result returned by Location Services</h4> +<p> + When Location Services invokes your implementation of the callback method +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code>, + indicating that the request is complete, examine the incoming status code. If the request + was successful, the geofences you requested are active. If the request was unsuccessful, + the geofences aren't active, and you need to re-try the request or report an error. For example: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /* + * Provide the implementation of + * OnAddGeofencesResultListener.onAddGeofencesResult. + * Handle the result of adding the geofences + * + */ + @Override + public void onAddGeofencesResult( + int statusCode, String[] geofenceRequestIds) { + // If adding the geofences was successful + if (LocationStatusCodes.SUCCESS == statusCode) { + /* + * Handle successful addition of geofences here. + * You can send out a broadcast intent or update the UI. + * geofences into the Intent's extended data. + */ + } else { + // If adding the geofences failed + /* + * Report errors here. + * You can log the error using Log.e() or update + * the UI. + */ + } + // Turn off the in progress flag and disconnect the client + mInProgress = false; + mLocationClient.disconnect(); + } + ... +} +</pre> +<!-- Handle disconnections --> +<h3>Handle disconnections</h3> +<p> + In some cases, Location Services may disconnect from the activity recognition client before + you call +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#disconnect()">disconnect()</a></code>. + To handle this situation, implement <code> +<a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onDisconnected()">onDisconnected()</a></code>. + In this method, set the request flag to indicate that a request is not in progress, and + delete the client: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /* + * Implement ConnectionCallbacks.onDisconnected() + * Called by Location Services once the location client is + * disconnected. + */ + @Override + public void onDisconnected() { + // Turn off the request flag + mInProgress = false; + // Destroy the current location client + mLocationClient = null; + } + ... +} +</pre> +<!-- Handle connection errors --> +<h3>Handle connection errors</h3> +<p> + Besides handling the normal callbacks from Location Services, you have to provide a callback + method that Location Services calls if a connection error occurs. This callback method + can re-use the {@link android.support.v4.app.DialogFragment} class that you defined to + handle the check for Google Play services. It can also re-use the override you defined + for {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()} that + receives any Google Play services results that occur when the user interacts with the + error dialog. The following snippet shows you a sample implementation of the callback method: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + // Implementation of OnConnectionFailedListener.onConnectionFailed + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + // Turn off the request flag + mInProgress = false; + /* + * If the error has a resolution, start a Google Play services + * activity to resolve it. + */ + if (connectionResult.hasResolution()) { + try { + connectionResult.startResolutionForResult( + this, + CONNECTION_FAILURE_RESOLUTION_REQUEST); + } catch (SendIntentException e) { + // Log the error + e.printStackTrace(); + } + // If no resolution is available, display an error dialog + } else { + // Get the error code + int errorCode = connectionResult.getErrorCode(); + // Get the error dialog from Google Play services + Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( + errorCode, + this, + CONNECTION_FAILURE_RESOLUTION_REQUEST); + // If Google Play services can provide an error dialog + if (errorDialog != null) { + // Create a new DialogFragment for the error dialog + ErrorDialogFragment errorFragment = + new ErrorDialogFragment(); + // Set the dialog in the DialogFragment + errorFragment.setDialog(errorDialog); + // Show the error dialog in the DialogFragment + errorFragment.show( + getSupportFragmentManager(), + "Geofence Detection"); + } + } + } + ... +} +</pre> +<!-- + Handle Geofence Transitions + --> +<h2 id="HandleGeofenceTransitions">Handle Geofence Transitions</h2> +<p> + When Location Services detects that the user has entered or exited a geofence, it + sends out the {@link android.content.Intent} contained in the {@link android.app.PendingIntent} + you included in the request to add geofences. This {@link android.content.Intent} is +</p> +<h3>Define an IntentService</h3> +<p> + The following snippet shows how to define an {@link android.app.IntentService} that posts a + notification when a geofence transition occurs. When the user clicks the notification, the + app's main activity appears: +</p> +<pre> +public class ReceiveTransitionsIntentService extends IntentService { + ... + /** + * Sets an identifier for the service + */ + public ReceiveTransitionsIntentService() { + super("ReceiveTransitionsIntentService"); + } + /** + * Handles incoming intents + *@param intent The Intent sent by Location Services. This + * Intent is provided + * to Location Services (inside a PendingIntent) when you call + * addGeofences() + */ + @Override + protected void onHandleIntent(Intent intent) { + // First check for errors + if (LocationClient.hasError(intent)) { + // Get the error code with a static method + int errorCode = LocationClient.getErrorCode(intent); + // Log the error + Log.e("ReceiveTransitionsIntentService", + "Location Services error: " + + Integer.toString(errorCode)); + /* + * You can also send the error code to an Activity or + * Fragment with a broadcast Intent + */ + /* + * If there's no error, get the transition type and the IDs + * of the geofence or geofences that triggered the transition + */ + } else { + // Get the type of transition (entry or exit) + int transitionType = + LocationClient.getGeofenceTransition(intent); + // Test that a valid transition was reported + if ( + (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) + || + (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) + ) { + List <Geofence> triggerList = + getTriggeringGeofences(intent); + + String[] triggerIds = new String[geofenceList.size()]; + + for (int i = 0; i < triggerIds.length; i++) { + // Store the Id of each geofence + triggerIds[i] = triggerList.get(i).getRequestId(); + } + /* + * At this point, you can store the IDs for further use + * display them, or display the details associated with + * them. + */ + } + // An invalid transition was reported + } else { + Log.e("ReceiveTransitionsIntentService", + "Geofence transition error: " + + Integer.toString()transitionType)); + } + } + ... +} +</pre> +<!-- Specify the IntentService in the manifest --> +<h3>Specify the IntentService in the manifest</h3> +<p> + To identify the {@link android.app.IntentService} to the system, add a + <code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> + element to the app manifest. For example: +</p> +<pre> +<service + android:name="com.example.android.location.ReceiveTransitionsIntentService" + android:label="@string/app_name" + android:exported="false"> +</service> +</pre> +<p> + Notice that you don't have to specify intent filters for the service, because it only receives + explicit intents. How the incoming geofence transition intents are created is described in the + section <a href="#requestmonitoring">Send the monitoring request</a>. +</p> +<!-- + Remove Geofences + --> +<h2 id="StopGeofenceMonitoring">Stop Geofence Monitoring</h2> +<p> + To stop geofence monitoring, you remove the geofences themselves. You can remove a specific + set of geofences or all the geofences associated with a {@link android.app.PendingIntent}. The + procedure is similar to adding geofences. The first operation gets a location + client for the removal request, and the second makes the request using the client. +</p> +<p> + The callback methods that Location Services invokes when it has finished removing geofences + are defined in the interface +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html">LocationClient.OnRemoveGeofencesResultListener</a></code>. Declare + this interface as part of your class definition, and then add definitions for its two methods: +</p> +<dl> + <dt> +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code> + </dt> + <dd> + Callback invoked when Location Services finishes a request to remove all geofences made + by the method +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)</a></code>. + </dd> + <dt> +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult(List<String>, LocationClient.OnRemoveGeofencesResultListener)</a></code> + </dt> + <dd> + Callback invoked when Location Services finished a request to remove a set of geofences, + specified by their geofence IDs, by the method +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(java.util.List<java.lang.String>, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(List<String>, LocationClient.OnRemoveGeofencesResultListener)</a></code>. + </dd> +</dl> +<p> + Examples of implementing these methods are shown in the next snippets. +</p> +<h3>Remove all geofences</h3> +<p> + Since removing geofences uses some of the methods you use to add geofences, start by defining + another request type: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + // Enum type for controlling the type of removal requested + public enum REQUEST_TYPE = {ADD, REMOVE_INTENT} + ... +} +</pre> +<p> + Start the removal request by getting a connection to Location Services. If the connection fails, +<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code> isn't called, + and the request stops. The following snippet shows how to start the request: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /** + * Start a request to remove geofences by calling + * LocationClient.connect() + */ + public void removeGeofences(PendingIntent requestIntent) { + // Record the type of removal request + mRequestType = REMOVE_INTENT; + /* + * Test for Google Play services after setting the request type. + * If Google Play services isn't present, the request can be + * restarted. + */ + if (!servicesConnected()) { + return; + } + // Store the PendingIntent + mGeofenceRequestIntent = requestIntent; + /* + * Create a new location client object. Since the current + * activity class implements ConnectionCallbacks and + * OnConnectionFailedListener, pass the current activity object + * as the listener for both parameters + */ + mLocationClient = new LocationClient(this, this, this); + // If a request is not already underway + if (!mInProgress) { + // Indicate that a request is underway + mInProgress = true; + // Request a connection from the client to Location Services + mLocationClient.connect(); + } else { + /* + * A request is already underway. You can handle + * this situation by disconnecting the client, + * re-setting the flag, and then re-trying the + * request. + */ + } + } + ... +} +</pre> +<p> + When Location Services invokes the callback method indicating that the connection is open, + make the request to remove all geofences. Disconnect the client after making the request. + For example: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /** + * 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 onConnected(Bundle dataBundle) { + /* + * Choose what to do based on the request type set in + * removeGeofences + */ + switch (mRequestType) { + ... + case REMOVE_INTENT : + mLocationClient.removeGeofences( + mGeofenceRequestIntent, this); + break; + ... + } + } + ... +} +</pre> +<p> + Although the call to +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)</a></code> Services calls + returns immediately, the result of the removal request is indeterminate until Location Services + calls +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code>. + The following snippet shows how to define this method: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /** + * 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) { + // If removing the geofences was successful + if (statusCode == LocationStatusCodes.SUCCESS) { + /* + * Handle successful removal of geofences here. + * You can send out a broadcast intent or update the UI. + * geofences into the Intent's extended data. + */ + } else { + // If adding the geocodes failed + /* + * Report errors here. + * You can log the error using Log.e() or update + * the UI. + */ + } + /* + * Disconnect the location client regardless of the + * request status, and indicate that a request is no + * longer in progress + */ + mInProgress = false; + mLocationClient.disconnect(); + } + ... +} +</pre> +<h3>Remove individual geofences</h3> +<p> + The procedure for removing an individual geofence or set of geofences is similar to the + removal of all geofences. To specify the geofences you want remove, add their geofence ID + values to a {@link java.util.List} of String objects. Pass this {@link java.util.List} to a + different definition of {@code removeGeofences} with the appropriate signature. This method + then starts the removal process. +</p> +<p> + Start by adding a request type for removing geofences by a list, and also add a global variable + for storing the list of geofences: +</p> +<pre> + ... + // Enum type for controlling the type of removal requested + public enum REQUEST_TYPE = {ADD, REMOVE_INTENT, REMOVE_LIST} + // Store the list of geofence Ids to remove + String<List> mGeofencesToRemove; +</pre> +<p> + Next, define a list of geofences you want to remove. For example, this snippet removes the +<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> + defined by the geofence ID "1": +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + List<String> listOfGeofences = + Collections.singletonList("1"); + removeGeofences(listOfGeofences); + ... +} +</pre> +<p> + The following snippet defines the {@code removeGeofences()} method: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /** + * Start a request to remove monitoring by + * calling LocationClient.connect() + * + */ + public void removeGeofences(List<String> geofenceIds) { + // If Google Play services is unavailable, exit + // Record the type of removal request + mRequestType = REMOVE_LIST; + /* + * Test for Google Play services after setting the request type. + * If Google Play services isn't present, the request can be + * restarted. + */ + if (!servicesConnected()) { + return; + } + // Store the list of geofences to remove + mGeofencesToRemove = geofenceIds; + /* + * Create a new location client object. Since the current + * activity class implements ConnectionCallbacks and + * OnConnectionFailedListener, pass the current activity object + * as the listener for both parameters + */ + mLocationClient = new LocationClient(this, this, this); + // If a request is not already underway + if (!mInProgress) { + // Indicate that a request is underway + mInProgress = true; + // Request a connection from the client to Location Services + mLocationClient.connect(); + } else { + /* + * A request is already underway. You can handle + * this situation by disconnecting the client, + * re-setting the flag, and then re-trying the + * request. + */ + } + } + ... +} +</pre> +<p> + When Location Services invokes the callback method indicating that the connection is open, + make the request to remove the list of geofences. Disconnect the client after making the request. + For example: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + private void onConnected(Bundle dataBundle) { + ... + switch (mRequestType) { + ... + // If removeGeofencesById was called + case REMOVE_LIST : + mLocationClient.removeGeofences( + mGeofencesToRemove, this); + break; + ... + } + ... + } + ... +} +</pre> +<p> + Define an implementation of +<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult()</a></code>. + Location Services invokes this callback method to indicate that the request to remove a list of + geofences is complete. In this method, examine the incoming status code and take the + appropriate action: +</p> +<pre> +public class MainActivity extends FragmentActivity implements + ConnectionCallbacks, + OnConnectionFailedListener, + OnAddGeofencesResultListener { + ... + /** + * 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) { + // If removing the geocodes was successful + if (LocationStatusCodes.SUCCESS == statusCode) { + /* + * Handle successful removal of geofences here. + * You can send out a broadcast intent or update the UI. + * geofences into the Intent's extended data. + */ + } else { + // If removing the geofences failed + /* + * Report errors here. + * You can log the error using Log.e() or update + * the UI. + */ + } + // Indicate that a request is no longer in progress + mInProgress = false; + // Disconnect the location client + mLocationClient.disconnect(); + } + ... +} +</pre> +<p> + You can combine geofencing with other location-aware features, such as periodic location updates + or activity recognition, which are described in other lessons in this class. +</p> +<p> + The next lesson, + <a href="activity-recognition.html">Recognizing the User's Current Activity</a>, shows you how + to request and receive activity updates. At regular intervals, Location Services can send you + information about the user's current physical activity. Based on this information, you can + change your app's behavior; for example, you can switch to a longer update interval if you + detect that the user is walking instead of driving. +</p> |