Device Web API Manager向けのデバイスプラグインを開発するための手順を解説します。

Android Studioの[Start a new Android Studio project]を選択します。

Application Name, Company Domainを入力します。

パッケージ名を変更したい場合には、editを選択し変更します。

また、プロジェクトを保存するパスを変更したい場合には、Project Locationを変更します。

入力を行なったら、Nextボタンを押下し、次の画面へ遷移します。

項目 設定値
Application Name My Device Plugin
Company Domain mycompany.com

Minimum SDKは、API 14: Android 4.0 (IceCreamSandWich)を設定していますが、作成するプラグインによって変更します。

設定が完了したら、Nextボタンを押下し、次の画面へん遷移します。

設定画面で、Activityが必要になりますが、プロジェクトを作成する段階では不要なので、Add No Activityを選択します。

選択を行なったら、Finishボタンを押下し、プロジェクトを作成します。

以上の手順でAndroidのプロジェクトが作成されます。

Device Connect Plugin SDKをインポートするには、build.gradleのdependenciesにDevice Connect Plugin SDKを追加します。
以下の設定を追加してください。

▼ build.gradle
android {
    // 省略・・・
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/DEPENDENCIES.txt'
    }
    // 省略・・・
}

repositories {
    maven { url 'https://raw.githubusercontent.com/DeviceConnect/DeviceConnect-Android/master/dConnectSDK/dConnectSDKForAndroid/repository/' }
    maven { url 'https://raw.githubusercontent.com/DeviceConnect/DeviceConnect-Android/master/dConnectDevicePlugin/dConnectDevicePluginSDK/repository/' }
}

dependencies {
    // 省略・・・
    compile 'org.deviceconnect:dconnect-device-plugin-sdk:1.1.0'
}

デバイスプラグインに最小限必要なファイルの説明を行う。

MyMessageServiceProvider.java

MyMessageServiceProvider.javaでは、DConnectMessageServiceProviderを継承(Extend)します。
DConnectMessageServiceProviderを継承することで、Device Connect Managerからの通知を受け付けることが可能になります。

▼ MyMessageServiceProvider.java
package com.mycompany.mydeviceplugin;

import android.app.Service;

import org.deviceconnect.android.message.DConnectMessageServiceProvider;

public class MyMessageServiceProvider extends DConnectMessageServiceProvider {

    @Override
    protected Class getServiceClass() {
        Class clazz = (Class) MyMessageService.class;
        return (Class) clazz;
    }
}

MyMessageService.java

MyMessageService.javaは、DConnectMessageServiceを継承します。
DConnectMessageServiceは、Device Connect Managerから送られてきたDConnectメッセージを処理するためのサービスです。

▼ MyMessageService.java
package com.mycompany.mydeviceplugin;

import com.mycompany.myplugin.profiles.MyBatteryProfile;
import com.mycompany.myplugin.profiles.MySystemProfile;

import org.deviceconnect.android.message.DConnectMessageService;
import org.deviceconnect.android.profile.SystemProfile;
import org.deviceconnect.android.service.DConnectService;

public class MyMessageService extends DConnectMessageService {

    @Override
    public void onCreate() {
        super.onCreate();

        // プラグイン起動時にサービスを追加。
        DConnectService service = new DConnectService("my_service_id");
        service.setName("My Service");
        service.setOnline(true);
        service.addProfile(new MyBatteryProfile());
        getServiceProvider().addService(service);
        // TODO 上記のような常駐サービスにしない場合は適宜修正してください.
    }
}

com_mycompany_myplugin.xml

Device Connectのデバイスプラグインとして、サービスを動作させるためのres/xml/com_mycompany_myplugin.xmlに提供プロファイル一覧を記載する必要があります。

res/xml以下にcom_mycompany_myplugin.xmlを作成します。
初期の状態ではxmlディレクトリは存在していませんので、まずはresにxmlディレクトリを作成する必要があります。
作成したcom_mycompany_myplugin.xmlに以下のように提供するプロファイル一覧を記載します。

