Deprecated: Creation of dynamic property wpdb::$categories is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$post2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$link2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Return type of WP_Theme::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 554

Deprecated: Return type of WP_Theme::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 595

Deprecated: Return type of WP_Theme::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 535

Deprecated: Return type of WP_Theme::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 544

Deprecated: Return type of WP_REST_Request::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 960

Deprecated: Return type of WP_REST_Request::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 980

Deprecated: Return type of WP_REST_Request::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 992

Deprecated: Return type of WP_REST_Request::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 1003

Deprecated: Return type of WP_Block_List::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 151

Deprecated: Return type of WP_Block_List::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 175

Deprecated: Return type of WP_Block_List::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 164

Deprecated: Return type of WP_Block_List::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 186

Deprecated: Return type of WP_Block_List::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 138

Deprecated: Return type of WP_Block_List::offsetExists($index) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 75

Deprecated: Return type of WP_Block_List::offsetGet($index) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 89

Deprecated: Return type of WP_Block_List::offsetSet($index, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 110

Deprecated: Return type of WP_Block_List::offsetUnset($index) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 127

Deprecated: Return type of WP_Block_List::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 199

Deprecated: Creation of dynamic property Automattic\Jetpack\Connection\Manager::$error_handler is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-connection/src/class-manager.php on line 91

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Instagram_Gallery::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-instagram-gallery.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Mailchimp::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php on line 17

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 21

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 22

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php on line 20

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 24

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Subscribers::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php on line 15

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property WPCOM_Widget_Goodreads::$shelves is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/modules/widgets/goodreads.php on line 33

Deprecated: DateTime::__construct(): Passing null to parameter #1 ($datetime) of type string is deprecated in /var/www/twodee/blog/wp-includes/script-loader.php on line 348

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_classic_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 347

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 349

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block_side is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 350

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$panels_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 351

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$used_block_buttons is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 354

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198
» CS 436 Lecture 24 – O Marks The Spot

teaching machines

CS 436 Lecture 24 – O Marks The Spot

November 20, 2014 by . Filed under
Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383
cs436, fall 2014, lectures.


Deprecated: ltrim(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 3030

Deprecated: parse_str(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/class.jetpack.php on line 4255

Deprecated: Creation of dynamic property Jetpack_Carousel::$localize_strings is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/modules/carousel/jetpack-carousel.php on line 339

The app we’ll write today is inspired by the Reverse Geocache Puzzle. Intel software engineer Mikal Hart had a friend who was getting married. This friend had introduced him to Arduino, a cheap computer that can be programmed and integrated into small appliances. Mikal returned the favor by giving him an Arduino-powered wedding gift: a box that would only open at a certain location in France. If the couple tried to open it elsewhere, the box would deny them access but report how far away they were from its location.

A Mobile App Version

Let’s make a mobile app that works similarly. I’m not quite sure what surprise we want to unlock, but we can focus on the mechanics of location for now. Our mobile app will operate as follows:

  1. The giver installs the app on the receiver’s GPS-enabled phone.
  2. The giver identifies a secret location by long-pressing on a Google Map.
  3. The receiver visits various locations and presses a button to determine the distance from the secret location. The receiver knows that the secret location lies somewhere on the perimeter of the circle centered on the receiver’s current location and whose radius is the reported distance. This circle is plotted on the map.
  4. With enough readings, the receiver has enough information to narrow down the secret location.

Google Play Services

Before we can embed a Google Map in our app, we must link in Google Play Services. This library is downloaded through the SDK Manager. Install both Google Play Services and the Google Repository, both of which are listed under Extras.

Add a line to the dependencies section of your app’s build.gradle script:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.gms:play-services:6.1.+'
}

Update the version number as needed.

Then in the application tag in manifest, declare the version of the Google Mobile Services you are using. The version number of the Google Play Services library shown in the SDK manager is not helpful. Instead, you can draw this number out of the library itself:

<meta-data
  android:name="com.google.android.gms.version"
  android:value="@integer/google_play_services_version"/>

Register the App with Google APIs Console

Web services like Google Maps are not entirely free. Heavy and frequent users may need to pay Google for premium services. Lightweight users like us can usually make a limited number of requests. For Google to monitor our usage, we must send along an API key with every request.

First, we register our app at the Google Developers Console. We create a project and enable Google Maps Android API v2. Under Credentials, we create a new key for public API access from Android devices. We are prompted for the SHA1 digest of the certificate used to sign our app. For now we’re content to use the debugging certificate. We can get the SHA1 digest on Linux and Mac at the shell:

cd ~/.android
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Windows users will have to do something different.

We paste the SHA1 digest into the console, appending ;com.example.package.of.app. The console spits back an API key, which we can embed in the application element in the manifest:

<meta-data
  android:name="com.google.android.maps.v2.API_KEY"
  android:value="YOUR_API_KEY"/>

Embed Google Map

With the library and API key in place, we can easily embed a map into our app using a MapFragment. We add the following to our layout XML:

<fragment
  android:id="@+id/map"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:name="com.google.android.gms.maps.MapFragment"/>

Does the app run?

Permissions

For the maps to work, we need a few permissions. Map data must be downloaded over the Internet, the library wants to query for network connectivity, and tiles of map data will be cached on external storage of the local device:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

While we’re in the manifest, let’s also add the permissions we’ll need to for locating the device:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

Lastly, if we plan to publish our app, we should declare our use of OpenGL ES 2, so that our app doesn’t show up in Google Play on devices that don’t support it:

<uses-feature
  android:glEsVersion="0x00020000"
  android:required="true"/>

Selecting Secret Location

We now want to let the giver register the secret location. To keep things simple, let’s ask the giver to issue an initial long-press on the map. The selected latitude/longitude pair will be the secret.

In onCreate, we can get a reference to the map shown by the fragment:

private GoogleMap map;

public void onCreate(Bundle savedInstanceState) {
  // ...
  MapFragment mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map);
  map = mapFragment.getMap();
}

Let’s add a method for registering the treasure’s location:

private Location treasureLocation;

private void setTreasureLocation(LatLng ll) {
  treasureLocation = new Location("Treasure");
  treasureLocation.setLatitude(ll.latitude);
  treasureLocation.setLongitude(ll.longitude);
  // Show a marker for debugging purposes?
  // map.addMarker(new MarkerOptions().title("Treasure").position(ll));
  Toast.makeText(MainActivity.this, "Treasure hidden...", Toast.LENGTH_SHORT).show();
}

We use a Location here because this class has methods for measuring the distance between two locations. LatLng does not.

Back at the end of onCreate, we can hook up a call to this method when the giver long-presses on the map:

map.setOnMapLongClickListener(new OnMapLongClickListener() {
  @Override
  public void onMapLongClick(LatLng ll) {
    setTreasureLocation(ll);
  }
});

GPS Readings

After the giver identifies the secret location, the receiver must figure out how far the device is from the secret location. For that, we need to determine the device’s location. Android has offered several ways of doing in its short history; we’ll go with the most recent method, though the current official tutorial documentation does not.

Let’s start by enabling the My Location layer on the map. We may do this in onCreate:

map.setMyLocationEnabled(true);

That gets our location drawn on the map. Now, let’s also get our location sent to our code using a Google API. Android has unified all access to Google APIs under the GoogleApiClient class. We can select the specific APIs we want using a builder in onCreate:

private GoogleApiClient apiClient;

public void onCreate(Bundle savedInstanceState) {
  // ...
  apiClient = new GoogleApiClient.Builder(this)
    .addApi(LocationServices.API)
    .addConnectionCallbacks(this)
    .addOnConnectionFailedListener(this)
    .build();
}

Our activity must implement interfaces  GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener:

@Override
public void onConnected(Bundle bundle) {
  Log.d("FOO", "connected");
}

@Override
public void onConnectionSuspended(int i) {
  Log.d("FOO", "connection suspended");
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
  Log.d("FOO", "connection failed");
}

Once we’ve connected to the API, we can issue a request for location updates. We have a lot of flexibility in determining how sensitive we want to be to location changes. Since we are testing, let’s burn battery by getting frequent updates:

@Override
public void onConnected(Bundle bundle) {
  Log.d("FOO", "connected");

  LocationRequest locationRequest = LocationRequest.create();
  locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
  locationRequest.setInterval(1000);
  locationRequest.setFastestInterval(1000);

  LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, this);
}

