BlackBerry Spark Communications Services Guide

Getting Started with Android

This overview will guide you through the steps required to integrate the BlackBerry Spark Communications Services SDK for Android with your application.

Prerequisites

Before adding the SDK to your application, ensure that your project meets the minimum requirements. To build and run your application using the SDK, you will need:

Add the SDK AAR to your Application

Download and extract the latest version of the Spark Communications Services SDK.

Copy sdk/bbm_sdk-release.aar into the app/libs directory of your application.

Edit your application's app/build.gradle file to ensure the SDK AAR is included as a dependency in your application's build:

dependencies {
  // The guava library is required by the SDK.
  implementation 'com.google.guava:guava:22.0-android'

  // This indicates that your application depends on the SDK.
  implementation files('libs/bbm_sdk-release.aar')

  // ...any other dependencies your application might have.
}

Configure the SDK with your Domain

You provide the ID of your newly created domain to the SDK by adding the com.bbm.sdk.UserDomain entry to the application manifest. The manifest also lets you select between the sandbox and production environments with the com.bbm.sdk.environment.sandbox flag.

Add the following lines to the application section of the manifest. Replace YOUR_DOMAIN_ID with the ID of your sandbox domain.

  <!-- Configure the Spark Communications Services domain.
    Replace 'YOUR_DOMAIN_ID' with the ID domain you created for your application. -->
  <meta-data android:name="com.bbm.sdk.UserDomain" android:value="YOUR_DOMAIN_ID"/>

  <!-- This is used to configure the SDK to use either the Production or the
    Sandbox environment.  By default, the SDK is configured to use the
    Production environment.
    Use this directive to configure your application to use the Sandbox
    environment. -->
  <meta-data android:name="com.bbm.sdk.environment.sandbox" android:value="true"/>

The Observer Pattern

On Android, the SDK uses an observer pattern to notify your application about events. Items requested from the SDK are returned as ObservableValues. You can listen for changes to an ObservableValue by adding an Observer to the observable. When the value of an Observable is modified its Observable.changed() method is called.

ObservableValue<String> example;
example.addObserver(new Observer() {
    @Override
    public void changed() {
        String changedValue = example.get();
    }
});
//Call changed to trigger the observer
example.changed()

The SDK includes helpers to simplify the use of the Observable class. Use ObservableMonitor and SingleShotMonitor to easily track any changes to Observable objects. These classes track changes to any Observer objects accessed within the monitor.

//Example Observable Monitor
ObservableValue<String> exampleObservableValue;
ObservableMonitor exampleMonitor = new ObservableMonitor() {
    @Override
    protected void run() {
        String observedValue = exampleObservableValue.get();
    }
};
//Observable monitors can be activated in Activity#onResume and disposed in Activity#onPause
exampleMonitor.activate();

SingleShotMonitors can be used to track Observables until a specific condition is met.

SingleshotMonitor.run(new SingleshotMonitor.RunUntilTrue() {
    @Override
    public boolean run() {
        String observedValue = exampleObservableValue.get();
        //Continue observing until the value equals "finished"
        if (observedValue.equals("finished")) {
            return true;
        }
        return false;
    }
});

Start the SDK

On Android, the entry point to the SDK is the BBMEnterprise singleton. Before you can interact with the SDK, you must initialize it with an Android Context. The initialization step prepares the native components of the SDK. After calling BBMEnterprise.start(), the SDK is ready to receive messages from your application.

// Initialize the SDK
BBMEnterprise.getInstance().initialize(this);

//Start the SDK
final boolean startSuccessful = BBMEnterprise.getInstance().start();
if (!startSuccessful) {
    //implies BBMEnterprise was already started.  Call stop before trying to start again
    Toast.makeText(SetupActivity.this, "Service already started.", Toast.LENGTH_LONG).show();
}

Observe the SDK State

Your application can monitor the state of the SDK. For example, the code below enables a sign-in button only when the state is STARTED.

//Create an observer to monitor the SDK state
private final Observer mEnterpriseStateObserver = new Observer() {
    @Override
    public void changed() {
        BBMEnterpriseState bbmEnterpriseState = BBMEnterprise.getInstance().getState().get();
        switch (bbmEnterpriseState) {
            case STARTING:
                break;
            case STARTED:
                mSignInButton.setVisibility(View.VISIBLE);
                break;
            case FAILED:
                //If the SDK state changes to FAILED read the failure reason.
                BBMEnterpriseFailureReason failReason = BBMEnterprise.getInstance().getFailureReason();
                //Take action on the failure
                //...
            case STOPPED:
            default:
                mSignInButton.setVisibility(View.VISIBLE);
                break;
        }
    }
};
//Add the observer to the state ObservableValue
BBMEnterprise.getInstance().getState().addObserver(mEnterpriseStateObserver);

