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

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

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

全体像

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

チュートリアルからの変更点は以下のとおりです。

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

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

プッシュ通知の初期化

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

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

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

registerJPush();

registerJPush() は MainActivity のプライベートメソッドとして以下のように実装されています。PromiseAPIWrapper の registerJPush() を呼び出して、結果をトーストで表示するだけの実装です。

public class MainActivity extends AppCompatActivity implements LoginFragment.OnFragmentInteractionListener {
  ......
  private void registerJPush() {
    PromiseAPIWrapper api = new PromiseAPIWrapper(mAdm, mApi);
    mAdm.when(api.registerJPush(getApplicationContext())
    ).then(new DoneCallback<Void>() {
      @Override
      public void onDone(Void param) {
        Toast.makeText(MainActivity.this, "Succeeded push registration", Toast.LENGTH_LONG).show();
      }
    }).fail(new FailCallback<Throwable>() {
      @Override
      public void onFail(final Throwable tr) {
        Toast.makeText(MainActivity.this, "Error push registration:" + tr.getLocalizedMessage(), Toast.LENGTH_LONG).show();
      }
    });
  }
  ......
}

PromiseAPIWrapper の registerJPush() の実装は以下のとおりです。mApi.installPush() を呼び出すことで、オーナーと JPush の登録 ID(デバイス)を紐付けて Thing Interaction Framework に登録しています。

public Promise<Void, Throwable, Void> registerJPush(final Context context) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackgroundSafe(Void... voids) throws Exception {
      JPushInterface.resumePush(context);
      String regId = JPushInterface.getUdid(context);
      JPushInterface.setAlias(context, regId, null);
      mApi.installPush(regId, PushBackend.JPUSH);
      return null;
    }
  });
}

ここで実装している処理は、JPush チュートリアルの プログラムの実装 に記載されている処理と実質的に同じです。ただし、チュートリアルでは独自クラス JPushPreference を使って、一度初期化した後は初期化をスキップする実装ですが、ここでは GCM の実装と同じくモバイルアプリの起動ごとに初期化するようにしています。

その他、MainActivity では、JPush チュートリアルのとおり、起動時と中断時の処理を実装しています。

public class MainActivity extends AppCompatActivity implements LoginFragment.OnFragmentInteractionListener {
  ......
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    JPushInterface.init(this);
    ......
  }

  @Override
  protected void onResume() {
    super.onResume();
    JPushInterface.onResume(this);
  }

  @Override
  protected void onPause() {
    JPushInterface.onPause(this);
    super.onPause();
  }
  ......
}

メッセージの受信

JPush からメッセージが届くと、KiiPushBroadcastReceiver の onReceive() が呼び出されます。

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

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

public class KiiPushBroadcastReceiver extends BroadcastReceiver {
  private static final String TAG = "KiiPushBroadcastRecv";
  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 onReceive(Context context, Intent intent) {
    String jpushMessageType = intent.getAction();
    if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(jpushMessageType)) {
      Bundle extras = intent.getExtras();
      ReceivedMessage message = PushMessageBundleHelper.parse(extras);
      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 = directMsg.getMessage().getString("commandID");
          if (commandID != null) {
              Intent registrationComplete = new Intent(INTENT_COMMAND_RESULT_RECEIVED);
              registrationComplete.putExtra(PARAM_COMMAND_ID, commandID);
              LocalBroadcastManager.getInstance(context).sendBroadcast(registrationComplete);
          }
          break;
      }
    }
  }
}

directMsg.getMessage().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() の実装は、この後、コマンドリザルトの取得 で示します。

public class CommandFragment extends Fragment {
  ......
  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);
    }
  });
}