Skip to end of metadata
Go to start of metadata

 

Introduction

The RAMP virtual machines (VMs) can be extended by writing "plug-ins" that can be called from RAMP script or displayed from UXML. This allows RAMP VMs to be extended to expose any feature provided by the underlying mobile OS.

There are two types of plug-ins:

  • functions, for example plug-ins that send SMSs, use bluetooth, access GPS, play multimedia etc
  • UI components, for example components that are only available on a specific mobile OS or a unique user created component.

Deploying a RAMP VM with plug-ins involves three steps:

  • Download plug-in library
  • Writing the plug-in
  • Deploying the plug-in on the RAMP deployment platform

Download Plug-in Library

Each platform has its own version of the plug-in library:

Writing a RAMP VM Plug-in

Writing a function plug-in

Writing a function plug-in involves extending the (abstract) APluginFunction class. The APluginFunction class has two constructors that both require paramaters:

  • The first constructor takes a single argument which is the name of a function that the plug-in exposes (e.g. "vmt_sendSms").
  • The second constructor takes an array of Strings; this constructor should be used if the plug-in exposes more than one function. E.g. an audio player might expose functions called "companyID_nextTrack", "companyID_mute", "companyID_setVolume", "companyID_fastForward", "companyID_pause", etc.

The plug-in must implement the abstract invoke method which takes two arguments:

  • A (String) function name that identifies the function that is being invoked. The function name must start with the Company ID of the RAMP account that creates the plugin followed by "_".
  • A Vector of arguments that are passed to the function.
    For example, if the plug-in function being invoked is vmt_sendSms("+27821234567", "Hello, world") then the function name would be "vmt_sendSms" (the company ID would be "vmt"), the first element in the arguments Vector would be "+27821234567" and the second element would be "Hello, world".
  • The method must return an instance of Object and cannot throw checked exceptions.

The plug-in must also implement the abstract isSupported method. This method should indicate whether the plug-in is supported on the device that it is currently running on. RAMP script has a native function funcSupported that allows you to check if a specific plug-in is supported on the current device that the RAMP VM is running on.

The code below is an example of a plug-in function. Note that the example used in this section is for a J2ME based RAMP VM.

package com.example;

import java.util.Vector;

import com.example.smsservices.SmsProvider;
import com.virtualmobiletech.nativefunctions.APluginFunction;

public class SendSms extends APluginFunction {

	public SendSms() {
		super("vmt_sendSms");
	}

	public Object invoke(String function, Vector args) {
		try {
			SmsProvider smsProvider = SmsProvider.getProvider();
			smsProvider.sendSms(args.elementAt(0).toString(), args.elementAt(1).toString());
            
			return "Success";
		} catch (Exception e) {
			return "Failed: arg0 " + args.elementAt(0) + " arg1 " + args.elementAt(1) + e.toString();
		}
	}
	
	public boolean isSupported() {
		try {
			Class.forName("javax.wireless.messaging.MessageConnection");
			Class.forName("javax.wireless.messaging.TextMessage");
		} catch (Exception e) {
			return false;
		}
		
		return true;
	}

}

The following 2 classes are also part of the plug-in function. They include all references to the J2ME Wireless Messaging API. The separation of all calls to the API is to ensure portability of applications that make use of the plug-in, since the API might not be present on some phones.

package com.example.smsservices;

public abstract class SmsProvider
{
	public abstract void sendSms(String number, String message) throws Exception;

	/**
	 *  this method will be used to get access to a SmsProvider class
	 */
   	public static SmsProvider getProvider() throws ClassNotFoundException 
	{
		SmsProvider provider;
		try
		{
			// this will throw an exception if the wireless package is missing
			Class.forName("javax.wireless.messaging.MessageConnection");
           
			Class c = Class.forName("com.example.smsservices.SmsImplementation");
			provider = (SmsProvider)(c.newInstance());
		} 
		catch (Exception e)
		{
			throw new ClassNotFoundException("No SMS API");
		}
		return provider;
	}
}
package com.example.smsservices;

import javax.microedition.io.Connector;
import javax.wireless.messaging.MessageConnection;
import javax.wireless.messaging.TextMessage;

public class SmsImplementation extends SmsProvider
{	
	SmsImplementation()
	{
		// We must have a no-parameter constructor, or Class.newInstance() cannot
		// create an instance of this class
	}

	// implement the abstract SmsProvider methods

	public void sendSms(String number, String message) throws Exception
	{
		MessageConnection conn = (MessageConnection) Connector.open("sms://" + number);
		TextMessage msg = (TextMessage) conn.newMessage(MessageConnection.TEXT_MESSAGE);
		msg.setPayloadText(message);
		conn.send(msg);
		conn.close();
	}
}

The SDK contains the complete eclipse project for the SendSmsPlugin in the plugins directory.

The plug-in must be compiled and the resulting class files must be packaged into a jar file.

Writing a UI component plug-in

Writing a UI component plug-in involves implementing the APluginUi interface to register all the UI components provided by the plug-in and to further to have each UI component implement either interface IDotNode or interface Renderable. This section will be explained with an example plug-in that adds the UXML component "ratingbar". Ratingbar provides the VM with a component unique to Android, a bar of stars that can be dragged to display a rating. A RAMP project implementing the ratingbar plug-in will then be able to display the UI component from the image below using only the following UXML code:

<ratingbar id="rb" stylename="barstyle" stars="5" rating="3.5" change="updateAverageRating()"/>

 

The APluginUi interface exposes a single method named "getTagToClassMap" that returns a map datatype. Each key-value pair in this map represents a UI component such that the key is the UXML tag that references the components and the value is the class object of the UI component.If a UI component requires features that is only available on certain versions of a device's operating system, then this is the method to test for the features and to exclude (if required) the component from the map that is returned and registered with the VM.

The code below registers component ratingbar, where ratingbar is represented by an instance of class UxmlRatingBar.

public class RatingBarPlugin implements APluginUi
{
	@Override
	public Hashtable getTagToClassMap() {
        Hashtable table = new Hashtable();
        table.put("ratingbar", UxmlRatingBar.class);
        return table;
    }
 }

 

The IDotNode interface exposes the minimum requirements of a UI component, its implementation can get and set values but it does not support rendering. It is typically used by components that are not rendered themselves but abstract the values required to correctly render a larger more complex component (its parent), i.e. items in a scrolling list. For a UI component to be rendered on screen it has to implement interface Renderable, which is an extension of IDotNode. Renderable exposes the methods required to create, style and layout the the component on screen.

The class UxmlRatingBar from the example implements interface Renderable since it displays an bar of stars. The code extract below reveals two of the methods exposed by IDotNode that UxmlRatingBar has to implement, since interface Renderable extends IDotNode. Note the code getting and setting the UXML attributes that ratingbar supports.

    @Override
    public Object getValue(String key)
    {
        key = PluginUtils.getInstance().getTrimmedKey(this, key);
        
        if (key.equals(IDotNode.ATTR_STYLENAME)) 
            return this.styleName;
        
        if (key.equals("rating")) 
            return ""+this.rating;
        
        if (key.equals("stars")) 
            return ""+this.stars;
        
        if (key.equals("change")) 
            return this.change;
        
        if (key.equals("raw"))
            return PluginUtils.getInstance().marshall(this, new String[] {IDotNode.ATTR_STYLENAME, "rating", "stars"});
        
        return null;
    }
    
    @Override
    public boolean setValue(String key, Object value) 
    {
        key = PluginUtils.getInstance().getTrimmedKey(this, key);
        
        if (key.equals(IDotNode.ATTR_STYLENAME)) 
        {
            this.styleName = (String) value;
            if (this.view != null)
                applyStylingOnUiThread();
            
            return true;
        }
        
        if (key.equals("rating")) 
        {
            this.rating = Float.parseFloat((String) value);
            if (this.view != null)
                updateViewOnUIThread();
        
            return true;
        }
        
        if (key.equals("stars")) 
        {
            this.stars = Integer.parseInt((String) value);
            if (this.view != null)
                updateViewOnUIThread();
        
            return true;
        }
        
        if (key.equals("change"))
        {
            this.change = (String) value;
        }
        
        return false;
    }

 

The following  code extract is from the Renderable portion of UxmlRatingBar. It shows method "getView" where the UI representation of the component is created, and method "applyStyling" where the UI representation of the component is styled for horizontal layout and margin.

    @Override
    public View getView() 
    {
        if (view == null)
        {
            ratingBar = new RatingBar(PluginUtils.getInstance().getContext());
            updateView();
            ratingBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
                @Override
                public void onRatingChanged(RatingBar ratingBar, float newRating, boolean fromUser) {
                    if (fromUser)
                    {
                        rating = newRating;
                        if ((change != null) && (change.length() > 0))
                            PluginUtils.getInstance().executeAction(change);
                    }
                }
                
            });
            
            view = new RelativeLayout(PluginUtils.getInstance().getContext());
            view.setGravity(Gravity.CENTER);
            RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            view.addView(ratingBar, relativeParams);    
        }
        applyStyling();
        
        return view;
    }

    @Override
    public void applyStyling() 
    {
        if (this.view == null)
            return;
        
        Style style = PluginUtils.getInstance().getUxmlStyle(styleName);
        
        view.setGravity(style.getHorizontalAlignment());
        
        int[] margins = style.getMargins();
        ((RelativeLayout.LayoutParams)ratingBar.getLayoutParams()).setMargins(
                margins[3],
                margins[0], 
                margins[1], 
                margins[2]);
    }

 

Plug-in utils

The singleton class PluginUtils provide access to the some of the under the hood functionality of the RAMP VM. On Java based platforms the class is located in package com.virtualmobiletech.plugins. The instance of PluginUtils can be obtained with static method getInstance(). PluginUtils provide both a standard set of methods across all supported platforms and additional methods unique to a platform. The unique, platform-specific methods are listed in appendices II - IV.

Standard PluginUtils methods

public void executeAction(String action)

Execute a RAMP action where an action is either

  • a RAMP function
  • or going to a ramp form.

 

An example of a RAMP function:

PluginUtils.getInstance().executeAction("login('username', 'password')");

is equivalent to RAMP code

 login("username", "password");

 

An example of going to a RAMP form:

 PluginUtils.getInstance().executeAction("form1");

equivalent to RAMP code

 goto("form1");

 

public void executeFunctionWithParams(String action, Object[] params)

Execute a RAMP function, e.g.   

PluginUtils.getInstance().executeFunctionWithParams("login", new Object[] {"username", "password"})

is equivalent to RAMP code

login("username", "password");

 

String getTrimmedKey(IDotNode node, String key)

Given a node and key, it returns the key trimmed (truncated) to everything after the node's ID. Used when developing UI components.

 

Object getValueFromUxmlChildren(IDotNode parent, Vector nodes, String key)

For parent component with a vector of nodes as its children, returns the value for the first IDotNode instance in nodes that supports the given key.

 

boolean setValueInUxmlChild(IDotNode parent, Vector nodes, String key, Object value)

For parent component with a vector of nodes as its children, set the value for the first IDotNode instance in nodes that supports the given key. Returns true if successful.

 

Vector unmarshall(Vector marshalled, Vector unmarshalled)

Unmarshalls a vector of marshalled (raw) UXML components.

 

Hashtable marshall(IDotNode node, String[] attributes)

Marshall (convert to raw type) a UXML node that support all the keys listed in the attributes parameter.

 

Vector marshallChildren(Vector childNodes)

Marshall (convert to a list of raw types) a vector of UXML nodes.

 

Style getUxmlStyle(String styleName)

Retrieve the UXML style object that has the ID styleName.

Deploying plug-in