The Existence Idiom

When your application requests an item from the BbmdsProtocol, it receives an ObservableValue as the result. However, the true value of the item you requested might not be immediately known. For example, when requesting a Chat, the chat data might need to be loaded from storage. The ObservableValue that is immediately returned will contain a placeholder value. To distinguish between the placeholder value and the real value, there is the the Existence interface.

Existence Value Description
MAYBE When the existence is MAYBE, the actual value is unknown.
YES When the existence is YES, the value exists and can be trusted.
NO When the existence is NO, the value does not exist in the SDK.
Chat chat = BBMEnterprise.getInstance().getBbmdsProtocol().getChat("chatId").get();
switch (chat.exists){
    case MAYBE:
        //Wait for the chat to be provided
        break;
    case YES:
        //A chat with this ID exists
        break;
    case NO:
        //A chat with this ID could not be found
        break;
}

Generate an Authentication Token

To accelerate development, sandbox domains can be configured to accept an unsigned JWT authentication token. You can generate such a token locally without an external identity provider. An unsigned token's header algorithm alg parameter is set to none and the jti token ID is randomly generated. For example:

//User ID is hard-coded for convenience here
String userId = "sampleUserId";

JSONObject header = new JSONObject();
header.put("alg", "none");

SecureRandom rand = new SecureRandom();
byte[] bytes = new byte[128];
//Get some random bytes
rand.nextBytes(bytes);
//Use the first 18 characters as the token ID
String jti = Base64.encodeToString(bytes, base64Flags).substring(0, 18);

JSONObject body = new JSONObject();
body.put("iss", "NoIDP");
body.put("jti", jti);
body.put("sub", userId);
body.put("iat", System.currentTimeMillis() / 1000);
//Expires in one hour.
body.put("exp", System.currentTimeMillis() + 1000 * 60 * 60);

String base64Header = Base64.encodeToString(header.toString().getBytes(), base64Flags);
String base64Body = Base64.encodeToString(body.toString().getBytes(), base64Flags);

token = base64Header + '.' + base64Body + '.';

Your application gives the authentication token to the SDK by sending an AuthToken message.

AuthToken authToken = new AuthToken(token, userId);
BBMEnterprise.getInstance().getBbmdsProtocol().send(authToken);

Monitor the GlobalAuthTokenState

The GlobalAuthTokenState tells you when the SDK needs an AuthToken to perform user authentication. When the GlobalAuthTokenState is Needed, you must send an AuthToken to the SDK to start or maintain your application's authorization with the BlackBerry Infrastructure.

Auth Token State Description
Needed When the state is Needed, your application must send an AuthToken to the SDK.
Ok When the state is Ok, your application does not need to take any action.
Rejected When the state is Rejected, the SDK was unable to use the AuthToken that your application gave it.
//Create an observer to monitor the auth token state
private final Observer mAuthTokenStateObserver = new Observer() {
    @Override
    public void changed() {
        GlobalAuthTokenState authTokenState = BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalAuthTokenState().get();
        if (authTokenState.getExists() != Existence.YES) {
            return;
        }

        switch (authTokenState.value) {
            case Ok:
                break;
            case Needed:
                //Generate an unsigned token to authenticate with the BlackBerry Infrastructure
                generateAuthToken();
                break;
            case Rejected:
                break;
            case Unspecified:
                break;
        }
    }
};

Monitor the GlobalSetupState

The GlobalSetupState indicates the state of the SDK endpoint setup process.

Setup State Description
NotRequested When the setup state is NotRequested, your application must register the local device as a new endpoint.
Ongoing When the setup state is Ongoing, the progressMessage property of the GlobalSetupState will indicate the current phase of setup.
Full The SDK limits the number of endpoints of each type that an identity can have registered with the BlackBerry Infrastructure simultaneously. When the setup state is Full, the identity has reached the maximum number of endpoints. Before continuing, at least one endpoint must be deregistered. Your application can request the set of existing endpoints and decide which endpoint to remove. The identity's current set of endpoints can be requested with the EndpointsGet message.
//Create an observer to monitor the setup state global
private final Observer mSetupStateObserver = new Observer() {
    @Override
    public void changed() {
        final GlobalSetupState setupState = BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalSetupState().get();

        if (setupState.getExists() != Existence.YES) {
            return;
        }

        switch (setupState.state) {
            case NotRequested:
                //Register this device as a new endpoint
                registerDevice();
                break;
            case Full:
                //Handle the case where this account has reached the maximum number of registered endpoints
                handleFullState();
                break;
            case Ongoing:
                //Ongoing has additional information in the progressMessage
                mSetupStateView.setText(setupState.state.toString() + ":" + setupState.progressMessage.toString());
                break;
            case SyncRequired:
                //SyncRequired state is processed by the syncPasscodeStateObserver
            case Success:
                //Setup completed
                break;
            case Unspecified:
                break;
        }
    }
};