▼ com_mycompany_myplugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<deviceplugin-provider>

    <profile name="serviceDiscovery" />
    <profile name="serviceInformation" />
    <profile name="system" />
    <profile name="battery" />

</deviceplugin-provider>

提供するプロファイルが増えた場合には、ここに記載を増やしていきます。

AndroidManifest.xml

Device Web API Managerは、AndroidManifest.xmlに記載されているorg.deviceconnect.android.devicepluginのmeta-dataを持つreceiverをデバイスプラグインとして認識して通信を行います。

▼ com_mycompany_myplugin.xml
        <service
            android:name=".MyMessageService"
            android:exported="false">
        </service>

        <receiver
            android:name=".MyMessageServiceProvider"
            android:enabled="true"
            android:exported="true">
            <meta-data
                android:name="org.deviceconnect.android.deviceplugin"
                android:resource="@xml/com_mycompany_myplugin"/>

            <intent-filter>
                <action android:name="org.deviceconnect.action.GET"/>
                <action android:name="org.deviceconnect.action.PUT"/>
                <action android:name="org.deviceconnect.action.POST"/>
                <action android:name="org.deviceconnect.action.DELETE"/>
            </intent-filter>
        </receiver>

Device Web API Managerでは、meta-dataのXMLからデバイスプラグインが提供するプロファイル一覧などを取得して、Device Web API Managerの設定画面などで使用します。

DConnectServiceのインスタンスをDConnectServiceProviderに追加することで、ServiceDiscoveryプロファイルで発見されるサービスを追加することができます。
DConnectServiceProviderは、DConnectMessageService#getServiceProvider()から取得することができます。

▼ MyMessageService.javaから抜粋
DConnectService service = new DConnectService("my_service_id");
service.setName("My Service");
service.setOnline(true);
service.addProfile(new MyBatteryProfile());
getServiceProvider().addService(service);

DConnectServiceコンストラクタの引数には、サービスの識別子(サービスID)を渡します。
このサービスIDは、ServiceDiscoveryプロファイルで発見されるサービスの識別子になりますので、nullを渡すと例外が発生します。

DConnectService#setName(String)は、ServiceDiscoveryプロファイルで発見されるサービスの名前になるので、サービスが分かるような名前にします。

DConnectService#setOnline(boolean)は、サービスへの接続されている場合はtrueを設定し、接続されていない場合にはfalseを設定します。
アプリは、このonlineを確認してサービスが操作できるのかを判断します。

サービスがサポートしているプロファイルを実装し、DConnectServiceに追加することで、プロファイルを提供することができます。
ここではプロファイルの仕組みと実装方法を解説します。

プロファイルの仕組み

Device Connect APIは、http://localhost:4035/{api}/{profile}/{interface}/{attribute} から構成されます。
それぞれのセグメントは以下のようなkey-valueに分解された形でデバイスプラグインにDevice Connectメッセージとして渡されてきます。

キー 備考
api {api} プロファイル名
profile {profile} プロファイル名
interface {interface} インターフェース名(省略可)
attribute {attribute} アトリビュート名(省略可)

Device Connect SDKでは、送られてきDevice Connectメッセージを以下のような形で処理を振り分けます。

プロファイル名は、DConnectProfileを継承した実装クラスに割り振られます。

  • BatteryProfile
  • CanvasProfile
  • DeviceOrientationProfile
  • LightProfile
  • VibrationProfile
  • etc.
インターフェース名とアトリビュート名はDConnectApiを実装したクラスに割り振られます。
また、DConnectApiには、HTTPメソッドに合わせて、以下の4つのクラスがあります。
  • GetApi
  • PutApi
  • PostApi
  • DeleteApi
デバイスプラグインでは、これらのクラスを実装し、DConnectProfileに追加することで機能を提供します。

例えば、GET /gotapi/battery/level を実装する場合には以下のようになります。
まずは、BatteryProfileを継承したMyBatteryProfileを実装します。
そして、MyBatteryProfileにlevelを取得するためのGetApiを実装し、MyBatteryProfileに追加します。

