Introduction

This guide provides a comprehensive description of plug-in development in Dynamix 2. We’ll be covering all of the major facets of plug-in development by walking step-by-step through the creation of a fully featured example. If you’re interested in creating Dynamix-based apps, check out our Android app quickstart and our Web app quickstart. Before starting out, it’s recommended to first read the Dynamix 2 Overview.

To generate an example plug-in, follow the Maven-based guide at the following URL: https://bitbucket.org/dynamixdevelopers/maven-archetype-dynamix-plugin-quickstart/wiki/Home

Plug-in Overview

First, let’s take a quick look at how plug-ins are used within Dynamix.

  1. The Dynamix Framework is installed once on the end-user’s mobile device. After the Dynamix Framework starts (as an Android Service), native apps and Web apps can register for context support using Dynamix’s APIs. Dynamix supports many apps simultaneously, each with individual settings and permissions.
  2. Each Context Plug-in defines a set of context types that it supports, along with rich contextual information that can be sent to apps using both unicast and broadcast events.
  3. Apps request context support from a Dynamix instance, and Dynamix automatically discovers and installs the plug-ins required to handle the request. Plug-ins may be hosted in public, private or file-system repositories.
  4. Users have complete control over which plug-ins are installed through the Dynamix Context Firewall, which pops up over the top of requesting application. By default, plug-ins have no permissions.
  5. When a plug-in is approved for installation, its OSGi Bundle holding its code (and any associated libraries and assets) is downloaded and installed into the Dynamix’s instance at runtime. The installation process is automatic and seamless, and neither Dynamix nor the app needs to be restarted. The plug-in is cached locally, so that future requests can be handled quickly.
  6. After the Context Plug-in is verified, the Context Plug-in is instantiated by the Dynamix Framework and then started to handle the requested context support.
  7. Once started, plug-ins provide the actual low-level capabilities required to allow apps to fluidly interact with the user’s environment (by sensing rich context information, controlling remote devices, etc.)
  8. Dynamix continues to operate in the background, potentially downloading additional Context Plug-ins, which all operate in parallel using the same mechanisms described above.

Plug-in Life-cycle

Plug-ins are installed into a Dynamix instance (generally running on the end-user’s mobile device) in response to an app’s request for context support or in response to a manual installation triggered by the user. As plug-ins are units of executable code that add extra functionality to a Dynamix instance, they must adhere to a strict life-cycle. As such, all Context Plug-ins must extend the ContextPluginRuntime base class (part of the plug-in SDK JAR) and implement its abstract methods (e.g. init, start, stop, destroy, etc.). This enable Dynamix to instantiate and control the plug-in during runtime. Plug-ins that do not extend this base class cannot be loaded by Dynamix.

Summary

  1. When the Context Plug-in is first instantiated by Dynamix, its state is NEW, meaning that it holds no resources and does not have an execution thread.
  2. During runtime, the Dynamix Framework initializes each plug-in. During initialization, a plug-in prepares internal state (possibly using incoming settings) and acquires the resources it needs to run. Plug-ins should not run at this point; rather, they should wait to perform context interactions until their ‘start’ method is called. If a plug-in initializes without exception, its state becomes INITIALIZED.
  3. After a plug-in is initialized, the Dynamix Framework may start the plug-in at any time, which indicates that context sensing or control services should be started. If the plug-in starts without an exception, its state becomes STARTED. The runtime behavior of a started plug-in is implementation specific.
  4. After a plug-in is started, Dynamix may stop the plug-in at any time, which indicates that all operations should be stopped as quickly as possible, but acquired resources should be maintained. When the plug-in stops, its state reverts back to INITIALIZED.
  5. Plug-ins may be restarted at any time from state INITIALIZED, reverting their state to STARTED.
  6. At any time, Dynamix may destroy the plug-in, meaning that the plug-in should stop running and release any acquired resources in preparation for garbage collection. After this call, the plug-in’s state becomes DESTROYED.

Discussion

Initialization

