1408 lines
56 KiB
Plaintext
1408 lines
56 KiB
Plaintext
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>
|