GCM によるコマンドリザルトの取得

android_gcm ディレクトリのプロジェクトでは、GCM を使ってプッシュ通知を受信する処理を実装しています。ソースコードの gcm フォルダ以下の実装は、Android (GCM) プッシュ通知設定チュートリアル の手順をすべて実装した結果です。受信処理の流れは、GCM の プッシュ通知設定チュートリアル で解説しています。

ここでは、GCM のチュートリアルの結果をベースに、 Hello Thing-IF の機能を組み込む方法を説明します。

全体像

はじめに、プッシュ通知によってコマンドリザルトの取得を行うまでの全体像を示します。薄い紫色の箇所が GCM のプッシュ通知設定チュートリアルから作り替える処理です。

GCM サンプルからの変更点は以下のとおりです。

  • プッシュ通知の初期化:RegistrationIntentService を起動するタイミングは、CommandFragment を表示するタイミングに変更します。同時に、デバイストークンをインストールする処理を Thing-IF SDK 用のものに変更します。
  • メッセージの受信:MyGCMListenerService でプッシュ通知を受け取った際、ブロードキャストを使って CommandFragment のブロードキャストレシーバに通知するようにします。
  • コマンド画面でのイベント受信:CommandFragment のブロードキャストレシーバでコマンドリザルトの取得処理を行います。

それぞれ、以下で詳細を説明します。

プッシュ通知の初期化

GCM を使ってプッシュ通知を初期化するには、GCM から取得したデバイストークンと、ThingIFAPI インスタンスが持つオーナーユーザーの情報を紐付ける必要があります。そのため、Hello Thing-IF では、ログイン画面からコマンド画面に切り替わるタイミングで、ThingIFAPI インスタンスを RegistrationIntentService に渡して、デバイストークンとオーナーを紐付けます。

コマンド画面に切り替える処理は、フラグメントによる画面遷移 に示したとおり、MainActivity で 2 箇所あります。どちらも以下のように実装しています。

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main, CommandFragment.newInstance(mApi));
transaction.commit();

Intent intent = new Intent(this, RegistrationIntentService.class);
intent.putExtra(RegistrationIntentService.PARAM_THING_IF_API, mApi);
startService(intent);

はじめの 3 行はフラグメントをコマンド画面に切り替えるもので、すでに説明したとおりです。その直後に RegistrationIntentService を起動して GCM の初期化処理を開始します。この際、サービスの起動パラメーター PARAM_THING_IF_API として、ThingIFAPI のインスタンス mApi を渡します。

PARAM_THING_IF_API は以下のように宣言されています。

public class RegistrationIntentService extends IntentService {
  public static final String PARAM_THING_IF_API = "ThingIFAPI";
}

RegistrationIntentService の実装は以下のようになっています。

public class RegistrationIntentService extends IntentService {
  ......
  @Override
  protected void onHandleIntent(Intent intent) {
    ......
    InstanceID instanceID = InstanceID.getInstance(this);
    String senderId = getString(R.string.gcm_defaultSenderId);
    String token = instanceID.getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

    ThingIFAPI api = intent.getParcelableExtra(PARAM_THING_IF_API);
    api.installPush(token, PushBackend.GCM);
    ......
  }
}

onHandleIntent() では、GCM からデバイストークンを取得して token とします。次の 2 行が Thing-IF SDK でのプッシュ通知特有のコードです。先ほど、サービスを起動する際に渡した ThingIFAPI のインスタンスを取得し、installPush メソッドを呼び出します。これにより、オーナーと GCM のデバイストークンが紐付きます。

処理が完了すると、プッシュ通知設定チュートリアルの解説のとおり、INTENT_PUSH_REGISTRATION_COMPLETED でのブロードキャストが行われます。ブロードキャストレシーバは、MainActivity に以下のように実装されています。ここでは、トーストを表示するだけの処理としています。

mRegistrationBroadcastReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    String errorMessage = intent.getStringExtra(RegistrationIntentService.PARAM_ERROR_MESSAGE);
    Log.e("GCMTest", "Registration completed:" + errorMessage);
    if (errorMessage != null) {
      Toast.makeText(MainActivity.this, "Error push registration:" + errorMessage, Toast.LENGTH_LONG).show();
    } else {
      Toast.makeText(MainActivity.this, "Succeeded push registration", Toast.LENGTH_LONG).show();
    }
  }
};

メッセージの受信

GCM からメッセージが届くと、MyGcmListenerService の onMessageReceived() が呼び出されます。

コマンドの送信結果は、Kii Cloud の 3 種類あるプッシュ通知(Push to App、Push to User、Direct Push)のうち、Direct Push として通知されます。その際、ペイロードの commandID パラメーターにコマンド ID が含まれています。

コマンド ID を取得する処理は以下のとおりです。

public class MyGcmListenerService extends GcmListenerService {
  public static final String INTENT_COMMAND_RESULT_RECEIVED = "com.kii.sample.hellothingif.COMMAND_RESULT_RECEIVED";
  public static final String PARAM_COMMAND_ID = "CommandID";

