Skip to content
Mar 23 10

TapeMeasure – Another Android experiment

I have again become interested in what you can do with Android applications. After some discussion with a housemate (who actually owns an Android, unlike me) about augmented reality applications and how they could be easily done (to some degree) if you could reasonably accurately track a phones location in a building (something that is hard to do with GPS).  I wanted to see if the phones accelerometer could be used (at least for short distances) to work out how a phone had moved since entering a building. Rather than attempt to make an augmented reality app, I decided to go for a slightly easier project of making a tape measure, i.e. an app that could measure distance by moving the phone. Before anyone thinks this will actually be useful I should point out that this was a rather complete failure and the resulting app is fairly useless. However, the code may be useful to anyone looking at writing an Android app that uses any of the internal sensors, it would also be interesting to see if any other phones (I only tested the app on a G1) are any more successful.

So straight to the code, after (briefly) reading the Android documentation it seemed there was very little info about using the sensors, so I turned to Google, this article looked like what I wanted and linked to this code which is the basis of my app. So I updated the code to use the non-deprecated API, changing its functionality slightly and ended up with:

package uk.co.connorhd.android.tapemeasure;
 
import android.app.Activity;
import android.os.Bundle;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
public class TapeMeasure extends Activity implements SensorEventListener,
		OnClickListener {
	private SensorManager sensorMgr;
	private Sensor sensorAccel;
	private TextView accuracyLabel;
	private TextView xLabel, yLabel, zLabel;
	private Button calibrateButton;
 
	private float moved = 0;
	private float speed = 0;
	private float accel = 0;
	private float accelDiff = 0;
 
	private float[] a;
 
	private long lastUpdate = 0;
	private float curAccel = 0;
	private int accelUpdates = 0;
 
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		accuracyLabel = (TextView) findViewById(R.id.accuracy_label);
		xLabel = (TextView) findViewById(R.id.x_label);
		yLabel = (TextView) findViewById(R.id.y_label);
		zLabel = (TextView) findViewById(R.id.z_label);
		calibrateButton = (Button) findViewById(R.id.calibrate_button);
		calibrateButton.setOnClickListener(this);
	}
 
	@Override
	protected void onPause() {
		super.onPause();
		sensorMgr.unregisterListener(this, sensorAccel);
		sensorMgr = null;
	}
 
	@Override
	protected void onResume() {
		super.onResume();
 
		sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);
		sensorAccel = sensorMgr.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
		boolean accelSupported = sensorMgr.registerListener(this, sensorAccel,
				SensorManager.SENSOR_DELAY_FASTEST);
 
		if (!accelSupported) {
			// on accelerometer on this device
			sensorMgr.unregisterListener(this, sensorAccel);
			accuracyLabel.setText(R.string.no_accelerometer);
		}
	}
 
	public void onClick(View v) {
		if (v == calibrateButton) {
			// Clicked button
			moved = 0;
			speed = 0;
			accelDiff = accel+accelDiff;
			accel = 0;
 
		}
	}
 
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// this method is called very rarely, so we don't have to
		// limit our updates as we do in onSensorChanged(...)
		if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
			switch (accuracy) {
			case SensorManager.SENSOR_STATUS_UNRELIABLE:
				accuracyLabel.setText(R.string.accuracy_unreliable);
				break;
			case SensorManager.SENSOR_STATUS_ACCURACY_LOW:
				accuracyLabel.setText(R.string.accuracy_low);
				break;
			case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:
				accuracyLabel.setText(R.string.accuracy_medium);
				break;
			case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:
				accuracyLabel.setText(R.string.accuracy_high);
				break;
			}
		}
	}
 
	public void onSensorChanged(SensorEvent event) {
		int sensorType = event.sensor.getType();
		if (sensorType == Sensor.TYPE_ACCELEROMETER) {
			a = event.values;
			accel = (float) (Math.sqrt((a[0]*a[0]+a[1]*a[1]+a[2]*a[2]))-SensorManager.GRAVITY_EARTH)-accelDiff;
			curAccel += accel;
			accelUpdates++;
 
			long curTime = event.timestamp - lastUpdate;
			if (curTime > 1000000000) {
				curAccel = curAccel/accelUpdates;
				speed += curAccel*curTime*10e-10;
				moved += speed*curTime*10e-10;
 
				lastUpdate = event.timestamp;
 
				xLabel.setText(String.format("Moved: %+2.20f", moved));
				yLabel.setText(String.format("Speed: %+2.20f", speed));
				zLabel.setText(String.format("Accel: %+2.20f Time:  %+2.3f", curAccel, curTime*10e-10));
				curAccel = 0;
				accelUpdates = 0;
			}
		}
	}
}