MyBatteryProfile.java

BatteryProfileを継承したくラスを実装することで、バッテリー情報を取得できるようにします。
このサンプルでは、バッテリー残量の情報を取得するためのAPIを追加します。

▼ MySystemProfile.java
package com.mycompany.myplugin.profiles;

import android.content.Intent;

import org.deviceconnect.android.message.MessageUtils;
import org.deviceconnect.android.profile.BatteryProfile;
import org.deviceconnect.android.profile.api.GetApi;

public class MyBatteryProfile extends BatteryProfile {

    public MyBatteryProfile() {
        // GET /gotapi/battery/level
        addApi(new GetApi() {
            @Override
            public String getAttribute() {
                return ATTRIBUTE_LEVEL;
            }

            @Override
            public boolean onRequest(final Intent request, final Intent response) {
                // TODO: Battery Level APIを実装する.
                MessageUtils.setUnknownError(response, "Not implemented yet.");
                return true;
            }
        });
    }
}

作成したMyBatteryProfileをDConnectServiceに追加します。

▼ プロファイルの追加
DConnectService service = new DConnectService("my_service_id");
service.addProfile(new MyBatteryProfile());

上記の実装を行い、呼び出すと以下のようなJSONが取得できます。

http://localhost:4035/gotapi/battery/level?serviceId=my_service_id.xxxxx.deviceconnect.local
(serviceIdDDききは、Device Web API Managerから取得したものを使用してください。ここに記載しているのは例なので、そのまま入力しても動作しませんのでご注意ください。)

{
    "result": 1,
    "product": "Device Web API Manager",
    "errorCode": 1,
    "version": "v2.1.0",
    "errorMessage": "Not Implemented yet."
}

実際にバッテリー残量の値を返却するためには「// TODO: Battery Level APIを実装する.」の箇所を以下のように修正します。

MessageUtils.setUnknownError(response, "Not implemented yet.");

この部分を以下のように書き換えます。

setResult(response, DConnectMessage.RESULT_OK);
setLevel(response, 0.5f);

これで、バッテリー残量が50%の値を返却するようになります。
この修正を反映させてから実行すると以下のような値が取得できます。

{
    "result": 0,
    "product": "Device Web API Manager",
    "level": 0.5,
    "version": "v2.1.0",
}

MySystemProfile.java

SystemProfileを継承したクラスを実装することで、設定画面の起動などをできるようにします。
このサンプルでは、設定画面の起動が要求された時にSettingsActivityを起動させるようにします。
また、Activityを起動しますので、AndroidManifest.xmlに追記を行う必要があります。

▼ MySystemProfile.java
package com.mycompany.myplugin.profiles;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

import com.mycompany.myplugin.SettingActivity;

import org.deviceconnect.android.profile.SystemProfile;

public class MySystemProfile extends SystemProfile {
    @Override
    protected Class getSettingPageActivity(final Intent intent, final Bundle bundle) {
        // TODO: デバイスと接続するために手動操作が必要な場合は、設定画面を実装してください.
        //       不要な場合は下記で null を返してください.
        return SettingActivity.class;
    }
}

SystemProfileの追加は、他のプロファイルとは違い、DConnectServiceに追加するのではなく、DConnectMessageServiceに追加を行います。
また、DConnectMessageService#getSystemProfile()でMySystemProfileのインスタンスを返すことで追加されます。

▼ MyMessageService.javaの抜粋
    @Override
    protected SystemProfile getSystemProfile() {
        return new MySystemProfile();
    }

設定画面が不要の場合には、nullを返却するようにしてください。

独自に拡張したプロフィルを実装し、DConnectServiceに登録することで、独自のプロファイルが使用できるようになります。
ここでは、独自拡張プロファイルの実装方法を解説します。

独自拡張したプロファイルを実装する場合には、以下の点を実装する必要があります。

  • DConnectProfileを継承して、独自拡張したプロファイルクラスを実装します。
  • 独自拡張したプロファイルの定義ファイルをassets/apiに配置します。

ToastProfile.java

