Android ThingsのUser Driverを理解する

[Android Things](https://developer.android.com/things/index.html)の[User-Space Drivers](https://developer.android.com/things/sdk/drivers/index.html)がいまひとつぴんとこなかったので、サンプルの[sample-button](https://github.com/androidthings/sample-button)を、ドキュメントのコード例のようにServiceで実装しながらまとめたメモ。

## User Driverとして実装することの利点

ほとんどのアプリは、[Peripheral I/O](https://developer.android.com/things/sdk/pio/index.html)を使って直接H/Wとやりとりするだけで足りるので、User Driverとして局所化し、Android Frameworkを介するまでもない。しかし、可搬性・再利用性・統合性を高めたかったらUser Driverとして実装したほうがよい、ということ。

## User Driverタイプ

User Driver APIが対象としているのは、[GPS](https://developer.android.com/things/sdk/drivers/gps.html)、キーボードなど[Human Interface Devices(HID)](https://developer.android.com/things/sdk/drivers/input.html)、[センサー](https://developer.android.com/things/sdk/drivers/sensors.html)の3種類の入力デバイス。[drivers-samples](https://github.com/androidthings/drivers-samples/tree/master/ht16k33)の中のSegment display sampleが使用しているht16k33ディスプレイドライバなどの出力デバイスドライバを広義に（あるいは混同して）User Driverと呼ぶことはあるかもしれないが、少なくともAPIの対象ではない。

|com.google.android.things.userdriverパッケージクラス|
|:--|
|GpsDriver|
|InputDriver|
|InputDriver.Builder|
|UserDriverManager|
|UserSensor|
|UserSensor.Builder|
|UserSensorDriver|
|UserSensorReading|

ただし、出力デバイスも入力デバイス同様にServiceなどに隠蔽して疎結合とし、可搬性を高めることは可能であり、実際にサンプルのLEDドライバをService化してみた。

## 実装例

GPS/HID/センサーともにパターンは同じ。尚、ドキュメントのコード例はServiceを使用しているが、サンプルの[contrib-drivers](https://github.com/androidthings/contrib-drivers)では使用しておらず、必須というわけではない。前述の通りServiceを使用したほうが若干結合度が弱いと思うが、このあたりはケースバイケースで使い分ければよいだろう。

### User Driver側

といっても、往々にしてアプリと同梱なので、アプリ下位側とでもいうべきか。

1) 開始時にドライバを生成し、UserDriverManagerに登録する。

- GPSは、GpsDriverを生成し登録する。

```
    private GpsDriver mDriver;
...
    mDriver = new GpsDriver();
    UserDriverManager.getManager().registerGpsDriver(mDriver);
```

- HIDは、ビルダーを使ってInputDriverを生成し、登録する。

```
    private InputDriver mDriver;
...
    mDriver = InputDriver.builder(InputDevice.SOURCE_CLASS_BUTTON)
                .setName(DRIVER_NAME)
                .setVersion(DRIVER_VERSION)
                .setKeys(new int[] {KEY_CODE})
                .build();
    UserDriverManager.getManager().registerInputDriver(mDriver);
```

- センサーは、UserSensorDriverの継承クラスインスタンスを生成し、ビルダーを使ったUserSensor生成時に渡し、UserSensorインスタンスを登録する。

```
    private UserSensorDriver mDriver = new UserSensorDriver() {};
    private UserSensor mSensor;
...
    mSensor = UserSensor.builder()
        .setName("GroveAccelerometer")
        .setVendor("Seeed")
        .setType(Sensor.TYPE_ACCELEROMETER)
        .setDriver(mDriver)
        .build();
    UserDriverManager.getManager().registerSensor(mSensor);
```

2) トリガー発生時もしくは定期的にH/Wイベントを注入する。

- GPSは、フレーム周期ごとにLocationイベントを送る。

```
    mDriver.reportLocation(mLastKnownLocation);
```

- HIDは、監視しているポートの立ち上がり（立ち下がり）を検知し、イベントを発する。

```
    int action = pressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
    KeyEvent[] events = new KeyEvent[] {new KeyEvent(action, KEY_CODE)};
    mDriver.emit(events);
```

- センサーは、UserSensorDriver#readがコールバックされたときに、センサーデバイスからデータを読み込んで値を返す。

```
UserSensorDriver mDriver = new UserSensorDriver() {
    // Sensor data values
    float x, y, z;

    @Override
    public UserSensorReading read() {
        try {
            // ...read the sensor hardware...

            // Return a new reading
            return new UserSensorReading(new float[]{x, y, z});
        } (catch Exception e) {
            // Error occurred reading the sensor hardware
            throw new IOException("Unable to read sensor");
        }
    }
};
```

3) 終了時にUserDriverManagerの登録を解除する。

- GPS

```
    UserDriverManager.getManager().unregisterGpsDriver(mDriver);
```

- HID

```
    UserDriverManager.getManager().unregisterInputDriver(mDriver);
```

- センサー

```
    UserDriverManager.getManager().unregisterSensor(mSensor);
```

### アプリ側

1) 開始時にサービスを起動する。

```
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        startService(new Intent(this, YourDriverService.class));
    }
```

2) コールバックでイベントを受信する。

- GPSは、LocationListenerで受信する。

```
    LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
                    0, 0, new LocationListener() {
        @Override
        public void onLocationChanged(Location location) { }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) { }

        @Override
        public void onProviderEnabled(String provider) { }

        @Override
        public void onProviderDisabled(String provider) { }
    });

```

- HIDは、当該メソッドで受信する。以下はキーの場合。

```
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return super.onKeyDown(keyCode, event);
    }

    public boolean onKeyUp(int keyCode, KeyEvent event) {
        return super.onKeyUp(keyCode, event);
    }
```

- センサーは、SensorEventListenerで受信する。

```
    SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    sensorManager.registerDynamicSensorCallback(new SensorManager.DynamicSensorCallback() {
        @Override
        public void onDynamicSensorConnected(Sensor sensor) {
            if (sensor.getType() == Sensor.TYPE_AMBIENT_TEMPERATURE) {
                sensorManager.registerListener(new SensorEventListener() {
                    @Override
                    public void onSensorChanged(SensorEvent event) { }

                    @Override
                    public void onAccuracyChanged(Sensor sensor, int accuracy) { }
                }, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }
    });
```

3) 終了時にサービスを停止する。

```
    protected void onDestroy(){
        super.onDestroy();

        stopService(new Intent(this, YourDriverService.class));
    }
```

Service化したソースはここにあります。
https://github.com/tatsu/sample-button