The JNI.jsm JavaScript code module abstracts all of the js-ctypes required for writing JNI code. To use it, you first need to import the code module into your JavaScript scope:

Components.utils.import("resource://gre/modules/JNI.jsm");

This module was available in Firefox since version 17. If you would like to support versions before that, you can copy and paste the contents of the JSM file int

JNI stands for Java Native Interface; this library allows calling Java code running in Java Virtual Machines (JVMs), etc. The most common use for this module is in add-ons and other works on Firefox for Android (Fennec). With this module, all of the Android SDK functions that Firefox has permissions for are at your fingertips.

A note about Firefox for Android, this JSM file is already globally imported and is available from the privileged window scope as window.JNI. There also exists a jenv variable in the window scope, so if you need to run JNI from the global scope, use a different variable name then jenv.

JNI.jsm contains all the js-ctypes needed to communicate with the JNI libraries. The js-ctypes is abstracted away while all the underlying data is all ctype data. Functions are declared similar to js-ctypes but in a very different syntax. Unlike C from js-ctypes, defining constants is not a manual magic number method in JNI, it is declared the same way we define functions, except in JNI they are called fields.

Method overview

CData GetForThread();
CData LoadClass(CData aJenv, String aClassFullyQualifiedName, [optional] Object aDeclares);
CData NewString(CData aJenv, String aStr);
String ReadString(CData aJenv, CData aJavaString);
void UnloadClasses();

Methods

GetForThread()

blah blah

CData GetForThread();
Parameters

This function does not take any arguments.

Return value

blah blah

LoadClass()

blah blah

CData LoadClass(
  aJenv,
  aFullyQualifiedName,
  [optional] Object aDeclares
);
Parameters
aJenv
The return value of GetForThread().
aFullyQualifiedName
A sig of a typed array or the name of the class that would be used in a signature, but without the surrounding L and ;.
If it is the sig of a typed array such as '[I' then aDeclares must not be declared. See the section "Working with arrays" to see how this is used to create typed arrays.

If it is the name of a class, then aDeclares MUST be declared. For example, the class of GeckoAppShell has the fully-qualified class name of org.mozilla.gecko.GeckoAppShell. The signature for this would be Lorg.mozilla.gecko.GeckoAppShell; but we pass here without the L and ;. We can use dot notification here for the parent class. However if it was a subclass such as GeckoInterface (org.mozilla.gecko.GeckoAppShell.GeckoInterface), then this will cause a crash. Therefore, it is always recommended to use slash notation. So for GeckoAppShell we would pass here "org/mozilla/gecko/GeckoAppShell" and for GeckoInterface we would use "org/mozilla/gecko/GeckoAppShell$GeckoInterface".
aDeclares optional
If this is omitted, then aFullyQualifiedName must be an array type.
If this is provided, then aFullyQualifiedName must be a class name. A required object that contains up to five fields of key names: constructors, fields, static_fields, methods, and static_methods.
Return value

blah blah

NewString()

blah blah

CData NewString(
 aJenv,
 aStr
);
Parameters
aJenv
The return value of GetForThread().
aStr
A Javascript String, use any encoding desired.
Return value

blah blah

ReadString()

blah blah

String ReadString(
 aJenv,
 aJavaString
);
Parameters
aJenv
The return value of GetForThread().
aJavaString
A CData object that is a Java string.
Return Value

A Javascript String.

UnloadClasses()

blah blah

void UnloadClasses();
Parameters

This function takes no arguments.

Return value

This function has no return value, if you try to store the return value in a variable, it will be undefined.

Working with arrays

Creating/preallocating a typed array

 

Methods

CData .get(Number aIndex);
CData .getElements(Number aStart, Number aLength);
void .set(Number aIndex, CData aValue);
void .setElements(Number aStart, [array, size_is(arr.length > Number anyNumber > 0)] in CData aValsArray);

.get()

Gets the value of the element in the array at given aIndex.

CData get(Number aIndex);
Parameters
aIndex
The position in the array to obtain.
Return value

Returns primitive CData.

.getElements()

Returns a new CData object of the section of the array specified by aStart and ending at position aStart + aLength.

void setElements(Number aStart, Number aLength);
Parameters
aStart
The position to start setting elements of the array in.
aLength
The number of elements to copy from aStart. If 0 is supplied, an empty CData array is returned.
Return value

A new CData array object based on the array.

.set()

Sets an element in the array at given aIndex to the given aVal.

void set(Number aIndex, CData aVal);
Parameters
aIndex
The position in the array to change.
aVal
A value to set it to.
Return value

This function has no return value, if you try to store the return value in a variable, it will be undefined.

.setElements()

Sets a section of a typed array to specified values. This function starts on the position of aStart in the array and sets it to the values in aValsArray. This is done by calling .set for each element with the respective value in the aValsArray.

void setElements(Number aStart, [array, size_is(arr.length > Number anyNumber > 0)] in CData aValsArray);
Parameters
aStart
The position to start setting elements of the array in.
aValsArray
A Javascript array holding the values to set.
Return value

This function has no return value, if you try to store the return value in a variable, it will be undefined.

Attributes

Attribute Type Description
length Number The number of elements in the array

Demonstration

