Getting the Command Result with GCM

The project in the android_gcm directory implements a functionality to receive push notifications using GCM. The source code in the gcm folder includes the functionality developed based on the steps described in Android (GCM) Push Notification Tutorial. See the GCM push notification tutorial for the flow of receiving push notifications.

This topic explains how to add the push notification feature to Hello Thing-IF based on the GCM 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 those in the GCM push notification tutorial.

The following processes are different from those in the GCM sample.

  • Initializing push notification: RegistrationIntentService is changed to start when CommandFragment is displayed. The process to install the device token is changed for the Thing-IF SDK.
  • Receiving a message: MyGCMListenerService 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 token obtained from GCM with the owner in the ThingIFAPI instance in order to initialize push notification with GCM. Hello Thing-IF achieves it by passing the ThingIFAPI instance to RegistgrationIntentService 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 use the code below.

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

The first three lines switch the fragment to the command screen as explained previously. Just after the switch, RegistrationIntentService is started to initialize GCM. The ThingIFAPI instance mApi is passed to the service as the start parameter PARAM_THING_IF_API.

PARAM_THING_IF_API is declared as below.

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

See below for RegistrationIntentService implementation.

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() gets the device token from GCM and stores it in token. The next two lines are specific to push notification with the Thing-IF SDK. The first line obtains the ThingIFAPI instance passed earlier when the service is started and the second line calls the installPush() method, which associates the owner with the GCM device token.

On completion of the association process, INTENT_PUSH_REGISTRATION_COMPLETED is broadcasted as described in the push notification tutorial. The broadcast receiver is implemented in MainActivity as below. It just displays the toast.

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

Receiving a message

When the mobile app receives a message from GCM, it calls onMessageReceived() in MyGcmListenerService.

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

If data.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 of onPushMessageReceived().

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