Once you have created your plug-in you have to upload it to the RAMP deployment platform. After logging in on the deployment platform you can navigate to:

RAMP VM CONFIG -> VM PLUGIN -> NEW VMPLUGIN

The following must be set:

  • Aliases: This field is only required for function plug-ins. This is the RAMP script aliases that will be assigned to your plug-in's functions. You will be able to invoke your plug-in functions from RAMP script using the aliases defined here. The aliases must correspond to the function names registered in the constructor of the plug-in.
  • VM Plugin binary: This is a jar file containing your compiled class file of your plug-in that either:
    • extends APluginFunction
    • and/or implements APluginUi
  • VM Plugin dependencies: This is all the jars containing classes that your plug-in depends on. The SendSmsPlugin example class depends on J2ME Wireless Messaging API (WMA), so the WMA classes must be uploaded. The WMA is already contained in J2ME runtimes so the check box "include in VM" should be unchecked. If the dependency classes are not contained in the target device's system runtimes, then the check box should be checked so that the depended classes are compiled into your RAMP VM.
  • Permissions: This defines the permissions that your plug-in requires to be able to execute on the target devices. The SendSmsPlugin example sends SMSs using the WMA and therefore requires the javax.wireless.messaging.sms.send permission. In the case of an Android plug-in, the permission's name is provided – e.g. the required Android permission <uses-permission android:name="android.permission.CAMERA" /> is added as android.permission.CAMERA.
  • Capability groups: This defines the set of RAMP VMs that should be extended with the plug-in.
  • VM plugin name: The name of your plug-in on the deployment platform.
  • VM plugin class name: This is the fully qualified class name of your plug-in. The example SendSmsPlugin class name would be com.vmt.plugins.sendsms.SendSmsPlugin
  • VM plugin version: The version of your plug-in on the deployment platform.
  • VM plugin description: A brief description of the functionality of your plug-in.

Using RAMP VM Plug-ins in an application.

Using your deployed plug-ins in a RAMP application requires:

  • adding the plug-ins it to a VM Config
  • set the VM Config for an application when the application is created.
  • inform the RAMP IDE.

Create VM Config

After logging in on the RAMP deployment platform you can navigate to:

RAMP VM CONFIG -> VM CONFIG -> NEW VMCONFIG

The following must be set:

  • VM plugin: Select all the plug-ins to be included in the config from the set of plug-ins you have created
  • Signing params: signing the config – more information in the "RAMP VM Signing" document.
  • VM config name: The name of your config on the deployment platform.
  • VM config version: The version of your config on the deployment platform.
  • VM config description: A brief description of the functionality of your config.

Inform the RAMP IDE

Using your plug-ins in a RAMP project requires first informing the RAMP IDE of the plug-ins associated with the project's application. Failing to do so will cause the RAMP IDE to complain that the plug-in functions are unknown and it will not be possible to upload the project.

In Eclipse, with the RAMP plug-in installed (a guide is available in the Getting Started guide), right-click on the project and navigate to "Get Ramp VM Plugins...". Fill in the service endpoint address, your RAMP account details and the relevant application. Click on OK. All the plug-in functions that were included with the VM config, that was asigned to project's application, are now accessible in the IDE.

 

Appendix I - Using activities when writing Android plug-ins.

Get current active RAMP activity

Starting services in Android requires access to the current active RAMP activity. The current activity can be obtained from class PluginUtils with method getActivity(). The method setActivity(activity) is also provided purely to test a plug-in outside of an actual RAMP app.

Launch new activity

Creating new activities requires both the current active activity and access to the application's AndroidManifest.xml. The manifest is unfortunately not accessible with the plugin library. The activity class PluginActivity is provided for creating and accessing activities. It comes pre-declared in the AndroidManifest.xml. All plug-in activities must be an instance of this class – this avoids conflicts between plug-ins.

