Developing Opportunistic Applications in Android

Setting up a new opportunistic application

Opportunistic applications are nothing more than regular Android applications that need to connect to the opportunistic engine provided by the HYCCUPS Tracer application. As such, to get started with working with opportunistic applications you need to do the following:

  1. Download the Opportunistic Layer library from the Downloads section
  2. Download the corresponding HYCCUPS Tracer application package file from the Downloads section and install it on your test device
  3. Open Android Studio and create a new Android application project
  4. Import the Opportunistic Layer library as a project dependency:
    1. Using a file navigator, go to the directory you downloaded the Opportunistic Layer library and copy/move it to the libs directory in your module’s root directory (e.g. app/libs).
    2. Right-click on your application project and open Module Settings (or press F4)
    3. Go to the dependencies tab
    4. Press the ‘+’ on the top right and select jar dependency
    5. Browse to the libs directory, select the library jar file and press OK

 

Declaring an opportunistic channel

To be able to connect to the opportunistic engine running as a part of the HYCCUPS Tracer, an application has to declare one (or more) opportunistic channels. As such, create a new class, i.e. MyChannel which inherits from Channel:


public class MyChannel extends Channel {
@Override
public String getName() {
return null;
}

@Override
public List getInterests() {
return null;
}

@Override
public void onPeerConnected(String deviceId, String userId) {

}

@Override
public void onPeerDisconnected(String deviceId, String userId) {

}

@Override
public void onMessageReceived(String sourceUserId, String destinationUserId, String message, long timestamp) {

}

@Override
public void onDisseminationReceived(String sourceUserId, String message, long timestamp, String[] tags) {

}
}

Each Channel functions as an Android Service and, as such, you need to declare it in the application’s manifest file:


The Intent Filter will allow the Opportunistic Engine to discover the opportunistic Channel you just created and will bind to it thus creating a continuous connection to it. As such, the Service hosting our channel must be exported to the outside world (hence exported being set to true). Furthermore, it’s a good practice to have the Channel running in a separate process, in case an exception occurs while communicating with the Opportunistic Engine, it will not bring the entire application down along with it.

For now, only implement the getName() method and provide a unique name of your choice:

@Override
public String getName() {
return "SuperAwesomeChannel";
}

The name provided by this method is used by the Opportunistic Engine not only for uniquely identifying your channel among others, but it is also used by the Engine to log debug messages corresponding to the channel. You can view these messages by inspecting logcat:

  1. In the command line:
    $ adb logcat | grep -i SuperAwesomeChannel
    05-13 13:47:06.179 16686 16686 I <b>Channel-SuperAwesomeChannel</b>: mHandler.connect()
    05-13 13:47:06.179 16686 16686 I <b>Channel-SuperAwesomeChannel</b>: mHandler.startDiscovery()
    05-13 13:47:06.179 16686 16686 I <b>Channel-SuperAwesomeChannel</b>: mHandler.requestName()
    05-13 13:47:06.179 16686 16686 I <b>Channel-SuperAwesomeChannel</b>: mHandler.bindSession()
    
  2. In Android Studio’s Monitor perspective:png;base647c71a7f1527aead2Make sure to use the Channel’s name as a filter and set the filters to “No Filters” so they can collect the HYCCUPS Tracer’s logs pertaining to your channel.

Managing opportunistic peers

The first step in developing opportunistic applications is that of determining interactions with other peers. In HYCCUPS, peers are identified using:

  1. User ID: identifies the user that we are interacting with. The user information is derived by the HYCCUPS Tracer based on the federated identity providers used to log in (i.e. Google+, Facebook).
  2. Device ID: identifies the device the user was signed in on. Given that a person can have multiple hardware devices, they can also be logged in as such on all devices. Therefore, the same User ID can be associated with multiple Device IDs.

In HYCCUPS, all IDs are anonymized and uniquely identify both users and their devices. In case you want to provide your own social information, instead of using the identities provided by the Opportunistic Engine, please check out Using Custom Social Information.

That being said, each opportunistic channel tracks peer interactions and uses two callbacks that you can implement in order to handle these events:

@Override
public void onPeerConnected(String deviceId, String userId) {

}

@Override
public void onPeerDisconnected(String deviceId, String userId) {

}

You can override these methods if you need to expose such information to the users of your applications. As an example, you can implement these methods to tap into these events and update your UI showing which users are currently connected. Furthermore, these two callbacks can be used to determine the IDs of the users you interact two in order to build up a database of users you can send messages to. If you are using the default social information provided by the Opportunistic Engine, you will have no other way of discovering peers other than those you interact with.

