Processing for Android

Wallpapers

Use the Android Mode to develop interactive live wallpapers.

(This tutorial is applicable only to version 4+ of the mode)

What is a live wallpaper?

Live wallpapers are a special type of applications that generate animated and interactive backgrounds in the home screen of the device. Technically, they are an Android Service, designed to run for a long time in the background. With the Android mode in Processing, you don't need to worry about these low-level details, and instead focus on the drawing code. Virtually any 2D or 3D sketch can be built as a live wallpaper. All you need to do is to select the Wallpaper option under the Android menu:

Wallpaper selection

Creating a wallpaper

Let's start by writing a simple wallpaper that just transitions the background between randomly selected colors. We will use the HSB color mode to create smooth transitions across the hue and the saturation of consecutive colors. We can test this sketch using Java mode first, to figure out the correct timing of the transitions. In our first version, we simply change the hue and saturation every 5 seconds without any transition:


float currH, currB;
int lastChange = 0;

void setup() {
  size(400, 400);
  colorMode(HSB, 100);
  currH = 100;
  currB = 100;
}

void draw() {
  background(currH, currB, 100);
  if (5000 < millis() - lastChange) { 
    pickNextColor();
    lastChange = millis();
  }
}

void pickNextColor() {
  currH = random(100);
  currB = random(100);
}

We can now add easing to smoothly transition between the colors:


float currH, currB;
float nextH, nextB;
float easing = 0.001;
int lastChange = 0;

void setup() {
  size(400, 400);
  colorMode(HSB, 100);
  currH = nextH = 100;
  currB = nextH = 100;
}

void draw() {
  background(currH, currB, 100);
  updateCurrColor();
  if (5000 < millis() - lastChange) { 
    pickNextColor();
    lastChange = millis();
  }
}

void pickNextColor() {
  nextH = random(100);
  nextB = random(100);
}

void updateCurrColor() {
  // Easing between current and next colors
  currH += easing * (nextH - currH);
  currB += easing * (nextB - currB);
}

Once you are satisfied with the transition and easing times, you need to replace size(400, 400) with fullScreen() before running the sketch on the device to make sure that the wallpaper uses the entire screen of the device.

After the wallpaper sketch has been installed on the device, it won't show up right away. You need to open the wallpaper selector, and scroll through the availalable wallpapers until you find yours. The wallpaper selector will look different depending on the Android version in your deveice and what other wallpapers are installed, on Android 5.0 and newer it should look something like this:

Wallpaper selector

Using sensor data in the wallpaper

In the sensors tutorial we saw how to use the sensor API in Android to read acceleration values. We can use the same technique to obtain sensor information needed to construct a compass. In this case, we need both accelerometer and geomagnetic sensor data, in order to determine the device's orientation with respect to the Earth's magnetic axis. We start with the following template:


import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;

Context context;
SensorManager manager;
SensorListener listener;
Sensor accelerometer;
Sensor magnetometer;

void setup() {
  fullScreen(P2D);
  orientation(PORTRAIT);
}

void draw() {
  background(255);
}

public void onResume() {
  super.onResume();  

  context = (Context) surface.getComponent();
  
  listener = new SensorListener();
  manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
  accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  magnetometer  = manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

  manager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
  manager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_NORMAL);
}

public void onPause() {
  super.onPause();
  if (manager != null) manager.unregisterListener(listener);
}

class SensorListener implements SensorEventListener {
  float[] gravity = new float[3];
  float[] geomagnetic = new float[3];
  float[] I = new float[16];
  float[] R = new float[16];
  float orientation[] = new float[3]; 
  
  public void onSensorChanged(SensorEvent event) {
    if (event.accuracy == SensorManager.SENSOR_STATUS_ACCURACY_LOW) return;
    
    if (event.sensor.getType() ==  Sensor.TYPE_MAGNETIC_FIELD) {
      arrayCopy(event.values, geomagnetic);
    }
    if (event.sensor.getType() ==  Sensor.TYPE_ACCELEROMETER) {
      arrayCopy(event.values, gravity);
    }
  }
  public void onAccuracyChanged(Sensor sensor, int accuracy) { }
}              

A couple of important things to note here: