Introduction

This guide provides an overview of plug-in development for Dynamix by walking step-by-step through the creation of a simple battery level detector plug-in. Before starting out, make sure to read the Dynamix Overview.

Downloads

Plug-in SDK

The Dynamix Plug-in SDK provides the foundational classes, documentation and samples necessary for domain experts to create a wide variety of plug-ins for the Dynamix Framework. The SDK is released under the same open-source license of the Dynamix Framework.
Downloads
Although this video is still relevant, the names of the context plug-in base classes have changed. Please see the documentation below for details.

Plug-in Basics

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

  1. The Dynamix Framework is installed on a mobile device. Once the Dynamix Framework starts (as an Android Service), applications can  register for context support.
  2. Through an update, application request, or as directed by capability analyses, Dynamix discovers a Context Plug-in to install, which is stored in a plug-in repository either in the network or on the device’s local filesystem.
  3. The OSGi Bundle holding the Context Plug-in’s code (and any associated libraries) is downloaded and installed into Dynamix’s embedded OSGi framework.
  4. After the Context Plug-in is verified, and the requisite permissions are granted by the user, the Context Plug-in is instantiated by the Dynamix Framework using a factory object provided by the Context Plug-in developer.
  5. After the Context Plug-in is instantiated, Dynamix initializes it, whereby the plug-in acquires all necessary resources and prepares its internal state. During initialization, Dynamix passes the Context Plug-in the current power scheme and (optionally) a settings object, which is used to setup saved state. (Note: The Dynamix Service provides private settings persistence for each Context Plug-in.)
  6. If the Context Plug-in successfully initializes, it may be started at some point by Dynamix. The actual start logic depends on the type of Context Plug-in involved.
  7. Once started, Context Plug-ins interact with the user’s environment and (optionally) use Dynamix’s predefined event utility methods to send the context events to the Dynamix Framework. Each Context Plug-in defines a set of context information types that are used to represent the modeled context information. Plug-ins tag sensed context information with metadata related to its privacy risk level.
  8. The Dynamix Framework caches incoming context events and then passes the events (and associated privacy risk levels) to its Context Firewall, which determines which applications will receive them. Only events that match a specific context event subscription and pass the Context Firewall privacy filters are sent to a registered application. (The user can change the privacy settings of the Context Firewall at any time.)

Dynamix continues to operate in this fashion, potentially downloading additional Context Plug-ins, which all operate in parallel using the same mechanisms described above.

Base Classes

The foundation of Context Plug-in development is the ContextPluginRuntime class (part of the plug-in SDK JAR), which provides a set of lifecycle methods a Context Plug-in implementation uses to interact with the hosting Dynamix Framework. The ContextPluginRuntime is the root of a hierarchy of classes, providing several lifecycle methods (e.g. init, start, stop, destroy, etc.) designed to enable Dynamix to control the Context Plug-in during runtime. Context Plug-in implementations do not extend the abstract ContextPluginRuntime class directly; rather, they extend a particular subclass of the ContextPluginRuntime, depending on their type. Currently, there are three basic types of ContextPluginRuntime, each with different behavior and utilities for sending events to the Dynamix Framework, as summarized in Figure 5.

Context Plug-in Type Description

AUTONOMOUS

Base class: AutoContextPluginRuntime

(formerly PushContextPluginRuntime)

Performs context interactions autonomously in the background, while (optionally) broadcasting context events to registered apps with appropriate Context Firewall policies. Example: pedometer.

REACTIVE

Base class: ReactiveContextPluginRuntime

(formerly PullContextPluginRuntime)

Performs individual context interactions in response to an app’s requests to do so. Example: speech-to-text.

INTERACTIVE

Base class: InteractiveContextPluginRuntime

Same as REACTIVE, but requires user interaction via a custom user interface provided by the context plug-in (e.g., to point a camera or input data). Example: barcode scanner.

Figure 5: Context Plug-in Types

Each base class provides appropriate context request handler methods (if needed) and relevant event sending mechanisms, which allow the plug-in to send context events to the Dynamix Service, which forwards events as needed to apps. The methods available for event sending vary by plug-in type. For example, for autonomous types, only broadcast events are supported, since events are sent to all apps holding context support and Context Firewall permissions. In contrast, for reactive plug-in types, only unicast events are supported, since context information is sent in response to app initiated context interaction requests. Note that combination types are also supported, and the plug-in SDK contains hybrid classes (e.g., AutoReactiveContextPluginRuntime, AutoInteractiveContextPluginRuntime) that contain a union of the features of each base class.

The JavaDocs for the ContextPluginRuntime base class are shown below:

Interacting with Android

Due to Dynamix security constraints, runtimes cannot directly access the local file-system, most hardware interfaces and most Android platform APIs. Rather, runtimes receive an IPluginFacade object during instantiation, which provides a secured mechanism for these purposes. Each time the plug-in is instantiated, its runtime is configured with a globally unique sessionId, which is to authenticate the runtime during secure interactions with the Dynamix Framework when making method calls using the IPluginFacade. The runtime’s getSessionId() method can be used to retrieve this id during runtime. An overview of the methods provided by the IAndroidFacade is shown in