Once a Context Plug-in’s runtime has been dynamically instantiated by the Dynamix Framework, it is then initialized, whereby it acquires the resources necessary for it to run. Initialization occurs when Dynamix calls the plug-in’s init method, which occurs when Dynamix starts, a plug-in is installed, or a plug-in is activated after being disabled. If initialization is successful, the runtime should simply return from the init method without generating an exception. If initialization fails, the runtime should attempt to release any resources and then throw an exception. The first parameter of the initialization method is a PluginPowerScheme, which indicates the current power management state of the Dynamix Framework. The second parameter of the initialization method is a ContextPluginSettings object, which can be used by the plug-in to setup state based on stored configuration settings. The runtime will receive the last ContextPluginSettings object stored, or null if no settings have been stored for the runtime.

Starting and Stopping

After a Context Plug-in has been successfully initialized, it may be started by the Dynamix Framework at any time, whereby the runtime should begin performing its context sensing and/or device control functions. Dynamix starts a plug-in by calling the its start method on a dedicated thread, meaning that the method may block if necessary. The behavior of the start method varies, depending on the plug-in type. After start is called, plug-ins may begin performing context sensing, acting and event generation as necessary. Some plug-ins may simply operate autonomously without requiring programmatic interacitons from apps, generating context events as needed. For other plug-ins, start indicates that the plug-in should prepare to handle incoming context requests, which arrive through its contextRequest and configuredContextRequest methods (described shortly).

Dynamix may stop runtimes at any point, indicating that the runtime should immediately stop ongoing context interactions, but not release acquired resources or clean up state. Dynamix stops the plug-in by calling its stop method on a dedicated thread, meaning that it can block if necessary. Note that until destroy is called, a runtime may be started again at any time with another call to its start method.

Destruction

During destruction, a runtime must release any acquired resources, clean up internal state (e.g. save state) and prepare for garbage collection. Dynamix destroys Context Plug-ins by calling the associated runtime’s destroy method. Once destroyed is called, a plug-in instance will not be called again (although a new instance may be created and initialized). Note that Dynamix may possibly initialize and and destroy Context Plug-ins many times during typical usage.

Defining Context Support

As a plug-in developer, it’s up to you to decide how your plug-in should behave once ‘start’ has been called. Given the power of the Android platform, the number of libraries that can be deployed with your plug-in, and the features of the runtime base class, you have considerable flexibility in this regard. Some plug-ins operate autonomously in the background, sensing context information and sending results to all registered listeners. For example, our pedometer plug-in automatically broadcasts step events and associated step force to all apps holding a context subscription. Other apps wait for apps to trigger an operation programmatically. For example, the text-to-speech plug-in waits for apps to make a request for results and then returns results directly to the calling app.

The first step in designing a plug-in is to decide on the context support that it will provide. As described in the Dynamix 2 Overview: Apps perform interactions with their environment by requesting context support from a Dynamix instance. Context support represents a specific type of computational functionality provided by one or more Dynamix plug-ins, such as the ability to sense light levels, discover media devices or control a drone helicopter. Context support is requested by apps using a context type, which is a unique, string-based identifier that is generally written in the reverse domain name format. For example, the string ‘org.ambientdynamix.contextplugins.pedometer’ can be used to request context support for detecting user steps and step force (for determining walking vs. running).”

The example plug-in supports two context types, each with a different runtime behavior. These context types are summarized as follows:

  1. org.example.dynamixplugin.batterylevel: Automatically broadcasts battery state information whenever the device’s battery level changes. Returns the current battery state when programmatically requested.
  2. org.example.dynamixplugin.commandtest: Demonstrates a simple command API for your plug-in using an Android Bundle for configuration.

Defining Context Information

Next, it’s time to define the type of context information your plug-in will provide to apps. You may define as many custom data types as necessary using the Plug-in SDK’s IContextInfo interface. This interface provides basic metadata for plug-in events (e.g., the context type and validity period), and also extends android.os.Parcelable, which requires you to provide the serialization and deserialization logic necessary to pass the context representation object across Android process boundaries. Don’t worry, it’s pretty easy to write a Parcelable class. Just follow the example below.

Example IContextInfo Implementation