To create an unique PluginActivity instance, the class PluginActivityModel is provided. Each PluginActivity instance is associated with a class that extends PluginActivityModel. Before an Android intent creates a new plug-in activity, A PluginActivityModel sublass must first be assigned to it – this is done with the static method PluginActivity.assignPluginActivityModelToIntent which takes as parameter the intent that starts the activity and the Class instance of the class extending PluginActivityModel. The instance of the class extending PluginActivityModel then also has access to the PluginActivity instance it is assigned to with the method getActivity().

For an example consider the extract of code provided below, it is from an Android plug-in that creates an activity to preview what its phone's camera sees before taking a picture. Note that in the example class CameraPreview extends classPluginActivityModel.

private void takePicture()
{
    Activity activity = PluginUtils.getInstance().getActivity();
    Intent intent = new Intent(activity, PluginActivity.class);
    intent = PluginActivity.assignPluginActivityModelToIntent(intent, CameraPreview.class);
    activity.startActivityForResult(intent, PluginUtils.IGNORE_REQUEST_CODE);
}

 

When launching a new activity from a standard RAMP activity (as opposed to an instance of PluginActivity), the "startActivity" method should not be used. This method does not trigger a result when the launched activity finishes, which will cause problems in the RAMP VM when the plug-in returns. One of the "startActivityForResult" methods (launch methods that trigger a result) should be called, and it is essential that the "requestCode" is set to PluginUtils.IGNORE_REQUEST_CODE. Below is a code example where a new activity is launched from the current RAMP activity using "startActivityForResult":

Activity activity = PluginActivity.getCurrentRampActivity();
activity.startActivityForResult(intent, PluginUtils.IGNORE_REQUEST_CODE);

 

The last requirement to creating a meaningful activity is access to its callback methods. Each of PluginActivity's callback methods calls an equivalent method defined for PluginActivityModel. For each protected "on" callback method, the PluginActivityModel has a "do" method that can be overridden. For an example consider the extract of PluginActivity's method onCreate(Bundle savedInstanceState) which calls the PluginActivityModel method doCreate(Bundle savedInstanceState):

@Override
protected void onCreate(Bundle  savedInstanceState)
{
    super.onCreate(savedInstanceState);   
    
    if (hasModel())
        model.doCreate(savedInstanceState);
    else
        this.finish();
}

 

Appendix II - PluginUtils methods unique to Android.

Activity getActivity()

Get the current active VM activity.

 

public void setActivity(Activity activity)

Set the current active VM activity. Only for testing purposes.

 

Context getContext()

Get the current active VM context.

 

void setContext(Context context)

Set the current active VM context. Only for testing purposes.

 

int parseIntAsColor(String value)

Convert RAMP's String representation of hexadecimal color to Android's integer representation.

 

Bitmap getImageBitmapFromName(String name)

Return the Bitmap of the Uxml Image component that has ID name.

 

void runOnUiThread(Runnable r)

Run runnable on the UI thread,

 

boolean isCalledOnUiThread()

Return true if currently on the UI thread.

 

int getDensityDpi()

Get the device's display density dpi.

 

int getDensityLow()

Returns Androids  classification for low density dpi.

 

int getDensityMedium()

Returns Androids  classification for medium density dpi.

 

int getDensityHigh()

Returns Androids  classification for high density dpi.

 

Appendix III - PluginUtils methods unique to Blackberry.

void pushScreen(Screen screen)

Display a new screen and add it to the display stack.

 

void popScreen(Screen screen)

Pop the current screen off the display stack and display the previous screen.

 

Bitmap getImageBitmapFromName(String name)

Return the Bitmap of the Uxml Image component that has ID name.

 

int parseIntAsColor(String value)

Convert RAMP's String representation of hexadecimal color to Blackberry's integer representation.

 

Appendix IV - PluginUtils methods unique to J2ME.

Image getImageBitmapFromName(String name)

Return the Image of the Uxml Image component that has ID name.

 

int parseIntAsColor(String value)

Convert RAMP's String representation of hexadecimal color to J2ME's integer representation.

 

 

 

 

  • No labels