Getting the Command Result with JPush

The project in the android_jpush directory implements a functionality to receive push notifications using JPush. The source code in the jpush folder includes the functionality developed based on the steps described in Android (JPush) Push Notification Tutorial.

This topic explains how to add the push notification feature to Hello Thing-IF based on the JPush tutorial.

Overview

First, let us grasp the entire flow of getting the command result via push notification. The processes in the pale purple boxes are different from the outcome from the tutorial.

The following processes are different from those in the tutorial.

  • Initializing push notification: The device token is installed when MainActivity displays CommandFragment. The initialization process for the Thing-IF SDK is used.
  • Receiving a message: KiiPushBroadcastReceiver now informs the broadcast receiver of CommandFragment that it received a push notification.
  • Receiving an event on the command screen: The broadcast receiver of CommandFragment receives the command result.

Description of each process is as follows:

Initializing the push notification feature

You need to associate the device registration ID obtained from JPush with the owner in the ThingIFAPI instance in order to initialize push notification with JPush. Hello Thing-IF achieves it by using the initialized ThingIFAPI instance when the login screen is switched to the command screen.

The process to open the command screen appears twice in MainActivity as indicated in Screen Transition with Fragments. Both call registerJPush() as below.

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

registerJPush();

registerJPush() is implemented as a private method of MainActivity as below. It just calls registerJPush() of PromiseAPIWrapper and displays the result in the toast.

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();
      }
    });
  }
  ......
}

registerJPush() of PromiseAPIWrapper is implemented as below. It associates the owner with the device registration ID of JPush and registers it to Thing Interaction Framework by calling mApi.installPush().

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;
    }
  });
}

The process implemented here is substantially the same as that described in Implement Your Application of the JPush tutorial. The difference is that Hello Thing-IF initializes push notification every time the mobile app starts as with the version with GCM while the JPush tutorial skips initialization after the custom JPushPreference class initializes push notification once.

In addition, MainActivity implements processes to execute when the mobile app starts or pauses as in the JPush tutorial.

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();
  }
  ......
}

Receiving a message

When the mobile app receives a message from JPush, it calls onReceive() in KiiPushBroadcastReceiver.

The command execution is notified as a Direct Push message, which is one of the three types of push notification (Push to App, Push to User, and Direct Push) of Kii Cloud. The payload includes the commandID parameter.

The below code gets the command 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;
      }
    }
  }
}

If directMsg.getMessage().getString() can get commandID, it means Thing Interaction Framework notified that the command had been executed. This notification is forwarded to the command screen by the broadcast of INTENT_COMMAND_RESULT_RECEIVED. commandID is included as the broadcast parameter.

Receiving an event on the command screen

The command screen receives the event of push notification via the broadcast.

The following code enables and disables the broadcast receiver mCommandResultBroadcastReceiver in CommandFragment. The receiver is enabled when the command screen is activated and disabled when the screen is stopped.

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();
  }
}

See below for mCommandResultBroadcastReceiver implementation. It gets commandID as a parameter of onReceive().

See Getting the Command Result for implementation 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);
    }
  };
  ......
}

Getting the command result

The push message from Thing Interaction Framework eventually calls the onPushMessageReceived() method with the command ID as the argument.

This method gets the entire command which has the specified command ID and evaluates the command result.

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());
    }
  });
}

First, it calls the getCommand() method of PromiseAPIWrapper. This method gets the entire command in a worker thread. It calls the onDone() method with the obtained command as the argument when the command is successfully obtained.

getActionResults() in onDone() gets an array of ActionResult from the command. This is an array of the subclasses of ActionResult prepared in [Defining a State and Actions(/en/samples/hello-thingif/android/login/#schema). The array should have TurnPowerResult and SetBrightnessResult instances according to the sent actions. If you turned off the power, the array should have only a TurnPowerResult instance. Each instance is processed with the for statement one by one.

Hello Thing-IF outputs error messages in one batch with StringBuilder. You can get the action name defined with getActionName() of ActionResult, the flag of success or failure, and the error message at failure from each ActionResult.

Finally, the concatenated error messages or the success message The command succeeded is displayed on the screen via mCommandResult.

See below for getTargetState() implementation in PromiseAPIWrapper. It wraps getCommand() of ThingIFAPI.

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);
    }
  });
}