  @Override
  public void onMessageReceived(String from, Bundle data) {
    ReceivedMessage message = PushMessageBundleHelper.parse(data);
    KiiUser sender = message.getSender();
    PushMessageBundleHelper.MessageType type = message.pushMessageType();
    switch (type) {
      case PUSH_TO_APP:
        PushToAppMessage appMsg = (PushToAppMessage) message;
        Log.d(TAG, "PUSH_TO_APP Received");
        break;
      case PUSH_TO_USER:
        PushToUserMessage userMsg = (PushToUserMessage) message;
        Log.d(TAG, "PUSH_TO_USER Received");
        break;
      case DIRECT_PUSH:
        DirectPushMessage directMsg = (DirectPushMessage) message;
        Log.d(TAG, "DIRECT_PUSH Received");
        String commandID = data.getString("commandID");
        if (commandID != null) {
            Intent registrationComplete = new Intent(INTENT_COMMAND_RESULT_RECEIVED);
            registrationComplete.putExtra(PARAM_COMMAND_ID, commandID);
            LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
        }
        break;
    }
  }
}

data.getString() によって commandID が取得できると、コマンドの実行結果が Thing Interaction Framework から送信されてきたことを意味します。このことを、ブロードキャスト INTENT_COMMAND_RESULT_RECEIVED によってコマンド画面に転送します。ブロードキャストのパラメーターには commandID を含めるようにします。

コマンド画面でのイベント受信

プッシュ通知を受け取ったイベントは、ブロードキャスト経由でコマンド画面が受信します。

以下は CommandFragment でブロードキャストレシーバ mCommandResultBroadcastReceiver の有効/無効を切り替える処理です。コマンド画面がアクティブになったときに有効化し、停止するときに無効化します。

public class CommandFragment extends Fragment {
  @Override
  public void onResume() {
    super.onResume();
    LocalBroadcastManager.getInstance(getContext()).registerReceiver(mCommandResultBroadcastReceiver,
            new IntentFilter(MyGcmListenerService.INTENT_COMMAND_RESULT_RECEIVED));
  }

  @Override
  public void onPause() {
    LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mCommandResultBroadcastReceiver);
    super.onPause();
  }
}

ブロードキャストレシーバ mCommandResultBroadcastReceiver の実装は以下のとおりです。onReceive() でブロードキャストを受け取ったとき、commandID をパラメーターとして取得します。

onPushMessageReceived() の実装は、この後、コマンドリザルトの取得 で示します。

private BroadcastReceiver mCommandResultBroadcastReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    String commandID = intent.getStringExtra(MyGcmListenerService.PARAM_COMMAND_ID);
    onPushMessageReceived(commandID);
  }
};

コマンドリザルトの取得

Thing Interaction Framework からのプッシュ通知を受け取ると、最終的に、コマンド ID を引数にとって onPushMessageReceived() メソッドが呼び出されます。

ここでは、Thing Interaction Framework から、指定されたコマンド ID を持つコマンドの全体を取得し、コマンドが持つコマンドリザルトを評価します。

private void onPushMessageReceived(String commandID) {
  PromiseAPIWrapper api = new PromiseAPIWrapper(mAdm, mApi);
  mAdm.when(api.getCommand(commandID)
  ).then(new DoneCallback<Command>() {
    @Override
    public void onDone(Command command) {
      if (getActivity() == null) {
        return;
      }
      List<ActionResult> results = command.getActionResults();
      StringBuilder sbMessage = new StringBuilder();
      for (ActionResult result : results) {
        String actionName = result.getActionName();
        boolean succeeded = result.succeeded();
        String errorMessage = result.getErrorMessage();
        if (!succeeded) {
          sbMessage.append(errorMessage);
        }
      }
      if (sbMessage.length() == 0) {
        sbMessage.append("The command succeeded.");
      }
      mCommandResult.setText(sbMessage.toString());
    }
  }).fail(new FailCallback<Throwable>() {
    @Override
    public void onFail(final Throwable tr) {
      showToast("Failed to receive the command result: " + tr.getLocalizedMessage());
    }
  });
}

まず、PromiseAPIWrapper の getCommand() メソッドが呼び出されます。このメソッドは作業スレッドでコマンド全体を取得します。成功時は取得したコマンドを引数として onDone() メソッドが呼び出されます。

onDone() では、getActionResults() によってコマンドから ActionResult の配列を取得します。これは、ステートとアクションの定義 で用意した ActionResult のサブクラスの配列です。送信したアクションに応じて、TurnPowerResult インスタンスと SetBrightnessResult インスタンスを要素として含んでいるはずです(電源オフの場合は TurnPowerResult のみ)。これを for で順番に処理します。

Hello Thing-IF では、エラーメッセージを StringBuilder で 1 つにまとめて出力します。各 ActionResult からは、ActionResult の getActionName() で定義したアクション名、成功したかどうかのフラグ、失敗時のエラーメッセージを取得できます。

最終的に、連結したエラーメッセージ、または、The command succeeded. という成功メッセージを mCommandResult 経由で画面に出力します。

PromiseAPIWrapper での getTargetState() の実装は以下のとおりです。ThingIFAPI の getCommand() をラップしています。

public Promise<Command, Throwable, Void> getCommand(final String commandID) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, Command>() {
    @Override
    protected Command doInBackgroundSafe(Void... voids) throws Exception {
      return mApi.getCommand(commandID);
    }
  });
}