com.phonegap.api.PluginManager.java Source code

Java tutorial

Introduction

Here is the source code for com.phonegap.api.PluginManager.java

Source

/*
 * PhoneGap is available under *either* the terms of the modified BSD license *or* the
 * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
 * 
 * Copyright (c) 2005-2010, Nitobi Software Inc.
 * Copyright (c) 2010, IBM Corporation
 */
package com.phonegap.api;

import java.util.HashMap;
import java.util.Map.Entry;

import org.json.JSONArray;
import org.json.JSONException;

import android.webkit.WebView;

/**
 * PluginManager is exposed to JavaScript in the PhoneGap WebView.
 * 
 * Calling native plugin code can be done by calling PluginManager.exec(...)
 * from JavaScript.
 */
public final class PluginManager {

    private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
    private HashMap<String, String> services = new HashMap<String, String>();

    private final PhonegapActivity ctx;
    private final WebView app;

    /**
     * Constructor.
     * 
     * @param app
     * @param ctx
     */
    public PluginManager(WebView app, PhonegapActivity ctx) {
        this.ctx = ctx;
        this.app = app;
    }

    /**
     * Receives a request for execution and fulfills it by finding the appropriate
     * Java class and calling it's execute method.
     * 
     * PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded 
     * string is returned that will indicate if any errors have occurred when trying to find
     * or execute the class denoted by the clazz argument.
     * 
     * @param service       String containing the service to run
     * @param action       String containt the action that the class is supposed to perform. This is
     *                   passed to the plugin execute method and it is up to the plugin developer 
     *                   how to deal with it.
     * @param callbackId    String containing the id of the callback that is execute in JavaScript if
     *                   this is an async plugin call.
     * @param args          An Array literal string containing any arguments needed in the
     *                   plugin execute method.
     * @param async       Boolean indicating whether the calling JavaScript code is expecting an
     *                   immediate return value. If true, either PhoneGap.callbackSuccess(...) or 
     *                   PhoneGap.callbackError(...) is called once the plugin code has executed.
     * 
     * @return             JSON encoded string with a response message and status.
     */
    @SuppressWarnings("unchecked")
    public String exec(final String service, final String action, final String callbackId, final String jsonArgs,
            final boolean async) {
        PluginResult cr = null;
        boolean runAsync = async;
        try {
            final JSONArray args = new JSONArray(jsonArgs);
            String clazz = this.services.get(service);
            Class c = null;
            if (clazz != null) {
                c = getClassByName(clazz);
            }
            if (isPhoneGapPlugin(c)) {
                final Plugin plugin = this.addPlugin(clazz, c);
                final PhonegapActivity ctx = this.ctx;
                runAsync = async && !plugin.isSynch(action);
                if (runAsync) {
                    // Run this on a different thread so that this one can return back to JS
                    Thread thread = new Thread(new Runnable() {
                        public void run() {
                            try {
                                // Call execute on the plugin so that it can do it's thing
                                PluginResult cr = plugin.execute(action, args, callbackId);
                                int status = cr.getStatus();

                                // If no result to be sent and keeping callback, then no need to sent back to JavaScript
                                if ((status == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
                                }

                                // Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
                                else if ((status == PluginResult.Status.OK.ordinal())
                                        || (status == PluginResult.Status.NO_RESULT.ordinal())) {
                                    ctx.sendJavascript(cr.toSuccessCallbackString(callbackId));
                                }

                                // If error
                                else {
                                    ctx.sendJavascript(cr.toErrorCallbackString(callbackId));
                                }
                            } catch (Exception e) {
                                PluginResult cr = new PluginResult(PluginResult.Status.ERROR);
                                ctx.sendJavascript(cr.toErrorCallbackString(callbackId));
                            }
                        }
                    });
                    thread.start();
                    return "";
                } else {
                    // Call execute on the plugin so that it can do it's thing
                    cr = plugin.execute(action, args, callbackId);

                    // If no result to be sent and keeping callback, then no need to sent back to JavaScript
                    if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
                        return "";
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
        } catch (JSONException e) {
            System.out.println("ERROR: " + e.toString());
            cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
        }
        // if async we have already returned at this point unless there was an error...
        if (runAsync) {
            if (cr == null) {
                cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
            }
            ctx.sendJavascript(cr.toErrorCallbackString(callbackId));
        }
        return (cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }");
    }

    /**
     * Get the class.
     * 
     * @param clazz
     * @return
     * @throws ClassNotFoundException
     */
    @SuppressWarnings("unchecked")
    private Class getClassByName(final String clazz) throws ClassNotFoundException {
        return Class.forName(clazz);
    }

    /**
     * Get the interfaces that a class implements and see if it implements the
     * com.phonegap.api.Plugin interface.
     * 
     * @param c The class to check the interfaces of.
     * @return Boolean indicating if the class implements com.phonegap.api.Plugin
     */
    @SuppressWarnings("unchecked")
    private boolean isPhoneGapPlugin(Class c) {
        if (c != null) {
            return com.phonegap.api.Plugin.class.isAssignableFrom(c)
                    || com.phonegap.api.IPlugin.class.isAssignableFrom(c);
        }
        return false;
    }

    /**
     * Add plugin to be loaded and cached.  This creates an instance of the plugin.
     * If plugin is already created, then just return it.
     * 
     * @param className            The class to load
     * @return                  The plugin
     */
    public Plugin addPlugin(String className) {
        try {
            return this.addPlugin(className, this.getClassByName(className));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("Error adding plugin " + className + ".");
        }
        return null;
    }

    /**
     * Add plugin to be loaded and cached.  This creates an instance of the plugin.
     * If plugin is already created, then just return it.
     * 
     * @param className            The class to load
     * @param clazz               The class object (must be a class object of the className)
     * @param callbackId         The callback id to use when calling back into JavaScript
     * @return                  The plugin
     */
    @SuppressWarnings("unchecked")
    private Plugin addPlugin(String className, Class clazz) {
        if (this.plugins.containsKey(className)) {
            return this.getPlugin(className);
        }
        try {
            Plugin plugin = (Plugin) clazz.newInstance();
            this.plugins.put(className, plugin);
            plugin.setContext(this.ctx);
            plugin.setView(this.app);
            return plugin;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Error adding plugin " + className + ".");
        }
        return null;
    }

    /**
     * Get the loaded plugin.
     * 
     * @param className            The class of the loaded plugin.
     * @return
     */
    private Plugin getPlugin(String className) {
        Plugin plugin = this.plugins.get(className);
        return plugin;
    }

    /**
     * Add a class that implements a service.
     * This does not create the class instance.  It just maps service name to class name.
     * 
     * @param serviceType
     * @param className
     */
    public void addService(String serviceType, String className) {
        this.services.put(serviceType, className);
    }

    /**
     * Called when the system is about to start resuming a previous activity. 
     */
    public void onPause() {
        java.util.Set<Entry<String, Plugin>> s = this.plugins.entrySet();
        java.util.Iterator<Entry<String, Plugin>> it = s.iterator();
        while (it.hasNext()) {
            Entry<String, Plugin> entry = it.next();
            Plugin plugin = entry.getValue();
            plugin.onPause();
        }
    }

    /**
     * Called when the activity will start interacting with the user. 
     */
    public void onResume() {
        java.util.Set<Entry<String, Plugin>> s = this.plugins.entrySet();
        java.util.Iterator<Entry<String, Plugin>> it = s.iterator();
        while (it.hasNext()) {
            Entry<String, Plugin> entry = it.next();
            Plugin plugin = entry.getValue();
            plugin.onResume();
        }
    }

    /**
     * The final call you receive before your activity is destroyed. 
     */
    public void onDestroy() {
        java.util.Set<Entry<String, Plugin>> s = this.plugins.entrySet();
        java.util.Iterator<Entry<String, Plugin>> it = s.iterator();
        while (it.hasNext()) {
            Entry<String, Plugin> entry = it.next();
            Plugin plugin = entry.getValue();
            plugin.onDestroy();
        }
    }
}