enoent

Android Things: first look

What is Android Things?

Android Things is an alternative Android version, announced at Google I/O 2015, and released as a first developer preview in December 2016. Its purpose is to develop embedded IoT devices, with a known and widely documented Android ecosystem basis.

It’s currently running on three different boards: the Intel Edison, the NXP Pico i.MX6UL, and the Raspberry Pi 3. Some higher-end boards are coming soon.

On the SDK side, Android Things comes with a specific support library to ease low-level hardware usage. It consists in two parts: the Peripheral I/O API, which supports GPIO, PWM, I2C, SPI and UART, and the User Driver API, which allows a developer to write a hardware-specific, high-level driver, to ease hardware reusability by injecting events into the Android framework. Other applications can in turn use those events without having to interact with the hardware directly.

There’s a down side: the bundled Android is not as complete as the one you can find on a phone. Most of the standard applications aren’t installed (Calendar, Phone…), and standard content providers are absent too (MediaProvider, Dictionary…).

Android Things supports displays, with the default Android UI toolkit. However, the display is a little bit different from what you’re used to see on an Android device: there’s no notification bar, navigation bar or anything, the running application will use the full display. That is, if it uses it at all: displays are purely optional.

Installing Android Things

Installation depends on the device you’re targeting. Up-to-date, device-specific instructions are available in the official documentation.

Note that there is no emulator available (yet?), you’ll need to install Android Things on a real board.

The next steps of this post assume your local adb is connected to your device (that is, adb devices is listing it as attached).

Creating a new application

Android Things uses the same Activity, Service… lifecycles you’re used to see in any Android application. As so, creating an Android Things project is really close to creating an Android one:

  • create a blank project on Android Studio (selecting a form factor is mandatory, and Android Studio doesn’t support Things yet, keep Phone and Tablet selected), without any activities
  • in the build.gradle, remove all the dependencies and add the Things support library:
build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "fr.enoent.mything"
        minSdkVersion 24
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    provided 'com.google.android.things:androidthings:0.1-devpreview'
}
  • add a reference to the Things support library in the AndroidManifest.xml:
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="fr.enoent.mything">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <uses-library
            android:name="com.google.android.things"/>

    </application>

</manifest>
  • the last step is to create an Activity: create a new blank, without layout, non-support Activity from Android Studio. You’ll also need to add a Things-specific intent-filter to this Activity in the Manifest, so it will start automatically on boot. Keep the Launcher intent-filter to make it easier to start this Activity from Android Studio:
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<manifest package="fr.enoent.mything"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <uses-library
            android:name="com.google.android.things"/>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

            <!-- Launch activity automatically on boot -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.IOT_LAUNCHER"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Now you have an Activity, which is supposed to start automatically on boot. Let’s check that by adding a log in the onCreate method:

MainActivity.java
1
2
3
4
5
6
7
8
public class MainActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d("MainActivity", "onCreate");
    }
}

As you can see, it’s perfectly standard Android code.

You can run it from Android Studio, and you should see the onCreate mention in the logs.

Lights, action!

I won’t cover Android UI in this post, as it’s something really standard you can already find all over the web. Let’s do a much more fun UI: a blinking LED to indicate the application is running.

Connecting the LED

I only have a Raspberry Pi 3, so I won’t be able to cover the other boards for this part. You can find the Pi pinout details here.

The circuit is dead simple: connect the LED’s cathode to Pi’s ground, a small resistor in series with your LED’s anode (Pi’s GPIOs are 3v3), and the other side of the resistor to the Pi’s BCM6. You can use any pin labelled as GPIO in the diagram.

Pimp your ‘droid

Time to go back to Android Studio. First step: listing the available GPIOs on your board. There’s a new class in the support library to access the GPIOs: PeripheralManagerService.

MainActivity.java
1
2
3
4
5
6
7
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PeripheralManagerService service = new PeripheralManagerService();
    Log.d("MainActivity", "Available GPIOs: " + service.getGpioList());
}

This code will list all the GPIOs we’re allowed to use. On a Pi, hopefully you’ll find the BCM6 pin you connected the LED to. Here’s the output on a Pi 3:

Available GPIOs: [BCM12, BCM13, BCM16, BCM17, BCM18, BCM19, BCM20, BCM21, BCM22, BCM23, BCM24, BCM25, BCM26, BCM27, BCM4, BCM5, BCM6]

The next step is to initialize this GPIO. We’ll use the PeripheralManagerService once more to get a reference to it, then set it up to LOW (0v) by default:

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final String GPIO_PIN_NAME_LED = "BCM6";

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PeripheralManagerService service = new PeripheralManagerService();

    try {
        Gpio ledGpio = service.openGpio(GPIO_PIN_NAME_LED);
        ledGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
    } catch (IOException e) {
        Log.e("MainActivity", "Error on PeripheralIO API", e);
    }
}