Here, we create an implementation of the IContextInfo interface, called the BatteryLevelInfo class. We start be defining the class and implementing the required public CREATOR field that generates instances of your Parcelable class from a Parcel. Don’t forget to implement the CREATOR field, or apps will not be able to use your data!

Next, we implement the methods of the IContextInfo interface, as shown below.

Next, we define as many custom properties as needed, using JavaBean getter conventions.

Recall that the IContextInfo interface extends android.os.Parcelable. As such, IContextInfo implementation must provide mechanisms for serializing and deserializing data (note that the object’s data must be written and read in the same order, as shown below). The Android

Web App Support

By using JavaBean getter conventions for the data exposed by your IContextInfo implementations, Dynamix will be able to automatically serialize your object into a Web-friendly representation (JSON). All data exposed via JavaBean conventions will be available to Web apps as properties that can be accessed using dot notation. These properties are generated according to the following simple rules:

  • Remove each getter method’s ‘get’ prefix and trailing parentheses ‘()’.
  • Convert the result to camel case.
  • Access directly on the context result object.
  • For example: To access the battery level in a Web agent, apps can simply call event.batterylevel.

IContextInfo JavaDocs

Defining the Runtime Class

Now that we have our context types and context information defined, we can now define our plug-in’s runtime class. This class is what Dynamix  instantiates first after our plug-in has been downloaded and installed. In our example, we begin by extending the ContextPluginRuntime as follows.

Next, we implement the ContextPluginRuntime’s abstract life-cycle methods as follows.

We also define a BroadcastReceiver to receive ongoing events from Android about battery level changes.

ContextPluginRuntime JavaDocs

Broadcasting Events

As shown in the code above, the example plug-in broadcasts events to all apps that have registered for “org.example.dynamixplugin.batterylevel” context support in its ‘start’ method and in its ‘batteryLevelReceiver’. Plug-ins may broadcast events at any time, whenever they are in the STARTED state by using the ‘sendBroadcastContextEvent’ method of the ContextPluginRuntime base class, as shown below.

There are several variants of this method, which fall into two basic categories.

  1. Broadcasts that include an IContextInfo implementation. These broadcasts include the custom data types provided by the plug-in, and may include a validity duration (in milliseconds). If the validity duration is omitted, the data is considered valid forever. In the example, we send events of the previously defined BatteryLevelInfo, which implements IContextInfo. Client apps must have the implementing IContextInfo classes on their build path to use the data.
  2. Broadcasts that include an Android Bundle, context type string, and (optional) validity duration (in milliseconds). This is a utility event system provided by Dynamix, which allows plug-ins to send data to apps using an Android Bundle and context type string. Again, if the validity duration is omitted, the data is considered valid forever.

Handling Context Requests

Plug-ins can be configured to respond to programatic requests from apps by overriding handleContextRequest and handleConfigureContextRequest methods of the ContextPluginRuntime base class. If overriden, these methods provide a mechanism whereby the plug-in can expose an API to apps.

In the simplest case, the plug-in can simply override handleContextRequest and check for a specific context type. The example plug-in provides an example of this, reacting to context requests with the type “todo” by returning the current battery state to the caller, as follows.

As shown above, the contextType indicates which type of context interaction the calling app wishes to invoke. In this case, the plug-in only checks for the battery level; however, some plug-ins may support multiple context types. The method also receives a requestId argument, which should be used as the responseId when returning results to the caller. Events generated this way are unicast (sent to only one receiver… the calling app).

When sending context events in response to context requests, the plug-in MUST use the ‘sendContextEvent’ method, including the incoming requestId as the responseId. In this case, events are not broadcast to all registered apps; rather, the event is send specifically to the requesting app.

The plug-in should be designed so that is can handle multiple context requests (each with a distinct requestId) simultaneously, being able to quickly cancel outstanding requests should stop() or destroy() be called.

Some plug-ins may also support configured context requests, which enables the app to control the context interaction using a plug-in-defined config Bundle (i.e., an Android bundle with pre-defined key value pairs for arguments). The Bundle arguments available for configured context requests must be clearly described in the plug-in’s documentation. This example doesn’t support ‘handleConfiguredContextRequest’ calls.

