The Android mini-app frameworkMenusUsing the phone sensors

Using the phone sensors

Mobile phones incorporate a number of sensors that we can use to build more interesting mini-apps.

Light sensor

The easiest sensor to use is the ambient light sensor. It returns a number indicating the ambient lighting situation (your phone typically uses this to adjust the screen brightness).

Here is a small program that registers a handler for the light sensor (light.kt):

//
// Light sensor test
//

import org.otfried.cs109.Context
import org.otfried.cs109.MiniApp

import org.otfried.cs109.Canvas
import org.otfried.cs109.Color
import org.otfried.cs109.DrawStyle
import org.otfried.cs109.TextAlign

class Main(val ctx: Context) : MiniApp {
  var light = 0.0

  init {
    ctx.setTitle("Light sensor demo")
    ctx.onLight { updateLight(it) }
  }

  fun updateLight(lx: Double) {
    if (lx != light) {
      light = lx
      ctx.update()
    }
  }

  override fun onDraw(canvas: Canvas) {
    val x = canvas.width / 2.0
    canvas.clear(Color(255, 255, 192))
    canvas.setColor(Color.BLUE)
    canvas.setFont(48.0)
    canvas.drawText("$light", x, 80.0, TextAlign.CENTER)
  }
}

The onLight method registers a function object, which will be called every time the light value has changed (in fact, it can be called more often, even when it did not change). In the demo program we call update to update the screen.

Note that the emulator has no access to a real light sensor, so instead it will pop up a second window where you can use a slider to adjust the setting of the light sensor that will be sent to your mini-app.

Some smartphones do not provide the light sensor output to apps, even if they use it for adjusting the screen brightness. If you run the demo program on your phone and it only displays 0.0, then mini-apps cannot use your phone's light sensor.

Accelerometer

A more exciting sensor is the accelerometer, which measures the acceleration acting on your phone. When the phone is in rest, the only acceleration it experiences is the force of gravity, and so we can use the accelerometer to measure the direction of gravity (its strength, of course, will remain unchanged as long as you don't travel into space with your phone).

The onGravity method registers a handler for the accelerometer. It receives the three coordinates of the acceleration vector acting on the phone—for a phone in rest this is simply the direction of gravity. The \(x\)- and \(y\)-coordinates are the coordinates of your phone screen, the \(z\)-coordinate points orthogonally into the phone.

Here is a small test program to see the output of the sensor (gravity1.kt):

//
// Gravity sensor test #1
//

import org.otfried.cs109.Context
import org.otfried.cs109.MiniApp

import org.otfried.cs109.Canvas
import org.otfried.cs109.Color
import org.otfried.cs109.DrawStyle
import org.otfried.cs109.TextAlign

fun sq(x: Double) = x * x

class Main(val ctx: Context) : MiniApp {
  var gravity = arrayOf(0.0, 0.0, 0.0)

  init {
    ctx.setTitle("Gravity sensor demo #1")
    ctx.onGravity { x, y, z -> updateGravity(x, y, z) }
  }

  fun updateGravity(x: Double, y: Double, z: Double) {
    gravity = arrayOf(x, y, z)
    ctx.update()
  }

  override fun onDraw(canvas: Canvas) {
    val x = canvas.width / 2.0
    canvas.clear(Color(255, 255, 192))
    canvas.setColor(Color.BLUE)
    canvas.setFont(48.0)
    for (i in 0..2)
      canvas.drawText("%.3f".format(gravity[i]), x, 80.0 + i * 60.0, 
                      TextAlign.CENTER)
    val norm = Math.sqrt(sq(gravity[0]) + sq(gravity[1]) + sq(gravity[2]))
    canvas.drawText("%.3f".format(norm), x, 300.0, TextAlign.CENTER)
  }
}

Again, on the emulator you will have two sliders to adjust the sensor reading (they correspond to tilting the phone left/right, and up/down).

Let's do something more interesting with the accelerometer. The following program will detect when you are holding the phone vertically, and measure the angle by which you have tilted the screen (gravity2.kt):

//
// Gravity sensor test #2
//

import org.otfried.cs109.Context
import org.otfried.cs109.MiniApp

import org.otfried.cs109.Canvas
import org.otfried.cs109.Color
import org.otfried.cs109.DrawStyle
import org.otfried.cs109.TextAlign

fun sq(x: Double) = x * x

class Main(val ctx: Context) : MiniApp {
  var angle: Double? = null

  init {
    ctx.setTitle("Gravity sensor demo #2")
    ctx.onGravity { x, y, z -> updateGravity(x, y, z) }
  }

  fun updateGravity(x: Double, y: Double, z: Double) {
    if (Math.abs(z) > 2.0) {
      angle = null
    } else {
      val n = Math.sqrt(x*x + y*y)
      val x0 = x/n
      val y0 = y/n
      angle = Math.atan2(x0, y0)
    }    
    ctx.update()
  }

  override fun onDraw(canvas: Canvas) {
    val x = canvas.width / 2.0
    val y = canvas.height / 2.0
    val w = (canvas.width + canvas.height).toDouble()
    canvas.clear(Color(255, 255, 192))
    canvas.translate(x, y)
    canvas.setColor(Color.BLUE)
    canvas.setFont(48.0)
    val a = angle
    if (a == null) {
      canvas.drawText("Hold phone vertical", 0.0, 0.0, TextAlign.CENTER)
    } else {
      canvas.rotate(a / Math.PI * 180.0)    
      canvas.beginShape()
      canvas.moveTo(-w, 0.0)
      canvas.lineTo(-w, w)
      canvas.lineTo(w, w)
      canvas.lineTo(w, 0.0)
      canvas.closePath()
      canvas.drawShape()
    }
  }
}

On the emulator, move the bottom slider all the way to the right to hold the phone vertically, then use the upper slider to tilt the phone left and right. But it's more fun to run the mini-app on a real phone: Hold it vertically in front of your face, then try tilting it left and right.

The Android mini-app frameworkMenusUsing the phone sensors