Android SDK

Introduction

These pages provide documentation for Namespaces, Classes, Functions, and Variables within the MobiledgeX Android SDK.

Navigating the SDK Documentation

Use either the tabs or the treeview to find the desired page. There are two main sections: Classes and Modules. Classes will bring you to a list of all classes in the Android MobiledgeX SDK. Modules will bring you to a more organized breakdown of the Android SDK. The Modules are split up into Classes , Function Groups , and Exceptions . Each of these modules group similar classes, functions, or exceptions together. For example, all of the MatchingEngine API functions will be found under Modules -> Functions Groups -> MatchingEngine APIs.

Where to Start?

The main class that developers will be using is the com.mobiledgex.matchingengine.MatchingEngine class. This class provides functions to register the user to the Distributed Matching Engine, find the nearest application instance, and then get a connection to that application instance that is ready to be used (See diagram below for workflow). Go to the sections: MatchingEngine APIs and GetConnection Functions to get started.

How To

Project Setup

Create or open an existing Android Studio project.

Add these definitions to your project's top most build.gradle file. In the same directory, create a local.properties file with the artifactory username and password you use to log into https://console.mobiledgex.net. Create a login there, if you haven't already.

Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def artifactory_user = properties.getProperty("artifactory_user")
def artifactory_password = properties.getProperty("artifactory_password")

project.ext.mobiledgeXContextUrl = "https://artifactory.mobiledgex.net/artifactory"
project.ext.debugRepoKey = "maven-development"
project.ext.releaseRepoKey = "maven-releases"
project.ext.repoKey = "${debugRepoKey}"
project.ext.grpcVersion = '1.32.1'

Define a repository to pull these depedencies in, using the top most build.gradle file:

allprojects {
    apply plugin: 'com.jfrog.artifactory'
    repositories {
        maven {
            credentials {
                // Create these variables if you don't have them.
                username artifactory_user
                password artifactory_password
            }
            url "${mobiledgeXContextUrl}/${repoKey}"
        }
        mavenLocal()
        google()
        jcenter()
    }
}

Add these depedencies to your app's build.gradle file:

implementation 'com.mobiledgex:matchingengine:3.0.0'
implementation 'com.mobiledgex:mel:1.0.11'
implementation 'com.google.guava:guava:29.0-android'

// Dependencies of Matching Engine:
implementation "io.grpc:grpc-stub:${grpcVersion}"

implementation "io.grpc:grpc-okhttp:${grpcVersion}"
implementation "io.grpc:grpc-protobuf-lite:${grpcVersion}"

With the dependencies defined, let Android Studio pull in the depedencies. To get started using MobiledgeX, you need a MatchingEngine instance. Since MatchingEngine needs location permissions, this might be reinitialized onResume():

Setup MatchingEngine

MatchingEngine me = new MatchingEngine(context);

For privacy reasons, there is a flag the application should ask the user for permission before enabling, concerning location usage. This is in addition to normal operating system permissions:

MatchingEngine.setMatchingEngineLocationAllowed(matchingEngineLocationAllowed);

Then, register, and find the first closest cloudlet. If not sure of your organization or appInst details, log in and view the app details here: https://console.mobiledgex.net

Optionally, If interested in dynamic edge migration, you can enable it:

// Register the class subscribing to EdgeEvents to the EdgeEventsBus (Guava EventBus interface).
me.setEnableEdgeEvents(true); // default is true.

Setup EdgeEvents

Then, attach an EdgeEvents subscriber, described later in this document:

mEdgeEventsSubscriber = new EdgeEventsSubscriber();
me.getEdgeEventsBus().register(mEdgeEventsSubscriber);
// set a default config.
// There is also a parameterized version to further customize.
EdgeEventsConfig backgroundEdgeEventsConfig = me.createDefaultEdgeEventsConfig();
backgroundEdgeEventsConfig.latencyTestType = NetTest.TestType.CONNECT;
// This is the internal port, that has not been remapped to a public port for a particular appInst.
backgroundEdgeEventsConfig.latencyInternalPort = 0; // 3765; // 0 will favor the first TCP port if found for connect test.
// Latency config. There is also a very similar location update config.
backgroundEdgeEventsConfig.latencyUpdateConfig.maxNumberOfUpdates = 0; // Default is 0, which means test forever.
backgroundEdgeEventsConfig.latencyUpdateConfig.updateIntervalSeconds = 7; // The default is 30.
backgroundEdgeEventsConfig.latencyThresholdTrigger = 186;