Monitor for Endpoint Setup Errors

The SDK will send a SetupError message if an error occurred during endpoint setup. To listen to for a SetupError, use an InboundMessageObservable of type SetupError. The InboundMessageObservable class listens for the specified inbound message type and triggers the provided Observer when a matching inbound message is received.

//Create an Observer to be notified when a SetupError occurs
private final Observer mSetupErrorObserver = new Observer() {
    @Override
    public void changed() {
        SetupError setupError = mSetupErrorObservable.get();
        //Take action on the endpoint setup error
    }
};

//Create an InboundMessageObservable to listen for SetupError messages and notify our Observer
InboundMessageObservable<SetupError> setupErrorObservable = new InboundMessageObservable<>(
        new SetupError(),
        BBMEnterprise.getInstance().getBbmdsProtocolConnector()
);
mSetupErrorObservable.addObserver(mSetupErrorObserver);

Monitor the GlobalSyncPasscodeState

When using the BlackBerry Key Management Service, your application must provide a passcode to the SDK during endpoint setup. The passcode could be obtained from the user or provided by the application. To recover existing security keys, your application must be able to provide the same passcode that was previously set.

When the GlobalSetupState is SyncRequired, the SDK requires a passcode to complete endpoint setup. The GlobalSyncPasscodeState should be checked to determine if you should provide a new passcode, or if current security keys exist that can be recovered by providing the existing passcode.

Passcode State Description
New When the GlobalSyncPasscodeState value is New, there are no security keys stored in the BlackBerry Key Management Service for this identity.
Existing

When the GlobalSyncPasscodeState value is Existing, there are security keys. Your application can recover them by providing the passcode that was previously set. Or, your application can set a new passcode, which will cause the old keys to be discarded and new ones to be generated and set.

To complete endpoint setup, your application sends SyncStart message with the appropriate action.

//Observes the GlobalSetupState and the GlobalSyncPasscodeState.
//When the required a passcode is sent to complete endpoint setup using the 'SyncStart' message.
private Observer mSyncPasscodeStateObserver = new Observer() {
    @Override
    public void changed() {
        GlobalSetupState setupState = BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalSetupState().get();
        //When the GlobalSetupState is 'SyncRequired' then send the passcode to the SDK to continue
        //endpoint setup
        if (setupState.state == GlobalSetupState.State.SyncRequired) {
            GlobalSyncPasscodeState syncPasscodeState =
                BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalSyncPasscodeState().get();
            //For simplicity, this example hard codes a passcode.
            //A passcode obtained from a user is a more secure solution.
            SyncStart syncStart = new SyncStart("user-passcode");
            switch (syncPasscodeState.value) {
                case New:
                    //No existing keys were found, so send the SyncStart with action 'New'
                    syncStart.action(SyncStart.Action.New);
                    BBMEnterprise.getInstance().getBbmdsProtocol().send(syncStart);
                    break;
                case Existing:
                    //Existing keys stored in KMS were found, so send the SyncStart with action 'Existing'
                    syncStart.action(SyncStart.Action.Existing);
                    BBMEnterprise.getInstance().getBbmdsProtocol().send(syncStart);
                    break;
                default:
                    //No action
            }
        }
    }
};

BBMDS and Raw Protocol Events

BBMDS is the name of the JSON message passing protocol that's used inside the SDK on Android, iOS, and Linux. On Android, each SDK BBMDS message has an equivalent Java class that handles serialization to and from JSON. Usually, your application does not to work directly with BBMDS.

The BbmdsProtocol class contains the accessors to retrieve BBMDS objects and send BBMDS messages to the SDK. To listen for raw BBMDS messages register a ProtocolMessageConsumer with the ProtocolConnector.

//Example of sending a BBMDS message to mark a chat message as read
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatMessageRead("chatId", 1234));

//Example of requesting a chat from the BBMDS protocol, the Chat is returned as an ObservableValue
ObservableValue<Chat> chat = BBMEnterprise.getInstance().getBbmdsProtocol().getChat("chatId");