Now, the only thing left to do is to toggle the GPIO value. This is a single call: ledGpio.setValue(!ledGpio.getValue());. Toggling it every second comes with a purely Android-oriented solution: a Handler, and a delayed Runnable.

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MainActivity extends Activity {
    private static final int INTERVAL_BETWEEN_BLINKS_MS = 1000;
    private static final String GPIO_PIN_NAME_LED = "BCM6";

    private Handler handler = new Handler();
    private Gpio ledGpio;

    private Runnable blinkRunnable = new Runnable() {
        @Override
        public void run() {
            // Exit if the GPIO is already closed
            if (ledGpio == null) {
                return;
            }

            try {
                ledGpio.setValue(!ledGpio.getValue());
                handler.postDelayed(blinkRunnable, INTERVAL_BETWEEN_BLINKS_MS);
            } catch (IOException e) {
                Log.e("MainActivity", "Error on PeripheralIO API", e);
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        PeripheralManagerService service = new PeripheralManagerService();

        try {
            ledGpio = service.openGpio(GPIO_PIN_NAME_LED);
            ledGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);

            handler.post(blinkRunnable);
        } catch (IOException e) {
            Log.e("MainActivity", "Error on PeripheralIO API", e);
        }

    }
}

User drivers

Google already ships some user-drivers, ready to use, in the form of Gradle libraries. Let’s take the Button driver as an example.

Installation

It’s a simple Gradle dependency to add: compile 'com.google.android.things.contrib:driver-button:0.1'

Usage

The button driver provides a simple class which you feed with the GPIO name to use, and a callback:

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MainActivity extends Activity {
    private static final String GPIO_PIN_NAME_BUTTON = "BCM6";

    Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("MainActivity", "onCreate");

        try {
            button = new Button(GPIO_PIN_NAME_BUTTON,
                    Button.LogicState.PRESSED_WHEN_HIGH
            );
            button.setOnButtonEventListener(new Button.OnButtonEventListener() {
                @Override
                public void onButtonEvent(Button button, boolean pressed) {
                    Log.d("MainActivity", "Button has been pressed!");
                }
            });
        } catch (IOException e) {
            Log.e("MainActivity", "Unable to configure the button", e);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        try {
            button.close();
        } catch (IOException e) {
            Log.e("MainActivity", "There's been an error while closing the button");
        }
    }
}

Notice that you’ll have to close the GPIO when you leave your Activity (button.close()).

And with those simple dozen lines of Java, you can use your hardware button to trigger things in your application. The button driver also provides a way to bind your hardware button to a software event, then any application can simply listen to the software key event. This is an example binding the button to the key A:

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MainActivity extends Activity {
    private static final String GPIO_PIN_NAME_BUTTON = "BCM6";

    ButtonInputDriver inputDriver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try {
            inputDriver = new ButtonInputDriver(GPIO_PIN_NAME_BUTTON,
                    Button.LogicState.PRESSED_WHEN_HIGH,
                    KeyEvent.KEYCODE_A // the keycode to send
            );
            inputDriver.register();
        } catch (IOException e) {
            Log.e("MainActivity", "Error while binding button", e);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        inputDriver.unregister();
        try {
            inputDriver.close();
        } catch (IOException e) {
            Log.e("MainActivity", "Error while unregistering the button driver", e);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_A) {
            Log.d("MainActivity", "Button has been pressed");
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}

Conclusion

Even if it may be early to conclude anything from this preview, I have mixed feelings regarding the state of Android Things.

While having the whole UI toolkit available is great for industry-oriented hardware, I don’t really see the point of it in consumer products. Most of the connected devices uses LEDs or a screen with minimal information. From my point of view, a connected device should be set up then forgot, and work without requesting anything from me past the initial configuration. I don’t want to press a button to turn the lights on. I don’t want to take my phone, unlock it, start the relevant application, then press a button to turn the lights on. I want the lights to turn on when I need it. Using a motion sensor, location tracking from my phone, whatever. As long as I have to interact with the lights, they are not smarter than my good old light bulbs with their big button I can use even in the dark with both hands full.

The first thing I expected from this preview was Weave integration. Which is completely absent (I guess it will come eventually). You can start making a connected device powered by Google technologies, but you can’t use the standard Google tries pushing forward to control it. You’ll have to write your own control interface (probably a REST API, which means integrating a web-server in your Android Things application).

Having the ability to work on the IDE I’m used to, with an SDK I already know, and being able to reuse the ton of existing Java libraries is really great though. It makes the entry barrier much lower than usual embedded development. That is, when you have someone else to do the hardware part for you.

I know computing power and storage comes nearly free nowadays, but being used to use AVRs, MSPs…, I can’t help thinking a Raspberry Pi 3 is totaly overkill for that kind of use. I just used a 1.2 GHz, quad-core SoC and 600 MB of storage to blink an LED. Most of those devices will only be remote-controlled and will send data for analysis anyway. An ESP8266 is much smaller, uses less power, comes with built-in Wi-Fi, for a couple bucks.

In the end, I think Google shouldn’t have used Android as a basis for that kind of IoT platform. While it surely looks attractive, it comes with multiple drawbacks inherent to the idea itself. A Google-branded toolchain for something like the ESP8266 with built-in Weave support and first-class Firebase/Google Cloud client libraries would have been a much better approach.

Comments