The IPluginFacade’s  getSecuredContext method can be used by runtimes to interact with the Android platform (e.g. to request services, etc.). The return type of this method is a SecuredContext – 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 Life-cycle

A ContextPluginRuntime transitions through a series of defined states during its lifecycle, which indicate which methods that can be called on it and provides the Dynamix Framework management information. The Context Plug-in life-cycle is described as follows:

  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 by calling it’s ‘init’ method. During initialization, a plug-in prepares internal state (possibly using incoming settings) and acquires the resources it needs to run. Plug-ins SHOULD NOT perform context sensing or actuation services until started.
  3. After a plug-in is INITIALIZED, the Dynamix Framework may call its ‘start’ method, which initiates that context sensing or actuation services should be started. Calls to this method are provided their own thread and may block, if necessary. If the plug-in starts without an exception, its state becomes STARTED.
  4. After a plug-in is started, Dynamix may call the plug-in’s ‘stop’ method AT ANY TIME, which indicates that all context sensing and actuation services should be stopped as quickly as possible. When the plug-in stops, its state reverts back to INITIALIZED.
  5. At any time, Dynamix may call the plug-ins ‘destroy’ method, which indicates that the plug-in should stop running and release any acquired resources in preparation for garbage collection. Once destroyed is called, a plug-in instance will not be called again (although a new instance may be created and initialized). After this call, the plug-in’s state becomes DESTROYED.

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 acting 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, Autonomous Plug-ins should begin performing context sensing, acting and event generation as necessary. For Reactive Plug-ins, start indicates that the plug-in should prepare to handle incoming context requests, which arrive through its contextRequest and configuredContextRequest methods. For Interactive Plug-ins, start indicates that the plug-in should be prepared to handle context requests through its user interface.

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.

Creating the Battery Level Plug-in

To begin creating the example plug-in, we first determine its type. Since the plug-in should automatically detect battery level changes and react to app queries, we extend the AutoReactiveContextPluginRuntime base class, as shown below.

Next, we implement the basic plug-in lifecycle methods, including init, start, stop and destroy, as follows:

Representing Context Information

As context information is often highly specific to a particular context domain, the Open Plug-in SDK provides the IContextInfo interface, which enables developers to create arbitrarily complex context representations using POJOs. This interface extends android.os.Parcelable, which requires plug-in developers to provide the serialization and deserialization logic necessary to pass the context representation object across Android process boundaries. IContextInfo implementations are self describing; providing both the context type and fully qualified class-name of the implementation. Plug-in developers can create their own implementations of the IContextInfo interface, or implement an existing IContextInfo type provided by the Dynamix community. See the sample plug-in(s) provided in the SDK for details on creating custom data types. The JavaDocs for the IContextInfo interface is provided below.

In our example plug-in, we begin by defining the IBatteryLevelInfo interface as follows.

The IBatteryLevelInfo interface is used by apps to access the plug-in’s context information when receiving context events. Note that it is important for plug-ins to provide this type of context information interface in order to support Dynamix embedded mode.

Web App Support

To support Web-based applications, make sure your context information interfaces follow JavaBean getter conventions, since Dynamix is able to automatically extracts getters into dynamically populated JavaScript properties, which makes it simple for Web apps to access the data (similar to POJOs). The available JavaScript properties can be derived as follows:

  • Remove each method’s ‘get’ prefix.
  • Remove each method’s trailing parentheses ‘()’.
  • Convert the result to camel case.
  • Example: event.getSomeProperty() would be available in JavaScript as event.someProperty;

Implementing the IBatteryLevelInfo interface

Next, we create an implementation of the IBatteryLevelInfo interface, called the BatteryLevelInfo. We start be defining the class and implimenting the REQUIRED public CREATOR field that generates instances of your Parcelable class from a Parcel.

Note that the BatteryLevelInfo MUST implement the IContextInfo interface, as shown in the example below.

Since context events may be send across process boundaries, 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.

For each IContextInfo implementation, as corresponding AIDL file must be created, as shown below. This file tells Android that the referenced object is capable of being serialized/deserialized using Parcelable.

Generating Context Events

When start is called, the plug-in registers for ACTION_BATTERY_CHANGED events using the SecuredContext provided by Dynamix. If the plug-in has permission, battery level updates will automatically start arriving in the plug-in’s ‘batteryLevelReceiver’, as shown below. In response, the plug-in dispatches related context events using the ‘sendBroadcastContextEvent’ method defined by the AutoReactiveContextPluginRuntime plug-in base class. During context event generation, the plug-in wraps the incoming battery level Intent within a custom BatteryLevelInfo object (discussed shortly) that exposes the data to native apps and Web apps.

In the example above, the second argument of the ‘sendBroadcastContextEvent’ indicates how long the context information continued within the event is considered valid (60000 milliseconds in this case). If this argument is omitted, the context information is considered valid forever.