Interacting with Dynamix and Android

The ContextPluginRuntime provides several methods for interacting with Dynamix and Android more generally. Most of these interactions are handled by calling superclass methods on the ContextPluginRuntime extended by the plug-in.

SecuredContext

The getSecuredContext method can be used by runtimes to interact with a secured version of the Android Context that is customarily used to interact with the Android platform. SecuredContext extends android.content.Context and is customized with a set of permissions that are controlled by the user. These permissions allow the user to specify which parts of the Android platform are available to each Context Plug-in. As an example, the SecuredContext’s getSystemService(String) method may allow one particular Context Plug-in access to the Android platform’s LOCATION_SERVICE, but disallow access to the POWER_SERVICE. Most Android system services are potentially available through this method; however, there are several important exceptions:

  1. The SensorManager is NOT available directly; rather, an org.ambientdynamix.api.contextplugin.security.SecuredSensorManager is returned in response to getSystemService(Context.SENSOR_SERVICE);
  2. The SpeechRecognizer is NOT available directly; rather, a org.ambientdynamix.api.contextplugin.security.SecuredSpeechRecognizer is returned in response to getSystemService(“android.speech.SpeechRecognizer”);

Storing and Retrieving Settings

Many Context Plug-ins require configuration before they can be used. To support configuration, each runtime is able to store and retrieve a ContextPluginSettings object, which is a HashMap of strings that are stored on behalf of ContextPlugins by the Dynamix Framework, and are provided to runtimes during initialization and on-demand via the IPluginFacade’s getContextPluginSettings method. If user interaction is required to acquire settings, the runtime’s View factory can be set to an Android-based user interface designed to help users capture settings.  Runtimes can call storeContextPluginSettings to store settings at any time, and setPluginConfiguredStatus to indicate whether or not they are properly configured. Runtimes that are not configured will not be started by the Dynamix Framework; however, once runtimes set their configured status to true, they will be automatically started, if needed.

Plug-in Instantiation

Since the Dynamix Framework downloads and installs Context Plug-ins during runtime, the Plug-in SDK provides factory mechanisms that allow plug-in instances (and plug-in user interfaces) to be dynamically created. Context Plug-in instances are created by Dynamix using an ContextPluginRuntimeFactory, which is extended by the Context Plug-in developer. The ContextPluginRuntimeFactory is configured by specifying the ContextPluginRuntime to be created in its constructor. Optionally, two additional user interface classes may also be specified, which are used for configuration and context acquisition. Configuration of the ContextPluginRuntimeFactory is completed using its constructor, which is shown in the example below.

In this case, the plug-in configures the factory by specifying the runtime class to be created (PluginRuntime.class). If the plug-in provides user interfaces for either context request handling or configuration, they can be similarly specified; however, this example does not show this functionality.

Plug-in Exporting

In order for a Context Plug-in to be integrated within a Dynamix Framework instance, it must be compiled as an Android 2.2 (and higher) application and packaged as a Dynamix-configured OSGi Bundle. As Context Plug-ins execute within the Android runtime, they must be compiled into the compact Dalvik Executable (.dex) format, which is optimized for resource-constrained systems. All referenced external libraries must also be compiled into the Dalvik Executable (.dex) format and included within the Context Plug-in’s Bundle as well.

To simplify the plug-in build and debug process, we recommend that all plug-in developers follow the Maven-based plug-in development guide at this URL: https://bitbucket.org/dynamixdevelopers/maven-archetype-dynamix-plugin-quickstart/wiki/Home 

Plug-in Exporting Details

Although the Maven build process linked above handles the packaging and exporting of Dynamix plug-ins automatically, this section provides some insight into this process, which can be helpful when problems arise.

