Tracks2Miles 1.1.0

The latest Tracks2Miles update

Added a timeline view to see what you friends have been up to.

*** Edit ***

I’ve just noticed that on some phones that after the update the launch icon will still open the Activity for publishing a new workout rather than the Timeline Activity. This is a “feature” of some of the launchers, you may need to restart your phone to get it to see the update.

*** Edit ***

Fixed up a couple of race condition type problems.

Updated the code to import from My Tracks to use the patch that has been accepted for the next release.

My Tracks Patched

The guys at My Tracks have integrated the changes I sent to enable more data to be passed with the GPX file. There are some slight changes needed to the currently released version of Tracks2Miles but I will push a new version in the next couple of days to be ready for the next My Tracks drop.

I don’t know when the next version of My Tracks will ship yet, but we will be ready,

In the meantime here are the details of the change so others can use it to build similar add-ons for any other site (why they would want to use anything but dailymile?).

As mentioned back in the first post on this subject your going to need an Intent filter that will trap when My Tracks is looking to export a GPX file (the changes to My Tracks apply to all file types, but I’m only going to talk about GPX here). The following is what I’ve been using for Tracks2Miles:

 <activity android:name=".Export"
      android:label="@string/app_name" android:exported="true">
     <action android:name="android.intent.action.SEND" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:mimeType="application/gpx+xml" />

This hooks the “android.intent.action.SEND” action and filters it for the mimeType “application/gpx+xml”. So in the Activity that gets called when an intent matching this is broadcast you will need a onCreate method that looks something like this.