Note that there are 2 configurations set. A application should use only one config, after deciding between performance testing (latencyUpdateConfig), or proximity based (locationUpdateConfig) newCloudlet updates. To use LocationUpdateConfig, where server pushes newCloudlet updates, simply set latencyUpdateConfig = null.

Once defined, you can start monitoring with the following. Monitoring starts when the first successful FIND_FOUND FindCloudlet Reply is returned in the next few lines of code.

me.startEdgeEventsFuture(backgroundEdgeEventsConfig);

Register and FindCloudlet

Many of these methods have asynchronous Futures versions. The blocking versions below should only be run inside something like a CompletableFuture so that the UI thread is not stuck waiting for a network response. The SDK will also print warnings if running on the UI thread. If you don't have a location service running, see the "Location Callback Example" section.

Create a register client request:

AppClient.RegisterClientRequest registerClientRequest = me.createDefaultRegisterClientRequest(context, organizationName).build();

Register, with a sane timeout, like 5 seconds.

registerReply = me.registerClient(registerClientRequest, GRPC_TIMEOUT_MS);

Create a default FindCloudlet Request:

AppClient.FindCloudletRequest findCloudletRequest = me.createDefaultFindCloudletRequest(context, location)
.build();

FindCloudlet, with a sane timeout, like 5 seconds.

findCloudletReply1 = me.findCloudlet(findCloudletRequest, GRPC_TIMEOUT_MS);

Connect to the Edge AppInst

To connect to the edge server instance, use something similar to the following:

if (findCloudletReply1.getStatus() == AppClient.FindCloudletReply.FindStatus.FIND_FOUND) {
// The edge server host and port can be constructed with the following utility code:
String host = me.getAppConnectionManager().getHost(findCloudletReply1, internalAppInstPortNum);
int port = me.getAppConnectionManager().getPublicPort(findCloudletReply1, internalAppInstPortNum);
Log.i(TAG, "Host: " + host + ", Port: " + port);
}

EdgeEventsSubscriber Template

Here is the EdgeEventsSubscriber class mentioned near the start of the document. The following example code is a Guava EventBus type Subscriber to EdgeEvents. onMessageEvent can be any name as the EventBus will forward it to the matching class type. The FindCloudletEvent variant is good to subscribe in your application. When recieved, the app should save state, and move to the next closer cloudlet.