We’ve just forced our activity to implement the LocationListener interface:

private Location lastLocation;

@Override
public void onLocationChanged(Location location) {
  Log.d("FOO", "Got location: " + location);
  lastLocation = location;
}

Finally, we only want to get location updates while our app is visible, so we initiate the connection in onStart and disconnect in onStop:

@Override
protected void onStart() {
  super.onStart();
  apiClient.connect();
}

@Override
protected void onStop() {
  super.onStop();
  LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, this);
  apiClient.disconnect();
}

Measure Action

Now that our activity is getting periodic notifications about the device’s whereabouts, let’s add something to the user interface so the receiver can query the distance to the secret location. Let’s use the ActionBar.

We start by adding an entry to the activity’s menu XML:

<item android:id="@+id/action_measure"
  android:title="Measure"
  android:showAsAction="always"/>

We define the callbacks for populating the menu and responding to a click on the measure action:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.menu_main, menu);
  return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
  if (item.getItemId() == R.id.action_measure) {
    measure();
    return true;
  } else {
    return super.onOptionsItemSelected(item);
  }
}

The measure method then computes the distance between the last known location and the secret location. It plops a circle glyph on the map, using the distance as its radius and the last known location as its center:

private void measure() {
  if (lastLocation == null) {
    float distance = treasureLocation.distanceTo(lastLocation);
    LatLng ll = new LatLng(lastLocation.getLatitude(), lastLocation.getLongitude());
    map.addCircle(new CircleOptions().center(ll).radius(distance).strokeColor(Color.BLUE));
  }
}

