Smartphones

A bare-bones live wallpaper template for Android

William Francis demystifies the process of creating live wallpapers for Android and shares a template you can use for your projects.
A TechRepublic reader suggested I do a tutorial on creating a live wallpaper. Live wallpaper was a feature added in Android 2.1 that allows the actual background or desktop of the phone to act much like an application. It can animate, make system calls, and interact with the user. A good example of the feature in action is the default Nexus wallpaper that started shipping with the Nexus One. Purporting to be a "peek inside a neural network," the Nexus live wallpaper (Figure A) animates a series of colorful traces, which change in response to the user touching the display of the phone. Figure A

Nexus live wallpaper

It's up to the developer's imagination to determine what a given live wallpaper does. The Android documentation for live wallpapers is thorough, and even includes a demonstration. However, because it is considerably different architecturally from a standard Android application, the Google provided sample code, as well as most of the tutorials provided on the web, are often a bit intimidating at first glance.

My goal in writing this tutorial is not to make a stellar live wallpaper, but rather to strip the required code to the bare minimum, thereby demystifying the process and hopefully jumpstarting a number of beginning Android programmers who maybe shied away from coding a live wallpaper in the past. Consider what we will be creating in this post and finishing in next week's post less of a completed application and more of a template for your own live wallpaper projects.

You can download the source code that goes along with this tutorial here. Pay special attention to the project setup requirements since they deviate from the norm in several places. Ready to get started?

1. In Eclipse create a new Android application. Be sure to target Android 2.1 or higher. Do not allow the project wizard to auto create an activity for this project.

2. In the /res directory of your new project, create an XML folder. In the folder add a new file called wallpaper.xml.

wallpaper.xml

<?xml version="1.0" encoding="UTF-8"?>

<wallpaper

xmlns:android=http://schemas.android.com/apk/res/android

android:thumbnail="@drawable/icon"

android:description="@string/app_name"/>

3. Properly configure your AndroidManifest.xml file. The manifest for a live wallpaper is different from most standard applications we create in this blog, and even different from a standard service (the base class from which live wallpapers derive). There are a number of options available for live wallpapers, but for now I've tried to whittle the contents down to only the essentials.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.authorwjf.live_wallpaper_p1" android:versionCode="1"

android:versionName="1.0">
<application android:label="TR Demo Live Wallpaper"
 android:icon="@drawable/icon">
<service android:label="TR Demo Live Wallpaper"
android:name=".DemoWallpaperService"

 android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data android:name="android.service.wallpaper"

 android:resource="@xml/wallpaper" />
</service>
</application>
<uses-sdk android:minSdkVersion="7" />

 <uses-feature android:name="android.software.live_wallpaper" />
</manifest>

4. With the plumbing in place now we can implement our /src/DemoWallpaperService.java file. We start by creating the outer class, which extends the Android WallpaperService class.

DemoWallpaperService.java
package com.authorwjf.live_wallpaper_p1;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;
public class DemoWallpaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new DemoWallpaperEngine();
}
}

5. We want to add an inner class that generates our derived wallpaper engine. Because the service will be constantly running, it's very important that we properly handle life cycle events.

DemoWallpaperService.java
private class DemoWallpaperEngine extends Engine {
 private boolean mVisible = false;
private final Handler mHandler = new Handler();
private final Runnable mUpdateDisplay = new Runnable() {

@Override

public void run() {

draw();

}};
@Override
public void onVisibilityChanged(boolean visible) {

mVisible = visible;

if (visible) {

draw();

} else {

mHandler.removeCallbacks(mUpdateDisplay);

}

}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {

draw();

}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
mVisible = false;

mHandler.removeCallbacks(mUpdateDisplay);

}
@Override
public void onDestroy() {
super.onDestroy();
mVisible = false;

mHandler.removeCallbacks(mUpdateDisplay);

}

}

6. Up to this point, the code we've implemented is generic to all live wallpapers; now inside the draw() method we will add the code specific to our live wallpaper. We are not going to do anything terribly creative or efficient. Basically every 100ms, we clear the entire screen, and then use the canvas object to paint the system millisecond timer onto the desktop.

DemoWallpaperService.java
privatevoid draw() {
SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
Paint p = new Paint();
p.setTextSize(20);
p.setAntiAlias(true);
String text = "system time: "+Long.toString(System.currentTimeMillis());
float w = p.measureText(text, 0, text.length());
int offset = (int) w / 2;
int x = c.getWidth()/2 - offset;
int y = c.getHeight()/2;
p.setColor(Color.BLACK);
c.drawRect(0, 0, c.getWidth(), c.getHeight(), p);
p.setColor(Color.WHITE);
c.drawText(text, x, y, p);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}

mHandler.removeCallbacks(mUpdateDisplay);

if (mVisible) {

mHandler.postDelayed(mUpdateDisplay, 100);

}

}

}

7. Your final version of DemoWallpaperService.java should look like the listing below -- at least until next week.

DemoWallpaperService.java
package com.authorwjf.live_wallpaper_p1;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;
public class DemoWallpaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new DemoWallpaperEngine();
}
private class DemoWallpaperEngine extends Engine {
private boolean mVisible = false;
private final Handler mHandler = new Handler();
private final Runnable mUpdateDisplay = new Runnable() {
@Override
public void run() {
draw();
}};
private void draw() {
SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
Paint p = new Paint();
p.setTextSize(20);
p.setAntiAlias(true);
String text = "system time: "+Long.toString(System.currentTimeMillis());
float w = p.measureText(text, 0, text.length());
int offset = (int) w / 2;
int x = c.getWidth()/2 - offset;
int y = c.getHeight()/2;
p.setColor(Color.BLACK);
c.drawRect(0, 0, c.getWidth(), c.getHeight(), p);
p.setColor(Color.WHITE);
c.drawText(text, x, y, p);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(mUpdateDisplay);
if (mVisible) {
mHandler.postDelayed(mUpdateDisplay, 100);
}
}
@Override
public void onVisibilityChanged(boolean visible) {
mVisible = visible;
if (visible) {
draw();
} else {
mHandler.removeCallbacks(mUpdateDisplay);
}
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
draw();
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
mVisible = false;
mHandler.removeCallbacks(mUpdateDisplay);
}
@Override
public void onDestroy() {
super.onDestroy();
mVisible = false;
mHandler.removeCallbacks(mUpdateDisplay);
}
}
}
8. Now we are ready to try it out. Install the service onto your emulator using the Run menu in the Eclipse IDE. Wait. What's that you said? Nothing happened? That's because we still need to activate our new wallpaper. After the download to the emulator is complete, press the Menu key from the home screen (Figure B). Figure B

9. Select Live Wallpapers from the secondary menu (Figure C). Figure C

10. Choose the TR Demo wallpaper from the installed live wallpapers (Figure D). Figure D

11. And last but not least set the wallpaper (Figure E). Figure E

There you have it! A live, though not particularly interesting, wallpaper. In part two of this tutorial we will work at sprucing our wallpaper service up a bit via the draw() method, as well as add a configuration file. Also I will share some tips on debugging live wallpapers, which if it had not occurred to you before now, is not always the easiest task.

About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

1 comments
mikpel
mikpel

Nice and clear example of basic work flow with live wallpapers. This should get the ball rolling.

Editor's Picks