指定された文字列をToastで表示させるプロファイルを作成します。

▼ ToastProfile.java
package com.mydomain.mydeviceplugin.profile;

import android.content.Intent;
import android.widget.Toast;

import org.deviceconnect.android.profile.DConnectProfile;
import org.deviceconnect.android.profile.api.PostApi;
import org.deviceconnect.message.DConnectMessage;

public class ToastProfile extends DConnectProfile {

    public ToastProfile() {
        // POST /gotapi/toast?message=xxxxx
        addApi(new PostApi() {
            @Override
            public boolean onRequest(final Intent request, final Intent response) {
                showToast(request.getStringExtra("message"));
                setResult(response, DConnectMessage.RESULT_OK);
                return true;
            }
        });
    }

    @Override
    public String getProfileName() {
        return "toast";
    }

    private void showToast(final String messageBody) {
        Toast.makeText(getContext(), messageBody, Toast.LENGTH_SHORT).show();
    }
}

独自拡張するプロファイルには、プロファイルを定義するJSONファイルを追加する必要があります。
JSONファイルの名前は、プロファイル名になりますので、toast.jsonとします。

▼ toast.json
{
  "swagger": "2.0",
  "info": {
    "title": "Toast Profile",
    "version": "1.0.0",
    "description": ""
  },
  "consumes": [
    "application/x-www-form-urlencoded",
    "multipart/form-data"
  ],
  "paths": {
    "/": {
      "post": {
        "x-type": "one-shot",
        "summary": "",
        "description": "",
        "parameters": [
          {
            "name": "serviceId",
            "in": "query",
            "required": true,
            "type": "string"
          },
          {
            "name": "message",
            "in": "query",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        }
      }
    }
  }
}

プロファイルを定義するJSONファイルは、swaggerの仕様を元に作成してあります。
詳しい説明は、swaggerのページを参照してください。

デバイスプラグインが提供するプロファイルにToastプロファイルを追加しますので、com_mycompany_myplugin.xmlにtoastを追加します。
com_mycompany_myplugin.xmlに追加するプロファイル名は、ToastProfile#getProfileName()で返却する名前と同じにする必要があります。

▼ com_mycompany_myplugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<deviceplugin-provider>

    <profile name="serviceDiscovery" />
    <profile name="serviceInformation" />
    <profile name="system" />
    <profile name="battery" />
    <profile name="toast" /> <!-- Toastプロファイルを追加 -->

</deviceplugin-provider>

同じく、DConnectServiceにもToastプロファイルを追加します。

▼ MyMessageService.javaから抜粋
DConnectService service = new DConnectService("my_service_id");
service.setName("My Service");
service.setOnline(true);
service.addProfile(new MyBatteryProfile());
service.addProfile(new ToastProfile()); // Toastプロファイルを追加
getServiceProvider().addService(service);

デバイスプラグインの設定画面を実装するためには、SystemProfileを継承したクラスで、SystemProfile#getSettingPageActivity(Intent, Bundle)を実装します。
このメソッドで返却したClassのActivityを設定画面で表示します。

▼ MySystemProfile.javaから抜粋
@Override
protected Class getSettingPageActivity(final Intent intent, final Bundle bundle) {
    // TODO: デバイスと接続するために手動操作が必要な場合は、設定画面を実装してください.
    //       不要な場合は下記で null を返してください.
    return SettingsActivity.class;
}

SettingsActivityの中身は、各デバイスの接続・切断の管理などの実装を行います。

▼ SettingsActivity
package com.mydomain.mydeviceplugin.activity;

import android.app.Activity;
import android.os.Bundle;

import com.mydomain.mydeviceplugin.R;

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings_layout);
    }
}

Device Connect APIでは、定期的に値をアプリに通知するイベントを提供することができます。

イベントを提供するには、以下の3つを実装します。

  • イベント登録
  • イベント通知
  • イベント解除

イベント登録は、イベントの開始をアプリから要求された時に呼び出されます。
ここでは、BatteryプロファイルのonBatteryChangeの実装を行います。このイベントはバッテリーの残量に変化があった時に通知を行うイベントになります。