Our app is more or less ready!

Mock Locations

To test our app as it stands, we must physically move around the earth. The GPS sensor is talking with satellites that really exist and getting real information. Enumerating all possible realities in the lab makes testing difficult. However, we can trick our app by using mock locations.

This is really easy to do on the emulator. However, getting Google Play Services on the emulator is less than natural. Instead, we’re going to enable mock locations on a real device. This isn’t as easy.

We start by enabling mock locations in the device’s developer settings.

We then add a new permission to the manifest:

<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

This gives a warning in Android Studio. I don’t know how to fix it. If you do, please comment.

After we connect—in onConnected—we enable mock mode:

LocationServices.FusedLocationApi.setMockMode(apiClient, true);

We add a method that submits a mock location to LocationServices. The requirements here are poorly documented in the Android tutorial. Be sure to set the times. Not doing so meant I didn’t get any location updates. Allegedly, the provider sent to the Location constructor is also important.

private void setMockLocation(LatLng ll) {
  Location location = new Location("network");
  location.setLatitude(ll.latitude);
  location.setLongitude(ll.longitude);
  location.setAccuracy(3.0f);
  location.setTime(new Date().getTime());
  location.setElapsedRealtimeNanos(System.nanoTime());
  LocationServices.FusedLocationApi.setMockLocation(apiClient, location);
  Toast.makeText(MainActivity.this, "Faked location...", Toast.LENGTH_SHORT).show();
}

So, how should we call this method? I’d like to have as much control as possible over the tests, so let’s switch our map’s long-press listener to now submit mock locations instead of registering the treasure location. We can do this at the end of setTreasureLocation:

private void setTreasureLocation(LatLng ll) {
  // ...
  map.setOnMapLongClickListener(new OnMapLongClickListener() {
    @Override
    public void onMapLongClick(LatLng ll) {
      setMockLocation(ll);
    }
  });
}

The Result

The secret location lies somewhere on this perimeter around Carson Park.

The secret location lies somewhere on this perimeter around Carson Park.

After taking a second reading at Putnam Rock, we narrow down the secret location to two choices. The secret location must be on the perimeter of both of the circles.

After taking a second reading at Putnam Rock, we narrow down the secret location to two choices. The secret location must be on the perimeter of both of the circles.

A third reading at Mt. Tom Park eliminates one of our choices. We see that the secret location is cave located on the Eau Claire River.

A third reading at Mt. Tom Park eliminates one of our choices. We see that the secret location is cave located on the Eau Claire River.

Resources