// (Guava EventBus Interface)
// This class encapsulates what an app might implement to watch for edge events. Not every event
// needs to be implemented. If you just want FindCloudlet, just @Subscribe to FindCloudletEvent.
//
class EdgeEventsSubscriber {
// Subscribe to error handlers!
@Subscribe
public void onMessageEvent(EdgeEventsConnection.EdgeEventsError error) {
Log.d(TAG, "EdgeEvents error reported, reason: " + error);
someText = error.toString();
updateText(ctx, someText);
// Check the App's EdgeEventsConfig and logs.
}
// Subscribe to FindCloudletEvent updates to appInst. Reasons to do so include
// the AppInst Health and Latency spec for this application.
@Subscribe
public void onMessageEvent(FindCloudletEvent findCloudletEvent) {
Log.i(TAG, "Cloudlet update, reason: " + findCloudletEvent.trigger);
// Connect to new Cloudlet in the event here, preferably in a background task.
Log.i(TAG, "Cloudlet: " + findCloudletEvent.newCloudlet);
someText = findCloudletEvent.newCloudlet.toString();
updateText(ctx, someText);
// If MatchingEngine.setAutoMigrateEdgeEventsConnection() has been set to false,
// let MatchingEngine know with switchedToNextCloudlet() so the new cloudlet can
// maintain the edge connection.
}
// Subscribe to ServerEdgeEvents!
//
// Optional. If you set this event handler, the app will take ownership of raw
// events off the EdgeEventBus so a custom handler can be written in to fit your application
// use case better.
//
// To optionally post messages to the DME, use MatchingEngine's EdgeEventsConnection.
//@Subscribe
public void onMessageEvent(AppClient.ServerEdgeEvent event) {
someText = event.toString();
updateText(ctx, someText);
switch (event.getEventType()) {
case EVENT_INIT_CONNECTION:
System.out.println("Received Init response: " + event);
break;
case EVENT_APPINST_HEALTH:
System.out.println("Received: AppInst Health: " + event);
handleAppInstHealth(event);
break;
case EVENT_CLOUDLET_STATE:
System.out.println("Received: Cloutlet State event: " + event);
handleCloudletState(event);
break;
case EVENT_CLOUDLET_MAINTENANCE:
System.out.println("Received: Cloutlet Maintenance event." + event);
handleCloudletMaintenance(event);
break;
case EVENT_LATENCY_PROCESSED:
System.out.println("Received: Latency has been processed on server: " + event);
break;
case EVENT_LATENCY_REQUEST:
System.out.println("Received: Latency has been requested to be tested (client perspective): " + event);
handleLatencyRequest(event);
break;
case EVENT_CLOUDLET_UPDATE:
System.out.println("Received: Server pushed a new FindCloudletReply to switch to: " + event);
handleFindCloudletServerPush(event);
break;
case EVENT_ERROR:
Log.d(TAG,"Received: An edgeEvents error: " + event.getErrorMsg());
break;
case EVENT_UNKNOWN:
System.out.println("Received UnknownEvent.");
break;
default:
System.out.println("Event Received: " + event.getEventType());
}
}

Location Callback Example and Posting Updates for Cloudlet Updates

It is possible to directly feed location into the EdgeEvents connection to the server. If there is a closer edge server, the app will be notified about a better cloudlet. Location permissions is app controled, as the app UI controls permission prompts between onPause() and onResume() states, where the the user might remove location permisisons.

mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult == null) {
return;
}
String clientLocText = "";
mLastLocationResult = locationResult;
// Post into edgeEvents updater:
me.getEdgeEventsConnectionFuture()
.thenApply(connection -> {
if (connection != null) {
connection.postLocationUpdate(locationResult.getLastLocation());
}
return null;
});
for (Location location : locationResult.getLocations()) {
// Update UI with client location data
clientLocText += "[" + location.toString() + "]";
}
TextView tv = findViewById(R.id.client_location_content);
tv.setText(clientLocText);
}
};

To request android location service to run in the background, you need google play services, please read here: https://developer.android.com/training/location/request-updates#java

MatchingEngine Permission Utility

The application should prompt for location permissions, as well as read the network state for edge server state management. Here's example to check using com.mobiledgex.matchingengine.util.RequestPermissions. It has getNeededPermissions() to check current missing permissions. Be sure to add needed permissions to the app's AndroidManifest.xml file. Be sure to include INTERNET permissions.

mRpUtil = new RequestPermissions();
// As of Android 23, permissions can be asked for while the app is still running.
if (mRpUtil.getNeededPermissions(appCompatActivity).size() > 0) {
mRpUtil.requestMultiplePermissions(appCompatActivity);
}

The above asks for READ_PHONE_STATE, ACCESS_COARSE_LOCATION, and ACCESS_FINE_LOCATION.

Managing Resources

If the application onPause() is called, consider calling stopEdgeEvents() to free resources. Re-enable at onResume().

Managing Resources

When the app needs to quit or free resources, call close():

@Override
public void onDestroy() {
super.onDestroy();
if (me != null) {
me.getEdgeEventsBus().unregister(mEdgeEventsSubscriber);
me.close();
me = null;
}
mEdgeEventsSubscriber = null;
ctx = null;
}