Note the following:

  • Context Plug-ins are able to load classes and libraries contained within their Bundle JAR (and any embedded JARs); however, Android-based layout resources, graphics, and associated (via R.java) will not be available for layout inflation because of Android class-path issues that arise due to OSGi.   An overview of a typical Eclipse Context Plug-in development structure is shown  below.
  • The MANIFEST.MF file exists within a META-INF folder. This directory structure must be maintained in the Bundle JAR.
  • Each concrete IContextInfo class (e.g. SamplePluginContextInfo.java) must have a corresponding ‘flag’ file, which is named exactly the same, but has an aidl file extension (e.g. SamplePluginContextInfo.aidl). This file should contain the fully qualified class name of the IContextInfo class as a parcelable statement (see sample code).

During runtime, the following classes will be provided to Context Plug-ins by the Dynamix Framework:

  • org.ambientdynamix.application.api
  • org.ambientdynamix.contextplugin.api
  • org.ambientdynamix.contextplugin.api.security

As such, these classes should not be included within the Context Plug-in’s Bundle JAR. Aside from these packages, however, each Context Plug-in must be entirely self-contained (i.e. it cannot reference external libraries) and must be packaged within a single OSGi –compliant Bundle JAR, according to the Dynamix-specifications described in Table 19.

OSGi Metadata

Context Plug-ins must be packaged as a Dynamix-configured OSGi Bundle[1]. Briefly, an OSGi Bundle is a group of related Java classes (and other resources, such as software libraries or images), which are packaged within a standard Java JAR with additional OSGi metadata (included within the Bundle’s MANIFEST.MF).  Dynamix Context Plug-ins must be specified using the Dynamix-specific OSGi Bundle Manifest Specification shown below.

OSGi Entity Specification
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: The text-based name of the plug-in.
Bundle-Vendor: The text-based name of the plug-in vendor.
Bundle-SymbolicName: A globally unique plug-in identifier, which must match the plug-in id specified in its associated Context Plug-in Description Document. This identifier is defined using a hierarchical naming pattern, with levels in the hierarchy separated by periods. In general, a plug-in identifier begins with the top level domain name of the organization and then the organization’s domain and then any sub-domains listed in reverse order. Plug-in identifier should be lowercase.
Bundle-Version: The version of the plug-in, using a <major>.<minor>.<micro> scheme.
Export-Package: The Context Plug-in’s exported packages.
Import-Package: The Context Plug-in must import org.ambientdynamix.application.api, org.ambientdynamix.contextplugin.api and org.ambientdynamix.contextplugin.api.security.

Each Context Plug-ins must provide a properly structured OSGi bundle manifest file under the location META-INF/MANIFEST.MF. The battery level plug-ins MANIFEST.MF file is shown below.

Data Type(s)

To enable app developers to utilize your plug-in’s data in their native applications, Maven exports your custom data type classes as follows:

  1. Package classes, interfaces, and related classes as a standard JAR, not an OSGi Bundle (i.e., no metadata is necessary).
  2. Only include the classes related to IContextInfo objects, including their primary interface(s) and any .aidl files.

When publishing your plug-in, be sure to make the data type JARs available to app developers for download. To access your plug-ins data, app developers must download this JAR, include it their app’s build path, and make sure that the data type JAR is exported with their project.

Example Plug-in Descriptor

Plug-ins are described within a Dynamix repository using an XML document (or snippet) called a Context Plug-in Description (CPD). An example CPD is shown below:


With reference to the example Context Plug-in Description document shown above, note the following:

  • The document provides information about a single Context Plug-in named Battery Level Plug-in. The document could describe many Context Plug-ins in this way, each described similarly within the ‘contextPlugins’ XML element.
  • The document provides a variety of metadata, such as id, name, version, target platforms, etc. The id must match the id specified in the MANIFEST.MF.
  • ‘minPlatformVersion’ refers to the minimum Android version that this plug-in is compatible with.
  • ‘minFrameworkVersion’ refers to the minimum Dynamix Framework version that this plug-in is compatible with.
  • The document provides the fully qualified class-name of its factory class: org.ambientdynamix.contextplugins.batterylevel.PluginFactory.
  • The CPD includes additional details, such as the plug-in’s supported context information types, fidelity levels, required permissions, install URL, etc.

Debugging

To debug your plug-in, please follow the guide here.