Device Connect SDKには登録されたイベントを管理するために、EventManagerが用意されています。
イベントの登録を行うには、EventManager#addEvent(Intent)にリクエストを渡すことでイベントを登録することができます。
EventManager#addEvent(Intent)の返り値がEventError.NONEの場合には、登録に成功しているので、イベントの送信を開始します。
EventError.NONE以外が返却された場合には、登録に失敗しているので、イベントの送信は開始せずにエラーを返却します。

MyBatteryProfile.javaに以下のサンプルコードを追加することで、イベントを追加することができます。

▼ イベント登録
// PUT /gotapi/battery/onBatteryChange
addApi(new PutApi() {
    @Override
    public String getAttribute() {
        return ATTRIBUTE_ON_BATTERY_CHANGE;
    }

    @Override
    public boolean onRequest(final Intent request, final Intent response) {
        EventError error = EventManager.INSTANCE.addEvent(request);
        switch (error) {
            case NONE:
                registerBatteryEvent();
                setResult(response, DConnectMessage.RESULT_OK);
                break;
            case INVALID_PARAMETER:
                MessageUtils.setInvalidRequestParameterError(response);
                break;
            default:
                MessageUtils.setUnknownError(response);
                break;
        }
        return true;
    }
});

EventManager#removeEvent(Intent)にリクエストを渡すことで、イベントを解除することができます。

▼ イベント解除
// DELETE /gotapi/battery/onBatteryChange
addApi(new DeleteApi() {
    @Override
    public String getAttribute() {
        return ATTRIBUTE_ON_BATTERY_CHANGE;
    }

    @Override
    public boolean onRequest(final Intent request, final Intent response) {
        EventError error = EventManager.INSTANCE.removeEvent(request);
        switch (error) {
            case NONE:
                unregisterBatteryEvent();
                setResult(response, DConnectMessage.RESULT_OK);
                break;
            case INVALID_PARAMETER:
                MessageUtils.setInvalidRequestParameterError(response);
                break;
            case NOT_FOUND:
                MessageUtils.setInvalidRequestParameterError(response, "Event is not registered.");
                break;
            default:
                MessageUtils.setUnknownError(response);
                break;
        }
        return true;
    }
});

登録されているイベントは、EventManager#getEvents(String, String, String, String)で取得することができます。
実際にイベントを通知するには、DConnectMessageService#sendEvent()もしくは、DConnectProfile#sendEvent()を使用します。

▼ イベント通知
private ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture mFuture;
private float mLevel = 0.5f;

private void registerBatteryEvent() {
    if (mFuture == null) {
        mFuture = mExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                mLevel += 0.1f;
                if (mLevel > 1.0f) {
                    mLevel = 0;
                }

                List events = EventManager.INSTANCE.getEventList("my_service_id",
                        BatteryProfile.PROFILE_NAME, null, BatteryProfile.ATTRIBUTE_ON_BATTERY_CHANGE);
                for (Event event : events) {
                    Bundle battery = new Bundle();
                    setLevel(battery, mLevel);

                    Intent intent = EventManager.createEventMessage(event);
                    setBattery(intent, battery);
                    sendEvent(intent, event.getAccessToken());
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
    }
}

private void unregisterBatteryEvent() {
    if (mFuture != null) {
        mFuture.cancel(true);
        mFuture = null;
    }
}

イベントの停止タイミングには、アプリからの要求以外にもあります。

  • DConnectMessageService#onManagerUninstalled()
  • DConnectMessageService#onManagerTerminated()
  • DConnectMessageService#onManagerEventTransmitDisconnected()
  • DConnectMessageServcie#onDevicePluginReset()
これらのメソッドが呼び出された時に必要に応じてイベントを停止する必要があります。
停止しない場合に不要なイベントが動作することになります。

Device Connect APIでは、レスポンスにURIを返却して、アプリから直接デバイスプラグインが立ち上げたサーバにアクセスして、自由なデータのやり取りを提供します。