var my_jenv = null;
try {
    my_jenv = JNI.GetForThread();

    var SIG = {
        String: 'Ljava/lang/String;',
        int: 'I'
    };

    JNI.LoadClass(my_jenv, '[' + SIG.String);
    var StringArray = JNI.classes.java.lang.String.array; // Object { js#obj: CData, js#proto: function (), __cast__: function (), new: function () }
    var sa = StringArray.new(5); // Object { js#obj: CData, length: 5 }

    JNI.LoadClass(my_jenv, '[' + SIG.int);
    var IntArray = JNI.classes.int.array; // Object { js#obj: CData, js#proto: function (), __cast__: function (), new: function () }
    var ia = IntArray.new(5); // Object { js#obj: CData, length: 5 }

ia.get(0); // 0
ia.get(1); // 0
ia.get(2); // 0
ia.get(3); // 0
ia.get(4); // 0

ia.set(2, 10); // void
ia.get(2); // 10

ia.setElements(3, [50, 75]); // void
ia.get(0); // 0
ia.get(1); // 0
ia.get(2); // 10
ia.get(3); // 50
ia.get(4); // 75

} finally {
    if (my_jenv) {
        JNI.UnloadClasses(my_jenv);
    }
}

Casting

This example shows how to cast, the casting happens at JNI.classes.android.view.WindowManager.__cast__(wm) below:

function main() {
    var my_jenv;
    try {
        my_jenv = JNI.GetForThread();

        var SIG = {
            WindowManager: 'Landroid/view/WindowManager;',
            WindowManager_LayoutParams: 'Landroid/view/WindowManager$LayoutParams;',
            ViewGroup_LayoutParams: 'Landroid/view/ViewGroup$LayoutParams;',
            View: 'Landroid/view/View;',
            void: 'V',
            Context: 'Landroid/content/Context;',
            String: 'Ljava/lang/String;',
            Object: 'Ljava/lang/Object;',
            GeckoAppShell: 'Lorg/mozilla/gecko/GeckoAppShell;'
        };

        var GeckoAppShell = JNI.LoadClass(my_jenv, fullyQualifiedNameOfClass(SIG.GeckoAppShell), {
            static_methods: [
            {
                name: 'getContext',
                sig: '()' + SIG.Context
            }]
        });

        var Context = JNI.LoadClass(my_jenv, fullyQualifiedNameOfClass(SIG.Context), {
            methods: [
            {
                /* http://developer.android.com/reference/android/content/Context.html#getSystemService%28java.lang.Class%3CT%3E%29
                 * public abstract Object getSystemService (String name)
                 */
                name: 'getSystemService',
                sig: genMethodSIG([
                        SIG.String // name
                    ],
                    SIG.Object // return
                )
            }],
            static_fields: [
                {
                    name: 'WINDOW_SERVICE',
                    sig: SIG.String
                } // http://developer.android.com/reference/android/content/Context.html#WINDOW_SERVICE // public static final String WINDOW_SERVICE
            ]
        });

        var WindowManager = JNI.LoadClass(my_jenv, fullyQualifiedNameOfClass(SIG.WindowManager), {
            methods: [
            {
                name: 'addView',
                sig: '(' + SIG.View + SIG.ViewGroup_LayoutParams + ')' + SIG.void
            },
            {
                name: 'removeView',
                sig: '(' + SIG.View + ')' + SIG.void
            }]
        });

        var aContext = GeckoAppShell.getContext();
        var wm = aContext.getSystemService(Context.WINDOW_SERVICE);
        var wm_casted = JNI.classes.android.view.WindowManager.__cast__(wm);

    } finally {
        if (my_jenv) {
            JNI.UnloadClasses(my_jenv);
        }
    }
}

// helper functions
function genMethodSIG(aParamsArr, aRet) {
    // aParamsArr is an array of SIG's for each param. Not fully qualified name, meaning if its a class, it needs the surrouning L and ;
    // aRet is a SIG for the return value. Not fully qualified name, same meaning as above row

    return '(' + (aParamsArr ? aParamsArr.join('') : '') + ')' + aRet;
}

function fullyQualifiedNameOfClass(aClass) {
    // aClass is a string with L and ; arround it
    return aClass.substr(1, aClass - 2);
}

Examples

Read Java strings

This example shows how to read Java strings as Javascript strings. We will get paths to special folders on Fennec (Firefox for Android) into a Javascript variable with ReadString(). This shows how to get the paths to the "Pictures" folder:

Components.utils.import("resource://gre/modules/JNI.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm"); // because we use OS.Path.join in this example

var my_jenv = null;
try {
    my_jenv = JNI.GetForThread();

    var SIG = {
        Environment: 'Landroid/os/Environment;',
        String: 'Ljava/lang/String;',
        File: 'Ljava/io/File;'
    };

    var Environment = JNI.LoadClass(my_jenv, SIG.Environment.substr(1, SIG.Environment.length - 2), {
        static_fields: [
            { name: 'DIRECTORY_PICTURES', sig: SIG.String }
        ],
        static_methods: [
            { name:'getExternalStorageDirectory', sig:'()' + SIG.File }
        ]
    });

    JNI.LoadClass(my_jenv, SIG.File.substr(1, SIG.File.length - 2), {
        methods: [
            { name:'getPath', sig:'()' + SIG.String }
        ]
    });

    var javaFile_dirExtStore = Environment.getExternalStorageDirectory(); // Object { js#obj: CData }
    var javaStr_dirExtStorePath = javaFile_dirExtStore.getPath(); // Object { js#obj: CData }
    var jsStr_dirExtStorePath = JNI.ReadString(my_jenv, javaStr_dirExtStorePath); // "/mnt/sdcard"

    var jsStr_dirPicsName = JNI.ReadString(my_jenv, Environment.DIRECTORY_PICTURES); // "Pictures"
    var jsStr_dirPics = OS.Path.join(jsStr_dirExtStorePath, jsStr_dirPicsName); // "/mnt/sdcard/Pictures"

} finally {
    if (my_jenv) {
        JNI.UnloadClasses(my_jenv);
    }
}

Example 2

blah blah blah

code here

See also