Sending opportunistic messages

There are two types of message passing operation in opportunistic networking:

  1. Data forwarding: a message has specified source and destination
  2. Data dissemination: a message has a specified source, but not a specified destination. As such, the message will be distributed to all peers interested in that message. Interests are specified as a set of tags used in this distributed publish/subscribe mechanism.

In order to send a message you need to create a Connection based on the application Context and the channel’s unique name:

Connection connection = new Connection(getApplicationContext(), "SuperAwesomeChannel");
connection.forward(userId, "This is a message for " + userId);
connection.disseminate("this is a message to all interested in 'music'", new String[] { "music" });

You don’t need to preserve a Connection after using it, you can simply create new ones on a per-use basis.

Receiving opportunistic messages

While a Connection is used for sending operations, the Channel itself is responsible for receiving messages from peers. As such, you will have to implement the following two callbacks:

  1. Receiving forwarded messages:
    @Override
    public void onMessageReceived(String sourceUserId, String destinationUserId, String message, long timestamp) {}
    

    Where sourceUserId is the ID of the user that sent the message, destinationUserId is the ID of the user that received the message (in this case the current user’s ID), message is the actual content of the forwarding operation, and timestamp is the time the message was generated expressed in UNIX time (milliseconds since 1970).

  2. Receiving disseminations:
    @Override
    public void onDisseminationReceived(String sourceUserId, String message, long timestamp, String[] tags) {}
    

    Where sourceUserId is the ID of the user that sent the message, message is the actual content of the forwarding operation, timestamp is the time the message was generated expressed in UNIX time (milliseconds since 1970), and tags is an array of interests that were used to tag the dissemination.

Furthermore, in order to receive disseminations, you must also implement an additional callback that notifies the Opportunistic Engine about the tags the your application’s user is interested in subscribing to in the dissemination process:

@Override
public List getInterests() {
return new ArrayList() {{
add("music");
add("movies");
add("sports");
}};
}

This can either be a constant list, or you can maintain it in a dynamic fashion. In case your application’s users want to customize this list you will have to call notifyInterestsChanged() by using a Connection. This will notify the Opportunistic Engine to reload the interests from your Channel in order to adapt to such changes.

Advanced topics

Using Custom Social Information

In case you want to override the Opportunistic Engine’s anonymized user information, and provide you own user social information provider, you can do that by overriding a method in your Channel’s implementation:

@Override
public SocialInfo getSocialInfo() {
String id = getTheCurrentUserID();
List friendIds = getTheCurrentUserFriendIDs();
return new SocialInfo(me.getId(), friends);
}

You will only have to return user IDs for both the currently signed in user, as well as for all of the user’s friends (if any). In case the friends list is modified dynamically you will have to call notifyFriendsChanged() by using a Connection.

Furthermore, after the user logs in you must make a call to register() by using a Connection, and after the user logs out, you have to call unregister() to inform the engine not to forward any messages to your Channel.

Troubleshooting

Given that the Opportunistic Engine runs in a different application other than your own, the OS can decide to terminate its process, case in which each Channel registered to it will be notified. In order to handle this event, you must override the following method:

@Override
public void onDisconnected(String error) {

}

Pre-filling Opportunistic Queues

Generally, the Opportunistic Engine will bind to the process that is hosting your Channel and will keep it alive. However, there are certain cases in which the OS can device to terminate the process running your Channel. Furthermore, there are cases in which the process hosting the Opportunistic Engine will cease to exist and will get restarted. In either cases, the Channel offers the means for pre-populating the message queue of the Opportunistic Engine with messages that have been previously generated by your Channel. This can be achieved by overriding the following method:

@Override
public void preFillQueue(int maxForwardMessages, int maxDisseminateMessages) {
fillForward(destinationUserId, message, timestamp);
fillDisseminate(message, timestamp, tags);
}

This callback is used by the Opportunistic Engine to let your Channel know that it would like to prefill its queues with a maximum number of forwarded messages, respectively a maximum number of disseminations. In order to fill the queues, use the Channel’s fillForward and fillDisseminate methods which have the same signatures as the Connection’s methods; in case you exceed the maximum count provided by the Engine, these messages will be ignored.

Downloads

Release 5.1

API reference Link
HYCCYUPS Tracer APK Download
Opportunistic Layer library JAR Download