The plug-in also needs to provide a mechanism for apps to request the battery level programmatically. To accomplish this, the plug-in implements the ‘handleContextRequest’ and ‘handleConfiguredContextRequest’ methods, defined by the AutoReactiveContextPluginRuntime plug-in base class.

Both context request methods receive a requestId and a contextType as arguments. 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. 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. 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.

The context event is dispatched using a SecuredContextInfo object, which provides a mapping between the BatteryLevelInfo object and its associated PrivacyRiskLevel. Context events generated by plug-ins are tagged with specific privacy risk meta-data, according to the guidelines presented below.

Privacy Risk Level Description

Low

Context information is not personally identifiable and poses a low privacy risk.

Medium

Context information is potentially personally identifiable and poses a medium privacy risk.

High

Context information is potentially personally identifiable and poses a high privacy risk.

Highest

Context information is likely to be personally identifiable and poses the highest privacy risk.

Privacy Risk Level Guidelines

The decision of which PrivacyRiskLevel to apply to context information is handled by the plug-in developer, who should clearly state the mappings in the plug-in’s documentation. The privacy risk levels are used by the Dynamix Context Firewall when provisioning context events to apps based on security policies. For example, if an app has permission to access a plug-in’s context information tagged with PrivacyRiskLevel.MEDIUM, context information tagged with PrivacyRiskLevel.HIGH or PrivacyRiskLevel.MAX will NOT be provisioned to the app. Note that a plug-in may support more than one PrivacyRiskLevel (e.g., providing context events with both LOW and HIGH risk – not shown in this example).

Persistent Request Channels

By default, plug-ins may return a single event for a given context request (‘handleContextRequest’ or ‘handleConfiguredContextRequest’). In short, a requestId can be used once as a responseId by default. However, there may be cases when multiple events should be returned to an app for a given context request. For example, a media streaming plug-in may wish to send ongoing play-state information to an app that requests media support (e.g., start, track position and stop events) using the original requestId. In this case, it’s possible for plug-ins to open a persistent request channel for a given requestId, which enables multiple responses to a given request to be returned to the app, as long as the request channel remains alive. To keep a request channel alive, plug-ins must send and event through the channel (or manually ‘ping’ the channel) within the channel timeout period (currently 30 seconds). Note that sending an event through the channel automatically pings the channel. Every channel ping keeps the channel alive for another 30 seconds. To close the channel, the plug-ins should simply stop sending events and/or pinging it. An example of this functinality is shown below (from the SampleContextPluginRuntime class, which is available in the SampleContextPlugin within the Context Plugin SDK (see the downloads).

Note that persistent request channels may be used by plug-ins to provide customized levels of service to different apps based on configuration arguments. For example, a plug-in may provide activity recognition services to apps with multiple levels of accuracy, with each level dependent on specific settings for the device’s accelerometer and other settings (e.g., “game performance” or “low performance”). In these cases, the plug-in developer should specify the configuration arguments and associated keys to be provided in the configuration Bundle passed by apps to the ‘handleConfiguredContextRequest’. Once a configured request has been received by the plug-in, it can configure its context support for the given based on the arguments, open a persistent request channel for the app, and then continue to send events to the app using the opened channel (i.e., using the original requestId as the response Id).

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 (BatteryLevelPluginRuntime.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.

For maximum compatibility, keep your plug-in’s target platform as close to Android version 2.2 @ API level 8 as possible. If your plug-in relies on features of higher Android versions, make sure to specify the minimum level in the plug-in’s Context Plug-in Description (CPD) XML file (described below)

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.

eclipse-1

Example Eclipse Project Structure

Note the following:

  • 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.

When using Eclipse to package OSGi Bundles, make sure to uncheck ‘Skip packaging and dexing until export of launch’ in Eclipse’s Android/Build preferences, since this feature currently does not function correctly (see below for details).

uncheck_skip_packaging

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.

If your plug-in utilizes library JARs, they must be packaged and included in your project as described here.

Exporting Context Data Type(s)

To enable app developers to utilize your plug-in’s data in their native applications, you should export all classes related to the IContextInfo entities from your plug-in project as follows:

  1. Package your 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 your IContextInfo objects, including their primary interface(s) and any .aidl files.
  3. You may want to encode version information (e.g., v1.0.0) in the file name (not shown above).
  4. Make the data type JAR 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.

The data type export for the battery level example is shown below (note that the plug-ins runtime and factory are not included).

data_type_export

If your plug-in includes multiple IContextInfo implementations, any associated interfaces, or related classes, they must all be included in the data type JAR.

Deployment & Testing

Once a Context Plug-in has been compiled and packaged, it can be configured for deployment from a plug-in repository. There are several repository types supported by Dynamix, including file-system, web-server, and Maven; however, for testing, it’s probably easiest to use the target device’s file-system. A Dynamix plug-in repository is simply any accessible storage location that contains a plug-in’s Bundle JAR and an associated Context Plug-in Description (CPD) XML file.


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 currently supported pluginType(s) are: AUTO, REACTIVE, INTERACTIVE, AUTO_REACTIVE, AUTO_INTERACTIVE, AUTO_REACTIVE_INTERACTIVE;
  • 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.