Skip to content
This repository has been archived by the owner on Apr 23, 2023. It is now read-only.

Commit

Permalink
Fix mock trace tests and sample app (#127)
Browse files Browse the repository at this point in the history
* Un-ignore mock trace tests

* Removes Thread.sleep(100) statements

* Update test runner

* Extract TraceThread class

* Refactors mock gpx trace logic to be more testable

* Adds fastest interval to sample app

* Unused imports

* Load sample app gpx trace from external files dir

* Adds bash script to install gpx trace file into app external files dir

* Update mock-locations-routes.md
  • Loading branch information
ecgreb authored Oct 13, 2016
1 parent d81d573 commit a7f2084
Show file tree
Hide file tree
Showing 14 changed files with 411 additions and 229 deletions.
27 changes: 22 additions & 5 deletions docs/mock-locations-routes.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#Mock Locations
# Mock Locations

With Lost you can mock not just individual locations but also entire routes. By loading a [GPX trace file](http://www.topografix.com/gpx.asp) onto the device you can configure Lost to replay locations from the trace file including latitude, longitude, speed, and bearing.

**Mocking a single location**
## Mocking a single location
To mock a single location with Lost you must first enable mock mode. Then you simply create a mock location object and pass it to the API.

```java
Expand All @@ -16,11 +16,28 @@ LocationServices.FusedLocationApi.setMockLocation(mockLocation);

The mock location object you set will be immediately returned to all registered listeners and will be returned the next time `getLastLocation()` is called.

**Mocking an entire route**
To mock an entire route you must first transfer a [GPX trace file](http://www.topografix.com/gpx.asp) to the device using [adb](http://developer.android.com/tools/help/adb.html). Sample GPX traces can be found on the [public GPS traces page](http://www.openstreetmap.org/traces) for OpenStreetMap. Once the trace file is loaded on the device you can tell Lost to replay the locations in the trace at the requested update interval.
## Mocking an entire route
To mock an entire route you must first transfer a [GPX trace file](http://www.topografix.com/gpx.asp) to the device using [adb](http://developer.android.com/tools/help/adb.html). Sample GPX traces can be found on the [public GPS traces page](http://www.openstreetmap.org/traces) for OpenStreetMap.

### Install GPX File
To install your gpx file onto your device, run the `install-gpx-trace.sh` script provided.

Example:

```bash
install-gpx-trace.sh lost.gpx com.example.myapp
```

Once the trace file is loaded on the device you can tell Lost to replay the locations in the trace by issuing a `LocationRequest`. Mock locations will be emitted according to the fastest interval value.

```java
File file = new File(Environment.getExternalStorageDirectory(), "mock_track.gpx");
File file = new File(context.getExternalFilesDir(null), "lost.gpx");
LocationServices.FusedLocationApi.setMockMode(true);
LocationServices.FusedLocationApi.setMockTrace(file);
LocationRequest locationRequest = LocationRequest.create()
.setInterval(1000)
.setFastestInterval(1000) // Mock locations will replay at this interval.
.setSmallestDisplacement(0)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
FusedLocationApi.requestLocationUpdates(lostApiClient, locationRequest, locationListener);
```
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,25 @@ private boolean isMockModePrefEnabled() {

if (prefs.getBoolean(getString(R.string.mock_mode_gpx_key), false)) {
String filename = prefs.getString(getString(R.string.mock_gpx_file_key), null);
File file = new File(Environment.getExternalStorageDirectory(), filename);
File file = new File(getExternalFilesDir(null), filename);
FusedLocationApi.setMockTrace(client, file);
}

final Resources res = getResources();

final String intervalKey = getString(R.string.interval_key);
final String displacementKey = getString(R.string.displacement_key);
final String priorityKey = getString(R.string.priority_key);

final Resources res = getResources();
final int intervalDefaultValue = res.getInteger(R.integer.interval_default_value);
final int displacementDefaultValue = res.getInteger(R.integer.displacement_default_value);
final int priorityDefaultValue = res.getInteger(R.integer.priority_default_value);

final LocationRequest locationRequest = LocationRequest.create()
.setInterval(
prefs.getInt(intervalKey, res.getInteger(R.integer.interval_default_value)))
.setSmallestDisplacement(
prefs.getInt(displacementKey, res.getInteger(R.integer.displacement_default_value)))
.setPriority(
prefs.getInt(priorityKey, res.getInteger(R.integer.priority_default_value)));
.setInterval(prefs.getInt(intervalKey, intervalDefaultValue))
.setFastestInterval(prefs.getInt(intervalKey, intervalDefaultValue))
.setSmallestDisplacement(prefs.getInt(displacementKey, displacementDefaultValue))
.setPriority(prefs.getInt(priorityKey, priorityDefaultValue));

FusedLocationApi.requestLocationUpdates(client, locationRequest, listener);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private void toggleMockMode() {
mockMode = !mockMode;
locationEngine.setRequest(null);
if (mockMode) {
locationEngine = new MockEngine(context, this);
locationEngine = new MockEngine(context, this, new GpxTraceThreadFactory());
} else {
locationEngine = new FusionEngine(context, this);
}
Expand All @@ -186,5 +186,4 @@ private void notifyLocationAvailabilityChanged() {
final LocationAvailability availability = locationEngine.createLocationAvailability();
clientManager.notifyLocationAvailability(availability);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mapzen.android.lost.internal;

import android.content.Context;

import java.io.File;

public class GpxTraceThreadFactory implements TraceThreadFactory {
@Override public TraceThread createTraceThread(Context context, File traceFile, MockEngine engine,
SleepFactory sleepFactory) {
return new TraceThread(context, traceFile, engine, sleepFactory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.List;

/**
*
* Set of location requests used to synthesize parameters for location updates.
*/
public class LocationRequestUnbundled {

Expand Down
116 changes: 6 additions & 110 deletions lost/src/main/java/com/mapzen/android/lost/internal/MockEngine.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,25 @@
package com.mapzen.android.lost.internal;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.content.Context;
import android.location.Location;
import android.os.Handler;

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

/**
* Mock implementation of LocationEngine that reports single locations and/or full GPX traces.
*/
public class MockEngine extends LocationEngine {
public static final String MOCK_PROVIDER = "mock";

// GPX tags
public static final String TAG_TRACK_POINT = "trkpt";
public static final String TAG_SPEED = "speed";
public static final String TAG_LAT = "lat";
public static final String TAG_LNG = "lon";

private Location location;
private File traceFile;
private TraceThreadFactory traceThreadFactory;
protected TraceThread traceThread;

public MockEngine(Context context, FusionEngine.Callback callback) {
public MockEngine(Context context, FusionEngine.Callback callback,
TraceThreadFactory traceThreadFactory) {
super(context, callback);
this.traceThreadFactory = traceThreadFactory;
}

@Override public Location getLastLocation() {
Expand All @@ -50,7 +32,8 @@ public MockEngine(Context context, FusionEngine.Callback callback) {

@Override protected void enable() {
if (traceFile != null) {
traceThread = new TraceThread();
traceThread = traceThreadFactory.createTraceThread(getContext(), traceFile, this,
new ThreadSleepFactory());
traceThread.start();
}
}
Expand Down Expand Up @@ -82,91 +65,4 @@ public void setTrace(File file) {
public File getTrace() {
return traceFile;
}

protected class TraceThread extends Thread {
private boolean canceled;
private Location previous;

public void cancel() {
canceled = true;
interrupt();
}

@Override public void run() {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final XPath xPath = XPathFactory.newInstance().newXPath();
final String expression = "//" + TAG_TRACK_POINT;
final String speedExpression = "//" + TAG_SPEED;

NodeList nodeList = null;
NodeList speedList = null;
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(traceFile);
nodeList = (NodeList) xPath.compile(expression).evaluate(document, XPathConstants.NODESET);
speedList =
(NodeList) xPath.compile(speedExpression).evaluate(document, XPathConstants.NODESET);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
}

parse(nodeList, speedList);
}

private void parse(NodeList nodeList, NodeList speedList) {
if (nodeList != null) {
for (int i = 0; i < nodeList.getLength(); i++) {
postMockLocation(nodeToLocation(nodeList, speedList, i));
sleepFastestInterval();
}
}
}

private Location nodeToLocation(NodeList nodeList, NodeList speedList, int i) {
final Node node = nodeList.item(i);
String lat = node.getAttributes().getNamedItem(TAG_LAT).getNodeValue();
String lng = node.getAttributes().getNamedItem(TAG_LNG).getNodeValue();

final Location location = new Location(MOCK_PROVIDER);
location.setLatitude(Double.parseDouble(lat));
location.setLongitude(Double.parseDouble(lng));
location.setTime(System.currentTimeMillis());
if (speedList.item(i) != null && speedList.item(i).getFirstChild() != null) {
location.setSpeed(Float.parseFloat(speedList.item(i).getFirstChild().getNodeValue()));
}

if (previous != null) {
location.setBearing(previous.bearingTo(location));
}

previous = location;
return location;
}

private void sleepFastestInterval() {
if (getRequest() != null) {
try {
Thread.sleep(getRequest().getFastestInterval());
} catch (InterruptedException e) {
canceled = true;
}
}
}

private void postMockLocation(final Location mockLocation) {
new Handler(getContext().getMainLooper()).post(new Runnable() {
@Override public void run() {
if (!canceled) {
setLocation(mockLocation);
}
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mapzen.android.lost.internal;

interface SleepFactory {
void sleep(long millis);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mapzen.android.lost.internal;

class ThreadSleepFactory implements SleepFactory {
@Override public void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Loading

0 comments on commit a7f2084

Please sign in to comment.