This calculates the current acceleration of the phone, subtracts gravity (which is added to the raw data), then derives the speed and distance travelled. The calibrate button resets the speed and distance, and assumes the phone is not moving, generating a bias to add to the acceleration in the case of the phones accelerometer being badly calibrated. If you want to try the app it can be downloaded and installed from here. I suggest launching the app, placing the phone on a stable surface and pressing calibrate, if you get any interesting results let me know.

Despite the phone reporting the accelerometer accuracy as “high” (with no description of what high means) and returning values in excess of 10 significant figures,  the acceleration data with the phone sat on a table varied in the range of about 0.5m/s^2 (although this did appear to change depending on the room I was in). This resulted in a massively wrong distance value very quickly increasing to several metres without moving the phone, making any kind of testing almost impossible. It seems the current purpose of accelerometers is to work out if some violent action (such as shaking) has happened to the phone, and not anything much more interesting. All in all a bit of a waste of time, but maybe future phones will have better sensors, and hopefully someone will find the code snippet above useful.

Feb 25 10

How To Node

At the risk of being boring, another node related link. A collection of articles written about node (mainly as tutorials) by the node community.

Feb 12 10

Try Ruby!

A site I came across some time ago, Try Ruby gives you an interactive (AJAX) ruby shell, with a simple tutorial to follow. In my opinion an awesome way to get started with a new language, I find learning is much easier by trying things out.

If you like this then you may also be interested in Try MongoDB (a similar MongoDB tutorial) and also codepad (a pastebin that allows you to execute certain programming languages and view the result).

Feb 4 10
Tags

,

dafont.com

Not that interesting, but an awesome site for finding fonts. Currently has over 10,000 free fonts, including the one used for the slogan on this site!

Feb 3 10

WebApp – An Android experiment

After thinking about a JavaScript API to allow alerts on an Android phone in this post, I came up with a method of adding this using the current Java Android app API. The solution involves starting a Webkit instance in a custom app, and intercepting links with a certain prefix. In this example android://alert/description creates an alert with title “alert” and description “description”.

To demonstrate here are some screenshots of a sample page using a sample application (if you have an Android phone you can use that link to download it and try it yourself):

The application when you load it:

When clicking go an alert is created:

Viewing the created alert:

The example page is very simple javascript:

function go() {
	window.location = 'android://'+document.getElementById('title').value
		+'/'+document.getElementById('text').value;
}

The source for the app follows and is also fairly simple, most of the code is the rather complex way alerts are generated on Android:

package uk.co.connorhd.android.webapp;
 
import java.net.URLDecoder;
 
import uk.co.connorhd.android.webapp.R;
import uk.co.connorhd.android.webapp.WebApp;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
 
public class WebApp extends Activity {
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    WebView webview = new WebView(this);
 
    // We're testing, clear the cache.
    webview.clearCache(true);
 
    setContentView(webview);
 
    webview.getSettings().setJavaScriptEnabled(true);
 
    final Activity activity = this;
    webview.setWebViewClient(new WebViewClient() {
      int alert = 1;
 
      public void onReceivedError(WebView view, int errorCode,
          String description, String failingUrl) {
        Toast.makeText(activity, "Oh no! " + description,
            Toast.LENGTH_SHORT).show();
      }
 
      public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // Is it a hack?
        if (url.startsWith("android")) {
          String ns = Context.NOTIFICATION_SERVICE;
          NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
          int icon = R.drawable.icon;
          CharSequence tickerText = "WebApp Alert!";
          long when = System.currentTimeMillis();
 
          Notification notification = new Notification(icon,
              tickerText, when);
          notification.flags = Notification.FLAG_AUTO_CANCEL;
          Context context = getApplicationContext();
 
          String[] split = url.split("/");
 
          CharSequence contentTitle = URLDecoder.decode(split[2]);
          CharSequence contentText = URLDecoder.decode(split[3]);
 
          Intent notificationIntent = new Intent(activity,
              WebApp.class);
          PendingIntent contentIntent = PendingIntent.getActivity(
              activity, 0, notificationIntent, 0);
 
          notification.setLatestEventInfo(context, contentTitle,
              contentText, contentIntent);
 
          mNotificationManager.notify(alert++, notification);
        } else {
          view.loadUrl(url);
        }
        return true;
      }
    });
 
    webview.loadUrl("http://connorhd.co.uk/files/WebApp.htm");
  }
}

This could easily be extended (and I may try to do this myself for Ircster) to provide all the standard browser functionality (for example the back button doesn’t currently work) and a more complete API to interface with Android.