Augmented Reality with HTML5
Before diving into the Dalvik part of the application, let's take a look at the HTML5 part, which draws a compass card and rotates the card to show the current direction the phone is pointed. The .html, .js and .png files used here are stored in the Dalvik application's assets folder, which is created automatically when Eclipse creates an Android project.
Listing 1. arcompass.html
<!DOCTYPE HTML PUBLIC>
<head>
<title>AR Compass</title>
<script type="text/javascript"
src="arcompass.js">
</script>
</head>
<body>
<div id="extra">
<button type="button"
onclick="window.direction.turnOnCompass()">
Start</button>
</div>
<div id="overlay" style="position: absolute;
left:280; top:60; z-index:500;
background-color:#0000" >
<canvas id="e" width="200" height="200">
</canvas>
<script>
drawCompass();
</script>
</div>
</body>
</html>
Listing 2. arcompass.js
var currDir;
var canvas;
var context;
var card;
function drawCompass() {
currDir = 0;
canvas = document.getElementById("e");
context = canvas.getContext("2d");
card = new Image();
card.src = "CompassCard.png";
card.onload = function() {
context.translate(100,100);
context.globalAlpha = 0.5;
context.drawImage(card, -100, -100, 200, 200);
}
}
function updateView(dir) {
context.rotate(currDir*2*Math.PI/360);
context.rotate(-dir*2*Math.PI/360);
context.drawImage(card, -100,-100,200,200);
currDir = dir;
}
The header of the HTML file declares a title and references the JavaScript file. The body consists of two <div>s: one with a button and one with a <canvas>. You don't really need the button for the application, but I wanted to show how you call Dalvik routines from JavaScript/HTML. Notice that the onclick attribute for the button is set to window.direction.turnOnCompass(). You'll see later how that API is declared in Dalvik and how it is wired to start the compass sensor sending direction updates.
The second <div> is the canvas where you draw the compass card. Let's assume a landscape orientation for the application and position the canvas on the right side of the screen. In a real application, you'd take account of the specific screen geometry of the device you're running on. For simplicity here, I've hard-coded some pixel values. A short embedded script then asks the drawCompass() function to draw the initial compass card image.
The JavaScript file declares some variables and defines two functions:
drawCompass() draws the initial compass card, with north pointing up.
updateView(dir) will be called whenever you get an updated compass direction from the compass sensor (I explain how later). It rotates the drawing context appropriately and redraws the compass card.
Let's turn our attention to the Dalvik part of the application. You need manifest and layout files (Listings 3 and 4).
Listing 3. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package="com.lj.ARCompass"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name"
android:debuggable="true">
<activity android:name=".ARCompass"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission
android:name="android.permission.CAMERA">
</uses-permission>
<uses-permission
android:name="android.permission.INTERNET">
</uses-permission>
<uses-permission
android:name="android.permission.SET_DEBUG_APP">
</uses-permission>
</manifest>
Listing 4. main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" > <SurfaceView android:id="@+id/preview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" /> <WebView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/webView0" android:layout_alignTop="@id/preview" android:layout_alignBottom="@id/preview" /> </RelativeLayout>
The manifest says the application consists of only one screen (the ARCompass activity) and that it needs the user's permission to access the camera and Internet. It also asked for SET_DEBUG_AP permission, which allows you to run the app on a real device while using the Eclipse debugger.
The layout file says the activity contains two views, a WebView cleverly named webView0 and a SurfaceView named preview. I'm using a Relative Layout so you can position the views on top of each other using the layout_align_top and layout_align_bottom attributes for webView0. I'll handle any other needed layout in the HTML that I'll ask WebView to render.
The Dalvik part of the application is more complicated, but not so bad if you break it down into sections:
package com.lj.ARCompass;
import java.io.IOException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
public class ARCompass extends Activity
implements SurfaceHolder.Callback {
private WebView mWebView;
private SensorManager mSensorManager;
private float[] mValues;
private boolean compassOn = false;
private static final String TAG = "ARCompass";
final Context mContext = this;
private Camera mCamera;
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private boolean mPreviewRunning;
These first lines import all the libraries you need, declare some needed variables, and declare the only Activity, ARCompass. Note that I've said ARCompass will implement the SurfaceHolder.Callback interface—this is needed for the camera preview.
The next block of code declares a SensorEventListener:
private final SensorEventListener mListener =
new SensorEventListener() {
@Override
public void onAccuracyChanged
(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
mValues = event.values;
Log.d(TAG,"Compass update: " + mValues[0]);
String url =
"javascript:updateView(" + mValues[0] + ");";
mWebView.loadUrl(url);
}
};
Later on, I'm going to wire this listener up to the update events that I'll get from the compass sensor. For now, notice in the onSensorChanged() method that the ultimate result is to load a URL into the WebView (also created later). The URL is of the form javascript:updateView(direction), because the first value passed to you in the array event.values[] is, in fact, the current compass direction. Loading the URL into the WebView has the effect of calling the updateView() function just defined in arcompass.js.
The next section of code gets into the onCreate() method, called when the activity is first created:
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
// Get rid of title
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
// Initialize the surface for camera preview
mSurfaceView =
(SurfaceView)findViewById(R.id.preview);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType
(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
Log.d(TAG, "SurfaceView initialized");
// Initialize the WebView
mWebView = (WebView) findViewById(R.id.webView0);
WebSettings webSettings = mWebView.getSettings();
webSettings.setSavePassword(false);
webSettings.setSaveFormData(false);
webSettings.setJavaScriptEnabled(true);
webSettings.setSupportZoom(false);
mWebView.setBackgroundColor(0);
mWebView.addJavascriptInterface
(new CompassJavaScriptInterface(), "direction");
Log.d(TAG, "JavaScript interface added");
/* Set WebChromeClient before calling loadUrl! */
mWebView.setWebChromeClient
(new WebChromeClient() {
@Override
public boolean onJsAlert(
WebView view, String url, String message,
final android.webkit.JsResult result){
new AlertDialog.Builder(mContext)
.setTitle("javaScript dialog")
.setMessage(message)
.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(
DialogInterface dialog, int which) {
result.confirm();
}
})
.setCancelable(false)
.create()
.show();
return true;
};
});
mWebView.loadUrl(
"file:///android_asset/arcompass.html");
}
After calling the superclass routine and setting a TAG to be used with log messages, I request the FEATURE_NO_TITLE for the window, because I don't want or need the usual Android title bar. Then, I connect with the main.xml layout file I looked at earlier.
The next block of code initializes the SurfaceView that you're going to use for the camera preview, and the next block of code initializes the WebView. I'll leave most of the details to the reader (the Android SDK help files are excellent), but note one line in particular:
webSettings.setJavaScriptEnabled(true);
By default, WebViews don't execute JavaScript. This setting turns on that ability.
The line after the WebView settings invokes addJavascriptInterface() to add a new API that can be called from scripts run by the WebView. I define the CompassJavaScriptInterface class later, including the method turnOnCompass(), but this is where I defined the “direction” part of the function call I made back in arcompass.html (window.direction.turnOnCompass()).
The next 20 lines or so define a WebChromClient, so you can issue alert() function calls from JavaScript, and those will be converted into Android alert boxes. This is useful for debugging, but not absolutely needed unless your JavaScript uses alerts.
The last line in this section loads the arcompass.html file into the WebView. Note the syntax of the file reference. Again, the file is in the assets folder of the application project, and the SDK includes that folder in the .apk package that is downloaded when installing the application. The next section of code connects the compass sensor to the application:
final class CompassJavaScriptInterface {
/* Note this runs in a separate thread */
CompassJavaScriptInterface() {
}
public void turnOnCompass() {
Log.d(TAG, "turnOnCompass");
mSensorManager = (SensorManager)
getSystemService(Context.SENSOR_SERVICE);
Sensor mSensor =
mSensorManager.getDefaultSensor
(Sensor.TYPE_ORIENTATION);
if(mSensor != null){
mSensorManager.registerListener(mListener,
mSensor, SensorManager.SENSOR_DELAY_NORMAL);
compassOn = true;
Log.d(TAG, "Compass started");
}
else{
Toast.makeText(mContext,
"No ORIENTATION Sensor",
Toast.LENGTH_LONG).show();
compassOn = false;
finish();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(compassOn){
mSensorManager.unregisterListener(mListener);
}
finish();
}
First, I declare the class that I referred to back in addJavascriptInterface(). When you make a call this way from JavaScript, it can be important to know that this code is going to run in a thread separate from the one where it was invoked above. In particular, if the called routine needs to manipulate the user interface, it will not be running in the UI thread, so it needs to post a runnable for that thread to pick up. In this case, I'm just working with the Sensor interface, so running in a separate thread is not an issue.
The only method I define is turnOnCompass(), but I could define others. If I defined another method blatz(), I could call it from JavaScript as window.direction.blatz(). The turnOnCompass() method invokes the SensorManager and asks for a handle to the default orientation sensor. If there is a default orientation sensor, it registers the SensorEventListener I defined at the beginning, sets a housekeeping boolean and returns. If there isn't an orientation sensor, it tells the user with a Toast, and exits.
The final block of code in this section makes sure that you de-register the listener when the application exits. If you happen to be the only registered listener for orientation, this would give Android the opportunity to power down that service, and even that sensor.
The last section of Dalvik code deals with the camera preview:
// Create camera preview.
public void surfaceCreated(SurfaceHolder holder){
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException exception) {
mCamera.release();
mCamera = null;
}
}
// Change preview's properties
public void surfaceChanged(SurfaceHolder holder,
int format, int w, int h){
mCamera.startPreview();
mPreviewRunning = true;
}
// Stop the preview.
public void surfaceDestroyed
(SurfaceHolder holder){
mCamera.stopPreview();
mPreviewRunning = false;
mCamera.release();
}
}
Again, the details of the camera preview implementation are best left to the Android SDK documentation. The three methods—surfaceCreated(), surfaceChanged() and surfaceDestroyed()—are the methods of the Surface.Callback interface that I said this activity would implement, and they are called for you at each of those events. When the surface is created, you connect the camera preview to the SurfaceHolder. When the surface is destroyed, you stop the camera preview and release the camera. The surfaceChanged method is called only once, after surfaceCreated, and you actually start the preview there.
When you build and run this program on a mobile phone, you get something like the picture shown in Figure 2. This is an HTC EVO screenshot. I haven't done a lot to account for screen geometry differences, so your phone may look a bit different.
The user sees a Start button and a compass card superimposed on the current camera preview. The Start button isn't really needed, but I included it so I could show how JavaScript/HTML can call a Dalvik method.
When you tap the Start button, the HTML part of the application calls window.direction.turnOnCompass(), which is implemented in Dalvik. The method asks the orientation sensor to start sending compass readings to mListener. Every time mListener gets a new compass reading, it calls the JavaScript routine updateView() to repaint the compass card on the screen.
Rick Rogers has been a professional embedded developer for more than 30 years. Now specializing in mobile application software, when Rick isn't writing software for a living, he's writing books and magazine articles like this one.
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Designing Electronics with Linux | May 22, 2013 |
| Dynamic DNS—an Object Lesson in Problem Solving | May 21, 2013 |
| Using Salt Stack and Vagrant for Drupal Development | May 20, 2013 |
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
- RSS Feeds
- Dynamic DNS—an Object Lesson in Problem Solving
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- Using Salt Stack and Vagrant for Drupal Development
- New Products
- A Topic for Discussion - Open Source Feature-Richness?
- Drupal Is a Framework: Why Everyone Needs to Understand This
- Validate an E-Mail Address with PHP, the Right Way
- What's the tweeting protocol?
- Designing Electronics with Linux
- Kernel Problem
1 hour 53 min ago - BASH script to log IPs on public web server
6 hours 20 min ago - DynDNS
9 hours 56 min ago - Reply to comment | Linux Journal
10 hours 28 min ago - All the articles you talked
12 hours 52 min ago - All the articles you talked
12 hours 55 min ago - All the articles you talked
12 hours 56 min ago - myip
17 hours 21 min ago - Keeping track of IP address
19 hours 12 min ago - Roll your own dynamic dns
1 day 26 min ago
Enter to Win an Adafruit Pi Cobbler Breakout Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Pi Cobbler Breakout Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- 5-21-13, Prototyping Pi Plate Kit: Philip Kirby
- Next winner announced on 5-27-13!
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?





Comments
Missed the latest developments?
It's a shame that you seem to have missed this:
http://my.opera.com/core/blog/2011/03/23/webcam-orientation-preview
HTML5 is awesome!
Absolutely awesome what HTML5 makes possible. As webworker I'm happy about the features that will follow in next years! Nice posting, I will twitter the post here.