1page.title=Creating and Monitoring Geofences 2 3trainingnavtop=true 4@jd:body 5 6 7<div id="tb-wrapper"> 8<div id="tb"> 9 10<h2>This lesson teaches you to</h2> 11<ol> 12 <li><a href="#RequestGeofences">Request Geofence Monitoring</a></li> 13 <li><a href="#HandleGeofenceTransitions">Handle Geofence Transitions</a></li> 14 <li><a href="#StopGeofenceMonitoring">Stop Geofence Monitoring</a></li> 15</ol> 16 17<h2>You should also read</h2> 18<ul> 19 <li> 20 <a href="{@docRoot}google/play-services/setup.html">Setup Google Play Services SDK</a> 21 </li> 22</ul> 23 24<h2>Try it out</h2> 25 26<div class="download-box"> 27 <a href="http://developer.android.com/shareables/training/GeofenceDetection.zip" class="button">Download the sample</a> 28 <p class="filename">GeofenceDetection.zip</p> 29</div> 30 31</div> 32</div> 33<p> 34 Geofencing combines awareness of the user's current location with awareness of nearby 35 features, defined as the user's proximity to locations that may be of interest. To mark a 36 location of interest, you specify its latitude and longitude. To adjust the proximity for the 37 location, you add a radius. The latitude, longitude, and radius define a geofence. 38 You can have multiple active geofences at one time. 39</p> 40<p> 41 Location Services treats a geofences as an area rather than as a points and proximity. This 42 allows it to detect when the user enters or exits a geofence. For each geofence, you can ask 43 Location Services to send you entrance events or exit events or both. You can also limit the 44 duration of a geofence by specifying an expiration duration in milliseconds. After the geofence 45 expires, Location Services automatically removes it. 46</p> 47<!-- 48 Send geofences to Location Services 49 --> 50<h2 id="RequestGeofences">Request Geofence Monitoring</h2> 51<p> 52 The first step in requesting geofence monitoring is to request the necessary permission. 53 To use geofencing, your app must request 54 {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. To request this 55 permission, add the following element as a child element of the 56<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> 57 element: 58</p> 59<pre> 60<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 61</pre> 62<!-- Check for Google Play services --> 63<h3>Check for Google Play Services</h3> 64<p> 65 Location Services is part of the Google Play services APK. Since it's hard to anticipate the 66 state of the user's device, you should always check that the APK is installed before you attempt 67 to connect to Location Services. To check that the APK is installed, call 68<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#isGooglePlayServicesAvailable(android.content.Context)">GooglePlayServicesUtil.isGooglePlayServicesAvailable()</a></code>, 69 which returns one of the 70 integer result codes listed in the API reference documentation. If you encounter an error, 71 call 72<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#getErrorDialog(int, android.app.Activity, int)">GooglePlayServicesUtil.getErrorDialog()</a></code> 73 to retrieve localized dialog that prompts users to take the correct action, then display 74 the dialog in a {@link android.support.v4.app.DialogFragment}. The dialog may allow the 75 user to correct the problem, in which case Google Play services may send a result back to your 76 activity. To handle this result, override the method 77 {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()} 78 79</p> 80<p class="note"> 81 <strong>Note:</strong> To make your app compatible with 82 platform version 1.6 and later, the activity that displays the 83 {@link android.support.v4.app.DialogFragment} must subclass 84 {@link android.support.v4.app.FragmentActivity} instead of {@link android.app.Activity}. Using 85 {@link android.support.v4.app.FragmentActivity} also allows you to call 86 {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager 87 getSupportFragmentManager()} to display the {@link android.support.v4.app.DialogFragment}. 88</p> 89<p> 90 Since you usually need to check for Google Play services in more than one place in your code, 91 define a method that encapsulates the check, then call the method before each connection 92 attempt. The following snippet contains all of the code required to check for Google 93 Play services: 94</p> 95<pre> 96public class MainActivity extends FragmentActivity { 97 ... 98 // Global constants 99 /* 100 * Define a request code to send to Google Play services 101 * This code is returned in Activity.onActivityResult 102 */ 103 private final static int 104 CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000; 105 ... 106 // Define a DialogFragment that displays the error dialog 107 public static class ErrorDialogFragment extends DialogFragment { 108 // Global field to contain the error dialog 109 private Dialog mDialog; 110 ... 111 // Default constructor. Sets the dialog field to null 112 public ErrorDialogFragment() { 113 super(); 114 mDialog = null; 115 } 116 ... 117 // Set the dialog to display 118 public void setDialog(Dialog dialog) { 119 mDialog = dialog; 120 } 121 ... 122 // Return a Dialog to the DialogFragment. 123 @Override 124 public Dialog onCreateDialog(Bundle savedInstanceState) { 125 return mDialog; 126 } 127 ... 128 } 129 ... 130 /* 131 * Handle results returned to the FragmentActivity 132 * by Google Play services 133 */ 134 @Override 135 protected void onActivityResult( 136 int requestCode, int resultCode, Intent data) { 137 // Decide what to do based on the original request code 138 switch (requestCode) { 139 ... 140 case CONNECTION_FAILURE_RESOLUTION_REQUEST : 141 /* 142 * If the result code is Activity.RESULT_OK, try 143 * to connect again 144 */ 145 switch (resultCode) { 146 ... 147 case Activity.RESULT_OK : 148 /* 149 * Try the request again 150 */ 151 ... 152 break; 153 } 154 ... 155 } 156 ... 157 } 158 ... 159 private boolean servicesConnected() { 160 // Check that Google Play services is available 161 int resultCode = 162 GooglePlayServicesUtil. 163 isGooglePlayServicesAvailable(this); 164 // If Google Play services is available 165 if (ConnectionResult.SUCCESS == resultCode) { 166 // In debug mode, log the status 167 Log.d("Geofence Detection", 168 "Google Play services is available."); 169 // Continue 170 return true; 171 // Google Play services was not available for some reason 172 } else { 173 // Get the error code 174 int errorCode = connectionResult.getErrorCode(); 175 // Get the error dialog from Google Play services 176 Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( 177 errorCode, 178 this, 179 CONNECTION_FAILURE_RESOLUTION_REQUEST); 180 181 // If Google Play services can provide an error dialog 182 if (errorDialog != null) { 183 // Create a new DialogFragment for the error dialog 184 ErrorDialogFragment errorFragment = 185 new ErrorDialogFragment(); 186 // Set the dialog in the DialogFragment 187 errorFragment.setDialog(errorDialog); 188 // Show the error dialog in the DialogFragment 189 errorFragment.show( 190 getSupportFragmentManager(), 191 "Geofence Detection"); 192 } 193 } 194 } 195 ... 196} 197</pre> 198<p> 199 Snippets in the following sections call this method to verify that Google Play services is 200 available. 201</p> 202<p> 203 To use geofencing, start by defining the geofences you want to monitor. Although you usually 204 store geofence data in a local database or download it from the network, you need to send 205 a geofence to Location Services as an instance of 206<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>, 207 which you create with 208<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.Builder.html">Geofence.Builder</a></code>. 209 Each 210<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> 211 object contains the following information: 212</p> 213<dl> 214 <dt>Latitude, longitude, and radius</dt> 215 <dd> 216 Define a circular area for the geofence. Use the latitude and longitude to mark a location 217 of interest, and then use the radius to adjust how close the user needs to approach the 218 location before the geofence is detected. The larger the radius, the more likely the 219 user will trigger a geofence transition alert by approaching the geofence. For example, 220 providing a large radius for a geofencing app that turns on lights in the user's house as 221 the user returns home might cause the lights to go on even if the user is simply passing by. 222 </dd> 223 <dt>Expiration time</dt> 224 <dd> 225 How long the geofence should remain active. Once the expiration time is reached, Location 226 Services deletes the geofence. Most of the time, you should specify an expiration time, but 227 you may want to keep permanent geofences for the user's home or place of work. 228 </dd> 229 <dt>Transition type</dt> 230 <dd> 231 Location Services can detect when the user steps within the radius of the geofence ("entry") 232 and when the user steps outside the radius of the geofence ("exit"), or both. 233 </dd> 234 <dt>Geofence ID</dt> 235 <dd> 236 A string that is stored with the geofence. You should make this unique, so that you can 237 use it to remove a geofence from Location Services tracking. 238 </dd> 239</dl> 240<h3>Define geofence storage</h3> 241<p> 242 A geofencing app needs to read and write geofence data to persistent storage. You shouldn't use 243<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> 244 objects to do this; instead, use storage techniques such as databases that can store groups of 245 related data. 246</p> 247<p> 248 As an example of storing geofence data, the following snippet defines two classes that use 249 the app's {@link android.content.SharedPreferences} instance for persistent storage. The class 250 {@code SimpleGeofence}, analogous to a database record, stores the 251 data for a single 252<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> 253 object in a "flattened" form. The class {@code SimpleGeofenceStore}, analogous to a database, 254 reads and writes {@code SimpleGeofence} data to the 255 {@link android.content.SharedPreferences} instance. 256</p> 257<pre> 258public class MainActivity extends FragmentActivity { 259 ... 260 /** 261 * A single Geofence object, defined by its center and radius. 262 */ 263 public class SimpleGeofence { 264 // Instance variables 265 private final String mId; 266 private final double mLatitude; 267 private final double mLongitude; 268 private final float mRadius; 269 private long mExpirationDuration; 270 private int mTransitionType; 271 272 /** 273 * @param geofenceId The Geofence's request ID 274 * @param latitude Latitude of the Geofence's center. 275 * @param longitude Longitude of the Geofence's center. 276 * @param radius Radius of the geofence circle. 277 * @param expiration Geofence expiration duration 278 * @param transition Type of Geofence transition. 279 */ 280 public SimpleGeofence( 281 String geofenceId, 282 double latitude, 283 double longitude, 284 float radius, 285 long expiration, 286 int transition) { 287 // Set the instance fields from the constructor 288 this.mId = geofenceId; 289 this.mLatitude = latitude; 290 this.mLongitude = longitude; 291 this.mRadius = radius; 292 this.mExpirationDuration = expiration; 293 this.mTransitionType = transition; 294 } 295 // Instance field getters 296 public String getId() { 297 return mId; 298 } 299 public double getLatitude() { 300 return mLatitude; 301 } 302 public double getLongitude() { 303 return mLongitude; 304 } 305 public float getRadius() { 306 return mRadius; 307 } 308 public long getExpirationDuration() { 309 return mExpirationDuration; 310 } 311 public int getTransitionType() { 312 return mTransitionType; 313 } 314 /** 315 * Creates a Location Services Geofence object from a 316 * SimpleGeofence. 317 * 318 * @return A Geofence object 319 */ 320 public Geofence toGeofence() { 321 // Build a new Geofence object 322 return new Geofence.Builder() 323 .setRequestId(getId()) 324 .setTransitionTypes(mTransitionType) 325 .setCircularRegion( 326 getLatitude(), getLongitude(), getRadius()) 327 .setExpirationDuration(mExpirationDuration) 328 .build(); 329 } 330 } 331 ... 332 /** 333 * Storage for geofence values, implemented in SharedPreferences. 334 */ 335 public class SimpleGeofenceStore { 336 // Keys for flattened geofences stored in SharedPreferences 337 public static final String KEY_LATITUDE = 338 "com.example.android.geofence.KEY_LATITUDE"; 339 public static final String KEY_LONGITUDE = 340 "com.example.android.geofence.KEY_LONGITUDE"; 341 public static final String KEY_RADIUS = 342 "com.example.android.geofence.KEY_RADIUS"; 343 public static final String KEY_EXPIRATION_DURATION = 344 "com.example.android.geofence.KEY_EXPIRATION_DURATION"; 345 public static final String KEY_TRANSITION_TYPE = 346 "com.example.android.geofence.KEY_TRANSITION_TYPE"; 347 // The prefix for flattened geofence keys 348 public static final String KEY_PREFIX = 349 "com.example.android.geofence.KEY"; 350 /* 351 * Invalid values, used to test geofence storage when 352 * retrieving geofences 353 */ 354 public static final long INVALID_LONG_VALUE = -999l; 355 public static final float INVALID_FLOAT_VALUE = -999.0f; 356 public static final int INVALID_INT_VALUE = -999; 357 // The SharedPreferences object in which geofences are stored 358 private final SharedPreferences mPrefs; 359 // The name of the SharedPreferences 360 private static final String SHARED_PREFERENCES = 361 "SharedPreferences"; 362 // Create the SharedPreferences storage with private access only 363 public SimpleGeofenceStore(Context context) { 364 mPrefs = 365 context.getSharedPreferences( 366 SHARED_PREFERENCES, 367 Context.MODE_PRIVATE); 368 } 369 /** 370 * Returns a stored geofence by its id, or returns {@code null} 371 * if it's not found. 372 * 373 * @param id The ID of a stored geofence 374 * @return A geofence defined by its center and radius. See 375 */ 376 public SimpleGeofence getGeofence(String id) { 377 /* 378 * Get the latitude for the geofence identified by id, or 379 * INVALID_FLOAT_VALUE if it doesn't exist 380 */ 381 double lat = mPrefs.getFloat( 382 getGeofenceFieldKey(id, KEY_LATITUDE), 383 INVALID_FLOAT_VALUE); 384 /* 385 * Get the longitude for the geofence identified by id, or 386 * INVALID_FLOAT_VALUE if it doesn't exist 387 */ 388 double lng = mPrefs.getFloat( 389 getGeofenceFieldKey(id, KEY_LONGITUDE), 390 INVALID_FLOAT_VALUE); 391 /* 392 * Get the radius for the geofence identified by id, or 393 * INVALID_FLOAT_VALUE if it doesn't exist 394 */ 395 float radius = mPrefs.getFloat( 396 getGeofenceFieldKey(id, KEY_RADIUS), 397 INVALID_FLOAT_VALUE); 398 /* 399 * Get the expiration duration for the geofence identified 400 * by id, or INVALID_LONG_VALUE if it doesn't exist 401 */ 402 long expirationDuration = mPrefs.getLong( 403 getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION), 404 INVALID_LONG_VALUE); 405 /* 406 * Get the transition type for the geofence identified by 407 * id, or INVALID_INT_VALUE if it doesn't exist 408 */ 409 int transitionType = mPrefs.getInt( 410 getGeofenceFieldKey(id, KEY_TRANSITION_TYPE), 411 INVALID_INT_VALUE); 412 // If none of the values is incorrect, return the object 413 if ( 414 lat != GeofenceUtils.INVALID_FLOAT_VALUE && 415 lng != GeofenceUtils.INVALID_FLOAT_VALUE && 416 radius != GeofenceUtils.INVALID_FLOAT_VALUE && 417 expirationDuration != 418 GeofenceUtils.INVALID_LONG_VALUE && 419 transitionType != GeofenceUtils.INVALID_INT_VALUE) { 420 421 // Return a true Geofence object 422 return new SimpleGeofence( 423 id, lat, lng, radius, expirationDuration, 424 transitionType); 425 // Otherwise, return null. 426 } else { 427 return null; 428 } 429 } 430 /** 431 * Save a geofence. 432 * @param geofence The SimpleGeofence containing the 433 * values you want to save in SharedPreferences 434 */ 435 public void setGeofence(String id, SimpleGeofence geofence) { 436 /* 437 * Get a SharedPreferences editor instance. Among other 438 * things, SharedPreferences ensures that updates are atomic 439 * and non-concurrent 440 */ 441 Editor editor = mPrefs.edit(); 442 // Write the Geofence values to SharedPreferences 443 editor.putFloat( 444 getGeofenceFieldKey(id, KEY_LATITUDE), 445 (float) geofence.getLatitude()); 446 editor.putFloat( 447 getGeofenceFieldKey(id, KEY_LONGITUDE), 448 (float) geofence.getLongitude()); 449 editor.putFloat( 450 getGeofenceFieldKey(id, KEY_RADIUS), 451 geofence.getRadius()); 452 editor.putLong( 453 getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION), 454 geofence.getExpirationDuration()); 455 editor.putInt( 456 getGeofenceFieldKey(id, KEY_TRANSITION_TYPE), 457 geofence.getTransitionType()); 458 // Commit the changes 459 editor.commit(); 460 } 461 public void clearGeofence(String id) { 462 /* 463 * Remove a flattened geofence object from storage by 464 * removing all of its keys 465 */ 466 Editor editor = mPrefs.edit(); 467 editor.remove(getGeofenceFieldKey(id, KEY_LATITUDE)); 468 editor.remove(getGeofenceFieldKey(id, KEY_LONGITUDE)); 469 editor.remove(getGeofenceFieldKey(id, KEY_RADIUS)); 470 editor.remove(getGeofenceFieldKey(id, 471 KEY_EXPIRATION_DURATION)); 472 editor.remove(getGeofenceFieldKey(id, KEY_TRANSITION_TYPE)); 473 editor.commit(); 474 } 475 /** 476 * Given a Geofence object's ID and the name of a field 477 * (for example, KEY_LATITUDE), return the key name of the 478 * object's values in SharedPreferences. 479 * 480 * @param id The ID of a Geofence object 481 * @param fieldName The field represented by the key 482 * @return The full key name of a value in SharedPreferences 483 */ 484 private String getGeofenceFieldKey(String id, 485 String fieldName) { 486 return KEY_PREFIX + "_" + id + "_" + fieldName; 487 } 488 } 489 ... 490} 491</pre> 492<h3>Create Geofence objects</h3> 493<p> 494 The following snippet uses the {@code SimpleGeofence} and {@code SimpleGeofenceStore} classes 495 gets geofence data from the UI, stores it in {@code SimpleGeofence} objects, stores these 496 objects in a {@code SimpleGeofenceStore} object, and then creates 497<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> 498 objects: 499</p> 500<pre> 501public class MainActivity extends FragmentActivity { 502 ... 503 /* 504 * Use to set an expiration time for a geofence. After this amount 505 * of time Location Services will stop tracking the geofence. 506 */ 507 private static final long SECONDS_PER_HOUR = 60; 508 private static final long MILLISECONDS_PER_SECOND = 1000; 509 private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12; 510 private static final long GEOFENCE_EXPIRATION_TIME = 511 GEOFENCE_EXPIRATION_IN_HOURS * 512 SECONDS_PER_HOUR * 513 MILLISECONDS_PER_SECOND; 514 ... 515 /* 516 * Handles to UI views containing geofence data 517 */ 518 // Handle to geofence 1 latitude in the UI 519 private EditText mLatitude1; 520 // Handle to geofence 1 longitude in the UI 521 private EditText mLongitude1; 522 // Handle to geofence 1 radius in the UI 523 private EditText mRadius1; 524 // Handle to geofence 2 latitude in the UI 525 private EditText mLatitude2; 526 // Handle to geofence 2 longitude in the UI 527 private EditText mLongitude2; 528 // Handle to geofence 2 radius in the UI 529 private EditText mRadius2; 530 /* 531 * Internal geofence objects for geofence 1 and 2 532 */ 533 private SimpleGeofence mUIGeofence1; 534 private SimpleGeofence mUIGeofence2; 535 ... 536 // Internal List of Geofence objects 537 List<Geofence> mGeofenceList; 538 // Persistent storage for geofences 539 private SimpleGeofenceStore mGeofenceStorage; 540 ... 541 @Override 542 protected void onCreate(Bundle savedInstanceState) { 543 super.onCreate(savedInstanceState); 544 ... 545 // Instantiate a new geofence storage area 546 mGeofenceStorage = new SimpleGeofenceStore(this); 547 548 // Instantiate the current List of geofences 549 mCurrentGeofences = new ArrayList<Geofence>(); 550 } 551 ... 552 /** 553 * Get the geofence parameters for each geofence from the UI 554 * and add them to a List. 555 */ 556 public void createGeofences() { 557 /* 558 * Create an internal object to store the data. Set its 559 * ID to "1". This is a "flattened" object that contains 560 * a set of strings 561 */ 562 mUIGeofence1 = new SimpleGeofence( 563 "1", 564 Double.valueOf(mLatitude1.getText().toString()), 565 Double.valueOf(mLongitude1.getText().toString()), 566 Float.valueOf(mRadius1.getText().toString()), 567 GEOFENCE_EXPIRATION_TIME, 568 // This geofence records only entry transitions 569 Geofence.GEOFENCE_TRANSITION_ENTER); 570 // Store this flat version 571 mGeofenceStorage.setGeofence("1", mUIGeofence1); 572 // Create another internal object. Set its ID to "2" 573 mUIGeofence2 = new SimpleGeofence( 574 "2", 575 Double.valueOf(mLatitude2.getText().toString()), 576 Double.valueOf(mLongitude2.getText().toString()), 577 Float.valueOf(mRadius2.getText().toString()), 578 GEOFENCE_EXPIRATION_TIME, 579 // This geofence records both entry and exit transitions 580 Geofence.GEOFENCE_TRANSITION_ENTER | 581 Geofence.GEOFENCE_TRANSITION_EXIT); 582 // Store this flat version 583 mGeofenceStorage.setGeofence(2, mUIGeofence2); 584 mGeofenceList.add(mUIGeofence1.toGeofence()); 585 mGeofenceList.add(mUIGeofence2.toGeofence()); 586 } 587 ... 588} 589</pre> 590<p> 591 In addition to the {@link java.util.List} of 592<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> 593 objects you want to monitor, you need to provide Location Services with the 594 {@link android.content.Intent} that it sends to your app when it detects geofence 595 transitions. 596<h4>Define a Intent for geofence transitions</h4> 597<p> 598 The {@link android.content.Intent} sent from Location Services can trigger various actions in 599 your app, but you should <i>not</i> have it start an activity or fragment, because components 600 should only become visible in response to a user action. In many cases, an 601 {@link android.app.IntentService} is a good way to handle the intent. An 602 {@link android.app.IntentService} can post a notification, do long-running background work, 603 send intents to other services, or send a broadcast intent. The following snippet shows how 604 how to define a {@link android.app.PendingIntent} that starts an 605 {@link android.app.IntentService}: 606</p> 607<pre> 608public class MainActivity extends FragmentActivity { 609 ... 610 /* 611 * Create a PendingIntent that triggers an IntentService in your 612 * app when a geofence transition occurs. 613 */ 614 private PendingIntent getTransitionPendingIntent() { 615 // Create an explicit Intent 616 Intent intent = new Intent(this, 617 ReceiveTransitionsIntentService.class); 618 /* 619 * Return the PendingIntent 620 */ 621 return PendingIntent.getService( 622 this, 623 0, 624 intent, 625 PendingIntent.FLAG_UPDATE_CURRENT); 626 } 627 ... 628} 629</pre> 630<p> 631 Now you have all the code you need to send a request to monitor geofences to Location 632 Services. 633</p> 634<!-- Send the monitoring request --> 635<h3 id="requestmonitoring">Send the monitoring request</h3> 636<p> 637 Sending the monitoring request requires two asynchronous operations. The first operation gets a 638 location client for the request, and the second makes the request using the client. In both 639 cases, Location Services invokes a callback method when it finishes the operation. The best way 640 to handle these operations is to chain together the method calls. The following snippets 641 demonstrate how to set up an activity, define the methods, and call them in the proper order. 642</p> 643<p> 644 First, modify the activity's class definition to implement the necessary callback interfaces. 645 Add the following interfaces: 646</p> 647<dl> 648 <dt> 649<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html">ConnectionCallbacks</a></code> 650 </dt> 651 <dd> 652 Specifies methods that Location Services calls when a location client is connected or 653 disconnected. 654 </dd> 655 <dt> 656<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.OnConnectionFailedListener.html">OnConnectionFailedListener</a></code> 657 </dt> 658 <dd> 659 Specifies a method that Location Services calls if an error occurs while attempting to 660 connect the location client. 661 </dd> 662 <dt> 663<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html">OnAddGeofencesResultListener</a></code> 664 </dt> 665 <dd> 666 Specifies a method that Location Services calls once it has added the geofences. 667 </dd> 668</dl> 669<p> 670 For example: 671</p> 672<pre> 673public class MainActivity extends FragmentActivity implements 674 ConnectionCallbacks, 675 OnConnectionFailedListener, 676 OnAddGeofencesResultListener { 677 ... 678} 679</pre> 680<h4>Start the request process</h4> 681<p> 682 Next, define a method that starts the request process by connecting to Location Services. 683 Mark this as a request to add a geofence by setting a global variable. This allows you to 684 use the callback 685<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code> 686 to add geofences and to remove them, as described in succeeding sections. 687</p> 688<p> 689<p> 690 To guard against race conditions that might arise if your app tries to start another request 691 before the first one finishes, define a boolean flag that tracks the state of the current 692 request: 693</p> 694<pre> 695public class MainActivity extends FragmentActivity implements 696 ConnectionCallbacks, 697 OnConnectionFailedListener, 698 OnAddGeofencesResultListener { 699 ... 700 // Holds the location client 701 private LocationClient mLocationClient; 702 // Stores the PendingIntent used to request geofence monitoring 703 private PendingIntent mGeofenceRequestIntent; 704 // Defines the allowable request types. 705 public enum REQUEST_TYPE = {ADD} 706 private REQUEST_TYPE mRequestType; 707 // Flag that indicates if a request is underway. 708 private boolean mInProgress; 709 ... 710 @Override 711 protected void onCreate(Bundle savedInstanceState) { 712 ... 713 // Start with the request flag set to false 714 mInProgress = false; 715 ... 716 } 717 ... 718 /** 719 * Start a request for geofence monitoring by calling 720 * LocationClient.connect(). 721 */ 722 public void addGeofences() { 723 // Start a request to add geofences 724 mRequestType = ADD; 725 /* 726 * Test for Google Play services after setting the request type. 727 * If Google Play services isn't present, the proper request 728 * can be restarted. 729 */ 730 if (!servicesConnected()) { 731 return; 732 } 733 /* 734 * Create a new location client object. Since the current 735 * activity class implements ConnectionCallbacks and 736 * OnConnectionFailedListener, pass the current activity object 737 * as the listener for both parameters 738 */ 739 mLocationClient = new LocationClient(this, this, this) 740 // If a request is not already underway 741 if (!mInProgress) { 742 // Indicate that a request is underway 743 mInProgress = true; 744 // Request a connection from the client to Location Services 745 mLocationClient.connect(); 746 } else { 747 /* 748 * A request is already underway. You can handle 749 * this situation by disconnecting the client, 750 * re-setting the flag, and then re-trying the 751 * request. 752 */ 753 } 754 } 755 ... 756} 757</pre> 758<h4>Send a request to add the geofences</h4> 759<p> 760 In your implementation of 761<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code>, 762 call 763<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>. 764 Notice that if the connection fails, 765<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code> 766 isn't called, and the request stops. 767</p> 768<pre> 769public class MainActivity extends FragmentActivity implements 770 ConnectionCallbacks, 771 OnConnectionFailedListener, 772 OnAddGeofencesResultListener { 773 ... 774 /* 775 * Provide the implementation of ConnectionCallbacks.onConnected() 776 * Once the connection is available, send a request to add the 777 * Geofences 778 */ 779 @Override 780 private void onConnected(Bundle dataBundle) { 781 ... 782 switch (mRequestType) { 783 case ADD : 784 // Get the PendingIntent for the request 785 mTransitionPendingIntent = 786 getTransitionPendingIntent(); 787 // Send a request to add the current geofences 788 mLocationClient.addGeofences( 789 mCurrentGeofences, pendingIntent, this); 790 ... 791 } 792 } 793 ... 794} 795</pre> 796<p> 797 Notice that 798<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> 799 returns immediately, but the status of the request is indeterminate until Location Services 800 calls 801<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code> 802 Once this method is called, you can determine if the request was successful or not. 803</p> 804<h4>Check the result returned by Location Services</h4> 805<p> 806 When Location Services invokes your implementation of the callback method 807<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code>, 808 indicating that the request is complete, examine the incoming status code. If the request 809 was successful, the geofences you requested are active. If the request was unsuccessful, 810 the geofences aren't active, and you need to re-try the request or report an error. For example: 811</p> 812<pre> 813public class MainActivity extends FragmentActivity implements 814 ConnectionCallbacks, 815 OnConnectionFailedListener, 816 OnAddGeofencesResultListener { 817 ... 818 /* 819 * Provide the implementation of 820 * OnAddGeofencesResultListener.onAddGeofencesResult. 821 * Handle the result of adding the geofences 822 * 823 */ 824 @Override 825 public void onAddGeofencesResult( 826 int statusCode, String[] geofenceRequestIds) { 827 // If adding the geofences was successful 828 if (LocationStatusCodes.SUCCESS == statusCode) { 829 /* 830 * Handle successful addition of geofences here. 831 * You can send out a broadcast intent or update the UI. 832 * geofences into the Intent's extended data. 833 */ 834 } else { 835 // If adding the geofences failed 836 /* 837 * Report errors here. 838 * You can log the error using Log.e() or update 839 * the UI. 840 */ 841 } 842 // Turn off the in progress flag and disconnect the client 843 mInProgress = false; 844 mLocationClient.disconnect(); 845 } 846 ... 847} 848</pre> 849<!-- Handle disconnections --> 850<h3>Handle disconnections</h3> 851<p> 852 In some cases, Location Services may disconnect from the activity recognition client before 853 you call 854<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#disconnect()">disconnect()</a></code>. 855 To handle this situation, implement <code> 856<a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onDisconnected()">onDisconnected()</a></code>. 857 In this method, set the request flag to indicate that a request is not in progress, and 858 delete the client: 859</p> 860<pre> 861public class MainActivity extends FragmentActivity implements 862 ConnectionCallbacks, 863 OnConnectionFailedListener, 864 OnAddGeofencesResultListener { 865 ... 866 /* 867 * Implement ConnectionCallbacks.onDisconnected() 868 * Called by Location Services once the location client is 869 * disconnected. 870 */ 871 @Override 872 public void onDisconnected() { 873 // Turn off the request flag 874 mInProgress = false; 875 // Destroy the current location client 876 mLocationClient = null; 877 } 878 ... 879} 880</pre> 881<!-- Handle connection errors --> 882<h3>Handle connection errors</h3> 883<p> 884 Besides handling the normal callbacks from Location Services, you have to provide a callback 885 method that Location Services calls if a connection error occurs. This callback method 886 can re-use the {@link android.support.v4.app.DialogFragment} class that you defined to 887 handle the check for Google Play services. It can also re-use the override you defined 888 for {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()} that 889 receives any Google Play services results that occur when the user interacts with the 890 error dialog. The following snippet shows you a sample implementation of the callback method: 891</p> 892<pre> 893public class MainActivity extends FragmentActivity implements 894 ConnectionCallbacks, 895 OnConnectionFailedListener, 896 OnAddGeofencesResultListener { 897 ... 898 // Implementation of OnConnectionFailedListener.onConnectionFailed 899 @Override 900 public void onConnectionFailed(ConnectionResult connectionResult) { 901 // Turn off the request flag 902 mInProgress = false; 903 /* 904 * If the error has a resolution, start a Google Play services 905 * activity to resolve it. 906 */ 907 if (connectionResult.hasResolution()) { 908 try { 909 connectionResult.startResolutionForResult( 910 this, 911 CONNECTION_FAILURE_RESOLUTION_REQUEST); 912 } catch (SendIntentException e) { 913 // Log the error 914 e.printStackTrace(); 915 } 916 // If no resolution is available, display an error dialog 917 } else { 918 // Get the error code 919 int errorCode = connectionResult.getErrorCode(); 920 // Get the error dialog from Google Play services 921 Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( 922 errorCode, 923 this, 924 CONNECTION_FAILURE_RESOLUTION_REQUEST); 925 // If Google Play services can provide an error dialog 926 if (errorDialog != null) { 927 // Create a new DialogFragment for the error dialog 928 ErrorDialogFragment errorFragment = 929 new ErrorDialogFragment(); 930 // Set the dialog in the DialogFragment 931 errorFragment.setDialog(errorDialog); 932 // Show the error dialog in the DialogFragment 933 errorFragment.show( 934 getSupportFragmentManager(), 935 "Geofence Detection"); 936 } 937 } 938 } 939 ... 940} 941</pre> 942<!-- 943 Handle Geofence Transitions 944 --> 945<h2 id="HandleGeofenceTransitions">Handle Geofence Transitions</h2> 946<p> 947 When Location Services detects that the user has entered or exited a geofence, it 948 sends out the {@link android.content.Intent} contained in the {@link android.app.PendingIntent} 949 you included in the request to add geofences. This {@link android.content.Intent} is 950</p> 951<h3>Define an IntentService</h3> 952<p> 953 The following snippet shows how to define an {@link android.app.IntentService} that posts a 954 notification when a geofence transition occurs. When the user clicks the notification, the 955 app's main activity appears: 956</p> 957<pre> 958public class ReceiveTransitionsIntentService extends IntentService { 959 ... 960 /** 961 * Sets an identifier for the service 962 */ 963 public ReceiveTransitionsIntentService() { 964 super("ReceiveTransitionsIntentService"); 965 } 966 /** 967 * Handles incoming intents 968 *@param intent The Intent sent by Location Services. This 969 * Intent is provided 970 * to Location Services (inside a PendingIntent) when you call 971 * addGeofences() 972 */ 973 @Override 974 protected void onHandleIntent(Intent intent) { 975 // First check for errors 976 if (LocationClient.hasError(intent)) { 977 // Get the error code with a static method 978 int errorCode = LocationClient.getErrorCode(intent); 979 // Log the error 980 Log.e("ReceiveTransitionsIntentService", 981 "Location Services error: " + 982 Integer.toString(errorCode)); 983 /* 984 * You can also send the error code to an Activity or 985 * Fragment with a broadcast Intent 986 */ 987 /* 988 * If there's no error, get the transition type and the IDs 989 * of the geofence or geofences that triggered the transition 990 */ 991 } else { 992 // Get the type of transition (entry or exit) 993 int transitionType = 994 LocationClient.getGeofenceTransition(intent); 995 // Test that a valid transition was reported 996 if ( 997 (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) 998 || 999 (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) 1000 ) { 1001 List <Geofence> triggerList = 1002 getTriggeringGeofences(intent); 1003 1004 String[] triggerIds = new String[geofenceList.size()]; 1005 1006 for (int i = 0; i < triggerIds.length; i++) { 1007 // Store the Id of each geofence 1008 triggerIds[i] = triggerList.get(i).getRequestId(); 1009 } 1010 /* 1011 * At this point, you can store the IDs for further use 1012 * display them, or display the details associated with 1013 * them. 1014 */ 1015 } 1016 // An invalid transition was reported 1017 } else { 1018 Log.e("ReceiveTransitionsIntentService", 1019 "Geofence transition error: " + 1020 Integer.toString()transitionType)); 1021 } 1022 } 1023 ... 1024} 1025</pre> 1026<!-- Specify the IntentService in the manifest --> 1027<h3>Specify the IntentService in the manifest</h3> 1028<p> 1029 To identify the {@link android.app.IntentService} to the system, add a 1030 <code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> 1031 element to the app manifest. For example: 1032</p> 1033<pre> 1034<service 1035 android:name="com.example.android.location.ReceiveTransitionsIntentService" 1036 android:label="@string/app_name" 1037 android:exported="false"> 1038</service> 1039</pre> 1040<p> 1041 Notice that you don't have to specify intent filters for the service, because it only receives 1042 explicit intents. How the incoming geofence transition intents are created is described in the 1043 section <a href="#requestmonitoring">Send the monitoring request</a>. 1044</p> 1045<!-- 1046 Remove Geofences 1047 --> 1048<h2 id="StopGeofenceMonitoring">Stop Geofence Monitoring</h2> 1049<p> 1050 To stop geofence monitoring, you remove the geofences themselves. You can remove a specific 1051 set of geofences or all the geofences associated with a {@link android.app.PendingIntent}. The 1052 procedure is similar to adding geofences. The first operation gets a location 1053 client for the removal request, and the second makes the request using the client. 1054</p> 1055<p> 1056 The callback methods that Location Services invokes when it has finished removing geofences 1057 are defined in the interface 1058<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html">LocationClient.OnRemoveGeofencesResultListener</a></code>. Declare 1059 this interface as part of your class definition, and then add definitions for its two methods: 1060</p> 1061<dl> 1062 <dt> 1063<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code> 1064 </dt> 1065 <dd> 1066 Callback invoked when Location Services finishes a request to remove all geofences made 1067 by the method 1068<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>. 1069 </dd> 1070 <dt> 1071<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> 1072 </dt> 1073 <dd> 1074 Callback invoked when Location Services finished a request to remove a set of geofences, 1075 specified by their geofence IDs, by the method 1076<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>. 1077 </dd> 1078</dl> 1079<p> 1080 Examples of implementing these methods are shown in the next snippets. 1081</p> 1082<h3>Remove all geofences</h3> 1083<p> 1084 Since removing geofences uses some of the methods you use to add geofences, start by defining 1085 another request type: 1086</p> 1087<pre> 1088public class MainActivity extends FragmentActivity implements 1089 ConnectionCallbacks, 1090 OnConnectionFailedListener, 1091 OnAddGeofencesResultListener { 1092 ... 1093 // Enum type for controlling the type of removal requested 1094 public enum REQUEST_TYPE = {ADD, REMOVE_INTENT} 1095 ... 1096} 1097</pre> 1098<p> 1099 Start the removal request by getting a connection to Location Services. If the connection fails, 1100<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code> isn't called, 1101 and the request stops. The following snippet shows how to start the request: 1102</p> 1103<pre> 1104public class MainActivity extends FragmentActivity implements 1105 ConnectionCallbacks, 1106 OnConnectionFailedListener, 1107 OnAddGeofencesResultListener { 1108 ... 1109 /** 1110 * Start a request to remove geofences by calling 1111 * LocationClient.connect() 1112 */ 1113 public void removeGeofences(PendingIntent requestIntent) { 1114 // Record the type of removal request 1115 mRequestType = REMOVE_INTENT; 1116 /* 1117 * Test for Google Play services after setting the request type. 1118 * If Google Play services isn't present, the request can be 1119 * restarted. 1120 */ 1121 if (!servicesConnected()) { 1122 return; 1123 } 1124 // Store the PendingIntent 1125 mGeofenceRequestIntent = requestIntent; 1126 /* 1127 * Create a new location client object. Since the current 1128 * activity class implements ConnectionCallbacks and 1129 * OnConnectionFailedListener, pass the current activity object 1130 * as the listener for both parameters 1131 */ 1132 mLocationClient = new LocationClient(this, this, this); 1133 // If a request is not already underway 1134 if (!mInProgress) { 1135 // Indicate that a request is underway 1136 mInProgress = true; 1137 // Request a connection from the client to Location Services 1138 mLocationClient.connect(); 1139 } else { 1140 /* 1141 * A request is already underway. You can handle 1142 * this situation by disconnecting the client, 1143 * re-setting the flag, and then re-trying the 1144 * request. 1145 */ 1146 } 1147 } 1148 ... 1149} 1150</pre> 1151<p> 1152 When Location Services invokes the callback method indicating that the connection is open, 1153 make the request to remove all geofences. Disconnect the client after making the request. 1154 For example: 1155</p> 1156<pre> 1157public class MainActivity extends FragmentActivity implements 1158 ConnectionCallbacks, 1159 OnConnectionFailedListener, 1160 OnAddGeofencesResultListener { 1161 ... 1162 /** 1163 * Once the connection is available, send a request to remove the 1164 * Geofences. The method signature used depends on which type of 1165 * remove request was originally received. 1166 */ 1167 private void onConnected(Bundle dataBundle) { 1168 /* 1169 * Choose what to do based on the request type set in 1170 * removeGeofences 1171 */ 1172 switch (mRequestType) { 1173 ... 1174 case REMOVE_INTENT : 1175 mLocationClient.removeGeofences( 1176 mGeofenceRequestIntent, this); 1177 break; 1178 ... 1179 } 1180 } 1181 ... 1182} 1183</pre> 1184<p> 1185 Although the call to 1186<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 1187 returns immediately, the result of the removal request is indeterminate until Location Services 1188 calls 1189<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code>. 1190 The following snippet shows how to define this method: 1191</p> 1192<pre> 1193public class MainActivity extends FragmentActivity implements 1194 ConnectionCallbacks, 1195 OnConnectionFailedListener, 1196 OnAddGeofencesResultListener { 1197 ... 1198 /** 1199 * When the request to remove geofences by PendingIntent returns, 1200 * handle the result. 1201 * 1202 *@param statusCode the code returned by Location Services 1203 *@param requestIntent The Intent used to request the removal. 1204 */ 1205 @Override 1206 public void onRemoveGeofencesByPendingIntentResult(int statusCode, 1207 PendingIntent requestIntent) { 1208 // If removing the geofences was successful 1209 if (statusCode == LocationStatusCodes.SUCCESS) { 1210 /* 1211 * Handle successful removal of geofences here. 1212 * You can send out a broadcast intent or update the UI. 1213 * geofences into the Intent's extended data. 1214 */ 1215 } else { 1216 // If adding the geocodes failed 1217 /* 1218 * Report errors here. 1219 * You can log the error using Log.e() or update 1220 * the UI. 1221 */ 1222 } 1223 /* 1224 * Disconnect the location client regardless of the 1225 * request status, and indicate that a request is no 1226 * longer in progress 1227 */ 1228 mInProgress = false; 1229 mLocationClient.disconnect(); 1230 } 1231 ... 1232} 1233</pre> 1234<h3>Remove individual geofences</h3> 1235<p> 1236 The procedure for removing an individual geofence or set of geofences is similar to the 1237 removal of all geofences. To specify the geofences you want remove, add their geofence ID 1238 values to a {@link java.util.List} of String objects. Pass this {@link java.util.List} to a 1239 different definition of {@code removeGeofences} with the appropriate signature. This method 1240 then starts the removal process. 1241</p> 1242<p> 1243 Start by adding a request type for removing geofences by a list, and also add a global variable 1244 for storing the list of geofences: 1245</p> 1246<pre> 1247 ... 1248 // Enum type for controlling the type of removal requested 1249 public enum REQUEST_TYPE = {ADD, REMOVE_INTENT, REMOVE_LIST} 1250 // Store the list of geofence Ids to remove 1251 String<List> mGeofencesToRemove; 1252</pre> 1253<p> 1254 Next, define a list of geofences you want to remove. For example, this snippet removes the 1255<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code> 1256 defined by the geofence ID "1": 1257</p> 1258<pre> 1259public class MainActivity extends FragmentActivity implements 1260 ConnectionCallbacks, 1261 OnConnectionFailedListener, 1262 OnAddGeofencesResultListener { 1263 ... 1264 List<String> listOfGeofences = 1265 Collections.singletonList("1"); 1266 removeGeofences(listOfGeofences); 1267 ... 1268} 1269</pre> 1270<p> 1271 The following snippet defines the {@code removeGeofences()} method: 1272</p> 1273<pre> 1274public class MainActivity extends FragmentActivity implements 1275 ConnectionCallbacks, 1276 OnConnectionFailedListener, 1277 OnAddGeofencesResultListener { 1278 ... 1279 /** 1280 * Start a request to remove monitoring by 1281 * calling LocationClient.connect() 1282 * 1283 */ 1284 public void removeGeofences(List<String> geofenceIds) { 1285 // If Google Play services is unavailable, exit 1286 // Record the type of removal request 1287 mRequestType = REMOVE_LIST; 1288 /* 1289 * Test for Google Play services after setting the request type. 1290 * If Google Play services isn't present, the request can be 1291 * restarted. 1292 */ 1293 if (!servicesConnected()) { 1294 return; 1295 } 1296 // Store the list of geofences to remove 1297 mGeofencesToRemove = geofenceIds; 1298 /* 1299 * Create a new location client object. Since the current 1300 * activity class implements ConnectionCallbacks and 1301 * OnConnectionFailedListener, pass the current activity object 1302 * as the listener for both parameters 1303 */ 1304 mLocationClient = new LocationClient(this, this, this); 1305 // If a request is not already underway 1306 if (!mInProgress) { 1307 // Indicate that a request is underway 1308 mInProgress = true; 1309 // Request a connection from the client to Location Services 1310 mLocationClient.connect(); 1311 } else { 1312 /* 1313 * A request is already underway. You can handle 1314 * this situation by disconnecting the client, 1315 * re-setting the flag, and then re-trying the 1316 * request. 1317 */ 1318 } 1319 } 1320 ... 1321} 1322</pre> 1323<p> 1324 When Location Services invokes the callback method indicating that the connection is open, 1325 make the request to remove the list of geofences. Disconnect the client after making the request. 1326 For example: 1327</p> 1328<pre> 1329public class MainActivity extends FragmentActivity implements 1330 ConnectionCallbacks, 1331 OnConnectionFailedListener, 1332 OnAddGeofencesResultListener { 1333 ... 1334 private void onConnected(Bundle dataBundle) { 1335 ... 1336 switch (mRequestType) { 1337 ... 1338 // If removeGeofencesById was called 1339 case REMOVE_LIST : 1340 mLocationClient.removeGeofences( 1341 mGeofencesToRemove, this); 1342 break; 1343 ... 1344 } 1345 ... 1346 } 1347 ... 1348} 1349</pre> 1350<p> 1351 Define an implementation of 1352<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult()</a></code>. 1353 Location Services invokes this callback method to indicate that the request to remove a list of 1354 geofences is complete. In this method, examine the incoming status code and take the 1355 appropriate action: 1356</p> 1357<pre> 1358public class MainActivity extends FragmentActivity implements 1359 ConnectionCallbacks, 1360 OnConnectionFailedListener, 1361 OnAddGeofencesResultListener { 1362 ... 1363 /** 1364 * When the request to remove geofences by IDs returns, handle the 1365 * result. 1366 * 1367 * @param statusCode The code returned by Location Services 1368 * @param geofenceRequestIds The IDs removed 1369 */ 1370 @Override 1371 public void onRemoveGeofencesByRequestIdsResult( 1372 int statusCode, String[] geofenceRequestIds) { 1373 // If removing the geocodes was successful 1374 if (LocationStatusCodes.SUCCESS == statusCode) { 1375 /* 1376 * Handle successful removal of geofences here. 1377 * You can send out a broadcast intent or update the UI. 1378 * geofences into the Intent's extended data. 1379 */ 1380 } else { 1381 // If removing the geofences failed 1382 /* 1383 * Report errors here. 1384 * You can log the error using Log.e() or update 1385 * the UI. 1386 */ 1387 } 1388 // Indicate that a request is no longer in progress 1389 mInProgress = false; 1390 // Disconnect the location client 1391 mLocationClient.disconnect(); 1392 } 1393 ... 1394} 1395</pre> 1396<p> 1397 You can combine geofencing with other location-aware features, such as periodic location updates 1398 or activity recognition, which are described in other lessons in this class. 1399</p> 1400<p> 1401 The next lesson, 1402 <a href="activity-recognition.html">Recognizing the User's Current Activity</a>, shows you how 1403 to request and receive activity updates. At regular intervals, Location Services can send you 1404 information about the user's current physical activity. Based on this information, you can 1405 change your app's behavior; for example, you can switch to a longer update interval if you 1406 detect that the user is walking instead of driving. 1407</p> 1408