public void onCreate(Bundle savedInstanceState) {
  Intent intent = getIntent();

  if (intent.getAction().equals(Intent.ACTION_SEND)) {
    if (intent.hasExtra("")) {
      Track track = (Track) intent.getParcelableExtra("");

    Uri gpxURI = (Uri)intent.getExtras().get(Intent.EXTRA_STREAM);

Where Track is an instance of You will need to grab this and the 2 classes in the package so they can be on the classpath for getParcelableExtra to find them. From the Track object you can get hold of name, time and distance of the workout and also there is a Statistics field that has info on average/max speed, elevation information.

I’m planning on writing a My Tracks wiki page about this some time soon where I’ll go in to a little bit more detail but this should get most people started.

Tracks2Miles 1.0.4

A quick update about the 1.0.4 * update release of Tracks2Miles.

  • Added some better error path handling to make it a bit more graceful should it fall over.
  • Added a local DB to cache route information, this is for 2 reasons. Firstly to make it quicker to just pick a route without having to wait for the list to be downloaded and secondly as a work around for the missing distance info in routes uploaded via the API. One of the side effects of this is that previously uploaded routes with no distance will not be shown in the list. Look at the end of the post for a way to fix these routes.
  • Added a bit more validation before posting, this should make it harder to post routes without titles or time and distances. A dialogue will ask if you really want to do this before posting.

I’ve also found a way to fix the uploaded routes missing distances. If you view the routes on the dailymile website, then hit the edit link at the top it will open the route editor. At this point it will recalculate the distance and you can just hit the Update button in the bottom right hand corner. The route should now have a valid distance.

Based on Dan’s comments on the announcement entry, I’m planning on looking to see if I can identify an existing route before posting a new one from My Tracks, this will reduce upload time and allow for dailymile to track your best time. To keep things easy it will probably just be based on the name of the track and distance.

I have added the following short video to the Android Market entry.

* 1.0.2 & 1.0.3 wouldn’t work on 2.1 due to using a method in API v8, I’ll remember to check 2.1 in the emulator before uploading next time

Announcing Tracks2Miles

OK, so the last 3* posts have been about the development of this so I guess you all knew it was coming, but now after some kind folk (Thanks @davidgeorgeuk, @bodonovan, @TheMightyAl & @ragtag) beta tested it for me it’s time to release it to the world.

A Friday afternoon chat between myself, Karl Roche, Brian O’Donovan and Dale Lane about tracking exercise habits with the website dailymile led to wondering why there was no Android app for it yet. A quick look at the API suggested it shouldn’t be too hard run something up. A short weekend of hacking later the majority of Tracks2Miles was done.
Tracks2Miles Icon
Tracks2Miles is an Android application to take workouts recorded with My Tracks and uploads them dailymile.

Rather than reinvent the wheel and write a full GPS tracking app I decided to see how easy it would be to extend one of the existing apps. My Tracks was one of the first training apps released for Android so it seamed like a good place to start. My Tracks have released a library to allow embedding the GPS tracking into new apps, but even this looked like a lot of work, but My Tracks also has a feature to “Share with friends…” which uses the android.intent.action.SEND intent action to allow other apps to forward on the data in a number of formats, one of which is GPX. This made writing the Tracks2Miles very easy.

Current Features:

  • Export routes from My Tracks – use the “Share with friends…” menu option to share the current track
  • Post workouts without using My Tracks – Tracks2Miles can be launched directly to upload a workout without uploading a new track e.g. treadmill seasons
  • Existing route reuse – start Tracks2Miles from the launcher and hit the “Use existing route” button and you can use routes already uploaded to dailymile

Possible Upcoming Features:

  • Support other apps – add support for exporting from apps like Endomondo or Runkeeper if they can export GPX files
  • Export any GPX – search the file system and upload any GPX found
  • dailymile timeline – a way to see what everybody else has been up to and add comments and motivation
  • Offline caching – save on bandwidth and API calls
  • User preferences – store defaults for Rating and workout type type
  • Contact syncing – add your running buddies to your phones contact list
  • Retry on network loss – uploading long routes can take a while over slow networks so a bit more fault tolerance would be good
  • Widget – because every app needs a widget

Known issues:

  • Facebook login not working – When authorising Tracks2Miles using an account created using your Facebook account it says password is wrong. This looks like either a problem on the dailymile or Facebook side of things, I have reported it
  • Time and distance not filled in when exporting from My Tracks – a patch to make this happen have been submitted to My Tracks, hopefully it will make it into the next release.
  • Reused routes have zero length – Some routes returned by dailymile are shown to have zero length so the distance is not filled in when using them. I have reported this to dailymile.

I’ve tested this on a HTC Desire, a ZTE Blade (Orange San Francisco) (updated to 2.2), a Samsung Galaxy and a Motorola Defy devices along with in the emulator running 2.1, 2.2 and 2.3. It should run on Android 2.1 or newer.

The Android Market URL is or us the following QR code:


  1. My Tracks to dailymile GPX route exporter – part 1
  2. My Tracks to dailymile GPX route exporter – part 2
  3. My Tracks to dailymile GPX route exporter – part 3

My Tracks to dailymile GPX route exporter – part 3

So I’ve got the oAuth 2.0 all sorted it’s time to write the code to do the actual publish to dailymile.

First up I need a UI to capture all the other info about the workout other than the route. Not all of the options on the website are available via the API, but there are enough make a useful post.

I wanted to fill in as many of the fields as possible, so the route name comes from the GPX file name. When My Tracks creates the intent to share the GPX file it adds 2 extras which map to the equivalent of an email subject and email some body text to go with the file, but none of this contains data like the distance covered or the time. This information isn’t explicitly in the default GPX files either.

I could parse the GPX file and generate this info, but it’s going to be a heavy weight process and the data is something My Tracks should already have. I had a quick look at some of the other formats that My Tracks can export and found a the track description field in the KML version which holds the info I’m looking for.

<desc><![CDATA[Created by <a href=''>My Tracks</a< on Android.<p>Total Distance: 223.32 km (138.8 mi)<br>Total Time: 1:57<br>Moving Time: 0:20<br>Average Speed: 6866.26 km/h (4266.5 mi/h)<br> Average Moving Speed: 40197.14 km/h (24977.3 mi/h)<br> Max Speed: 0.00 km/h (0.0 mi/h)<br>Min Elevation: 0 m (0 ft)<br>Max Elevation: 0 m (0 ft)<br>Elevation Gain: 0 m (0 ft)<br>Max Grade: 0 %<br>Min Grade: 0 %<br>Recorded: Tue Apr 12 18:53:08 GMT+00:00 2011<br>Activity type: -<br><img border="0" src=",y&chxr=0,0,223,37|1,0.0,100.0,25&chco=009A00&chm=B,00AA00,0,0,0&chg=100000,25.0,1,0&chd=e:,"/>]]></desc>

There is a similar field in the GPX file that is currently empty. A quick look at the code showed that a 5 line patch should populate that field with the same data. I’ve sent the patch off to the My Tracks guys, but in the mean time if the field is empty the user will have to fill in the time and distance by hand.

With the UI sorted, the publishing is done in a service as it’s a multi-stage process including uploading the GPX file which could take a while over mobile networks. It’s basically 3 dailymile API calls

  1. Create a new route – POST
  2. Upload GPS data to form the route – PUT
  3. Create new workout entry – POST

Interspersing these with Notification updates so the user can see how far through the process it is.


private void upload() {
HttpResponse resp;
final HttpClient httpClient = new DefaultHttpClient();</code>

NotificationManager notificationManager = (NotificationManager) getSystemService(ns);

Notification notification = new Notification(R.drawable.icon, "Uploading Workout", System.currentTimeMillis());
Context context = getApplicationContext();

Intent notificationIntent = new Intent();
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

notification.setLatestEventInfo(context, "Uploading Workout", "Creating new Route", contentIntent);
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_NO_CLEAR;
notificationManager.notify(1, notification);
Log.i("DailyMile",Constants.NEW_ROUTE_URL + authToken);
final HttpPost post = new HttpPost(Constants.NEW_ROUTE_URL + authToken);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("name", routeName));
nameValuePairs.add(new BasicNameValuePair("activity_type", routeType));
try {
post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
String routeID = "";
try {
resp = httpClient.execute(post);
if (resp.getStatusLine().getStatusCode() == 201) {
InputStream in = resp.getEntity().getContent();
StringBuffer buffer = new StringBuffer();
for (int i =; i != -1; i = {
JSONObject jsObject = new JSONObject(new JSONTokener(buffer.toString()));
routeID = jsObject.getString("id");
Log.i("DailyMile", routeID);
} else {
Log.i("DailyMile","" + resp.getStatusLine().getStatusCode());
notification.setLatestEventInfo(context, "Uploading Workout", "Failed to create a new route", contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(1, notification);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block
catch (JSONException e) {
// TODO Auto-generated catch block
notification.setLatestEventInfo(context, "Uploading Workout", "Uploading Route", contentIntent);
notificationManager.notify(1, notification);

String putURL = Constants.UPDATE_ROUTE_URL.replace("{id}", routeID) + authToken;
Log.i("DailyMile", putURL);
final HttpPut put = new HttpPut(putURL);
try {
ParcelFileDescriptor pfd = this.getContentResolver().openFileDescriptor(gpxURI, "r");
InputStream in = this.getContentResolver().openInputStream(gpxURI);
InputStreamEntity entity = new InputStreamEntity(in, pfd.getStatSize());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block

try {
resp = httpClient.execute(put);
if (resp.getStatusLine().getStatusCode() != 201) {
Log.i("DailyMile","" + resp.getStatusLine().getStatusCode());
notification.setLatestEventInfo(context, "Uploading Workout", "Failed to upload new route", contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(1, notification);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block

notification.setLatestEventInfo(context, "Uploading Workout", "Adding new workout", contentIntent);
notificationManager.notify(1, notification);

final HttpPost postWorkOut = new HttpPost(Constants.NEW_ENTRY_URL+authToken);
StringEntity entity = null;
JSONObject json = new JSONObject();
JSONObject workout = new JSONObject();
JSONObject distance = new JSONObject();
try {
workout.put("activity_type", routeType);
workout.put("felt", workoutRating.toLowerCase());
distance.put("units", workoutDistanceUnits);
workout.put("distance", distance);
json.put("workout", workout);
entity = new StringEntity(json.toString());
} catch (JSONException e) {
// TODO Auto-generated catch block
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block

try {
resp = httpClient.execute(postWorkOut);
if (resp.getStatusLine().getStatusCode() != 201) {
Log.i("DailyMile","" + resp.getStatusLine().getStatusCode());
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.setLatestEventInfo(context, "Uploading Workout", "Failed to upload new workout", contentIntent);
notificationManager.notify(1, notification);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block

notification.setLatestEventInfo(context, "Uploading Workout", "All Done", contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(1, notification);



So it’s all done now, just got to jump trough a few hopes to get it sign off to publish it in the Android Market and hopefully the My Tracks guys will include my patch in the next drop.

My Tracks to dailymile GPX route exporter – part 2

In my last post I talked about a project I’m working on to export GPS tracks from My Tracks directly to dailymile.

I was having problems getting the mobile client flow for oAuth 2.0 to work with dailymile. As a work around I decided to see if I could get the User-Agent flow to work for this application.

At the end of the previous post I suggested using an Intent filter to catch a redirect to a custom URI schema, while I got this to work it was messy as it left the browser app on the activity stack during the authentication. While looking for a way round this I found the documentation for the WebView widget and came up a much better solution. The WebView widget allows you to embed HTML content in your application so it should be possible to embed the web based authorisation set into a Activity.

WebView webView = new WebView(this);
final Activity activity = this;

In the onCreate method of the Activity I have done away with a normal layout and just used a full screen WebView and asked it to load the authentication URL from the User-Agent Flow<CLIENT_ID>&redirect_uri=dm://locahost

Login Authorise

The callback redirect URI (dm://localhost) still has a custom schema similar to the one suggested in the previous post. Assuming the user allows access to their dailymile profile we can intercept the redirect by overriding the WebViewClient on the Webview which will look a bit like this:


webView.setWebViewClient(new WebViewClient() {
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("dm://")) {
      String summary = "<html><body>Grabbing Account info</body></html>";
      view.loadData(summary, "text/html", "utf-8");
      String token = url.substring(url.lastIndexOf("=")+1);
      User user = Utilities.getAccountDetails(token, activity, handler);
      if (user != null) {
      return false;
    return true;

Now I’ve got the AuthToken I can tuck it away in the Android AccountManager so I can grab it later when I want to upload a training session.

My Tracks to dailymile GPX route exporter – part 1

On Friday afternoon a few people I know were talking about a web site for people who like to record their running and cycling and share it with their friends, the site is called dailymile. Dailymile allows you to upload GPS recordings of your routes so you can work out how far you’ve gone and also share routes with others.

Those of us that have Android phones tend to use an app called My Tracks to do the GPS recording and time our excursions. At the moment you either have to email the GPX files to yourself or if you have the Dropbox application installed, save them to the cloud storage before you can use dailymile’s web interface to upload them to the site.

Dailymile have recently published a REST API which includes an end point to upload new routes to, so it looked like it shouldn’t be too hard to put something together allow direct exporting of training sessions.

Getting something into the My Track’s “Share with Friends…” list is just a case of setting the right Intent Filter

 <activity android:name=".Export"
      android:label="@string/app_name" android:exported="true">
     <action android:name="android.intent.action.SEND" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:mimeType="application/gpx+xml" />

Share with Friends...
Share with Friends...

I had already been playing with adding Account mangers to the Accounts & Sync section under the Android settings for a work project and this seamed like the best way to manage the OAuth requirements for the dailymile API. So I set about writing a SyncAdapter based on the previous code I already had. The problem is that dailymile have chosen to use OAuth 2.0 which is still a draft, so while there are a load of Java libraries out there that have support for 1.0 there are only a couple that have varying levels of support for different levels of draft for 2.0. The other problem is that there is no example on the dailymile api page of how to do OAuth authentication from a mobile app, which I’m starting to think means they’ve not implemented it yet.

A site with a very similar name (DailyMotion) also uses OAuth 2.0 and has an example of how to do mobile app authentication, following this would lead to the following curl test.

curl -F grant_type=password -F client_id=[client_id] -F client_secret=[client_secret] -F username=[username] -F password=[password]

This gives the following output


So I’m wondering if this means that dailymile have to enable specific clients for this direct access to a token.

I’ve send a mail to the dailymile guys asking about this and if they can update their examples/docs for this case. In the mean time I’m going to have a look at using the web based authorization approach with a custom schema for the callback URL which I should be able to register with a suitable Intent filter.

 <activity android:name=".AuthCallBackAct" android:label="@string/app_name"
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.BROWSABLE"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <data android:scheme="dm" android:host="localhost"/>

Unbricking Guruplug Server

I’ve been playing with a Guruplug Server over the last few days trying to get it to work with my MIMO UM-740 Touch Screen. It’s been slow progress because nobody seams to have a link to the src for the kernel that it comes with so I can’t build the DisplayLink module.

Used with permission from Flickr taken by andypiper

To try and get round this I tried to update the kernel using some instructions on the website. Unfortunately due to a miss match in the uboot boot loader that came installed on the device the new kernel wouldn’t boot.

I tried following the un-bricking instructions on the wiki and ran into the problem that not only is the src not available for the original but neither is the binary. I tried to grab a copy from a colleagues machine but it didn’t want to read the area of flash properly once booted.

I finally found a nice little post on the New IT forum that had all the bits needed to update uboot, the kernel and a clean root file system using a USB stick.

The updated version of uboot should also allow the pre built kernels out there to boot now as well.

So while I’m not back to original state of the as the Guruplug shipped, it is now up are running again and I can get back to getting my MIMO working with it.

Linux Photo-me-booth

I’ve spent the last few days on site at a bespoke coach builder’s helping to fit out a truck, this all came about after a chat over lunch at the end of last year. Kevin mentioned a project he was involved in to put some our lab demo’s into a truck that will be touring round a number of universities as part of the Smarter Planet initiative.

Side 1

As well as building portable versions of some demo’s there was a new bit. The plan was to have a sort of photo booth to take pictures of the visiting students and build a custom avatar for them to use at each station and upload to sites like Flickr and Facebook.

Since we were already doing some work for the truck we said we would have a look at doing this bit as well, about half an hour of playing that afternoon it looked like we should be able to put something together using a commodity webcam, Linux and some existing libraries.

First Pass

The first approach was to see if we could do what we needed in a reasonably simple web page. Using a streaming video server, the HTML 5 <video> tag and a bit of javascript we had a working prototype up and running very quickly.

The only problem was the lag introduced by the video encoding and the browser buffering, most of the time it was about 12 seconds, with a bit of tinkering we got it down to 5 seconds, but this was still far too long to ask somebody to pose for in order to just grab a single image.

Second Pass

So after getting so close with the last attempt I decided to have a look at a native solution that should remove the lag. I had a bit of a look round to see what options where available and I came across the following:

  • Video4Linux
    This is gives direct access to the video hardware connected to Linux
  • GStreamer
    This is a framework that allows you to build flows for interacting with media sources. This can be audio or video and from files as well as hardware devices.

As powerful as the Video4Linux API is it’s seamed a bit too heavy weight for what I was looking for. While looking into the GStreamer code I found it had a pre-built package that would do pretty much exactly what I wanted called CameraBin.

With a little bit of python it is possible to use the CameraBin module to show a Viewfinder and then write an image on request.

	self.camerabin = gst.element_factory_make("camerabin", "cam")
	self.sink = gst.element_factory_make("xvimagesink", "sink")
	src = gst.element_factory_make("v4l2src","src")
	self.camerabin.set_property("viewfinder-sink", self.sink)
	self.camerabin.set_property("video-source", src)
	self.camerabin.set_property("flicker-mode", 1)

Where self.sink is a Glade drawing area to use as a view finder and self.image_captured is a call back to execute when the image has been captured. To set the filename to save the image to and start the viewfinder run the following code.

	self.camerabin.set_property("filename", "foo.jpg")

To take a photo call the self.camerabin.emit(“capture-start”) method

The plan was for the avatar to be a silhouette on a supplied background, to make generating the silhouette easier the students will be standing in front of a green screen

Green screen

The Python Imaging Library makes manipulate the captured image and extract the silhouette and then build up the final image from the background, the silhouette and finally the text.

	image =
	image2 = image.crop((150,80,460,450))
	image3 = image2.convert("RGBA")
	pixels = image3.load()
	size = image2.size;
	for y in range(size[1]):
		for x in range(size[0]):
			pixel = pixels[x,y]
			if (pixel[1] > 135 and pixel[0] < 142 and pixel[2] < 152):
				pixels[x,y] = (0, 255, 0, 0)
				pixels[x,y] = (0, 0, 0, 255)

	image4 = image3.filter(ImageFilter.ModeFilter(7))
	image5 = image4.resize((465,555))
	background ='facebook-background.jpg')
	text ='facebook-text.png')

The final result shown on one of the plasma screens in the truck.

Silhouette wall

As well as building Silhouette wall, ETS has provided a couple of other items to go on the truck

  1. See It Sign It

    This application is a text to sign language translation engine that uses 3D avatars to sign. There will be 2 laptops on the truck that can be used to have a signing conversation. There is a short video demonstration of the system hooked up to a voice to text system here:

  2. Smarter Office

    This is an evolution of the Smarter Home section in the ETS Demo lab at Hursley. This uses a Current Cost power meter to monitor the energy used and feeds this to a Ambient Orb to visualise the information better. It also has a watch that can recognise different gestures which in turn can be used to turn things like the lamp and desk fan on and off and the amount of power used by these is reflected in the change in colour from the orb.

For details of where the truck will be visiting over the year, please visit the tours facebook page in the resources.