Storyboard Lua Android Integration
On Android target platforms Storyboard provides an additional level of platform integration. In order to access the native Java service API on Android platforms Storyboard has incorporated the LuaJava module to provide a bridge from Storyboard Lua script functions to the Android Java API.
Access to the LuaJava bridge is through the luajava Lua variable. On
non-Android platforms, this variable will not be defined and this can be
used to provide alternate or simulated behavior.
function my_callback(mapargs)
if(luajava == nil) then
print("LuaJava bridge not available")
return
end
-- LuaJava available for use ...
end
The general mapping of standard Lua/Java types such as strings and numbers is handled transparently so that Lua strings can be used in Java constructors and methods in the same way that the Java String class would normally be used and similarly for Lua numbers and vice/versa.
When a Lua variable is created that is a reference or proxy to a Java
object, then access to the methods of that object are performed using
the colon (:) notation with the Lua variable,
lua_variable:method_name() notation. When accessing static member
variables of an object, this can be performed using the traditional dot
(.) notation lua_variable.member_variable_name. This is further
demonstrated in the examples shown below.
In order to access a nested Java class for instantiation or binding, the
dollar sign ($) must be used as a separator. For instance, if the Java
class Bar is a nested class of Foo, then binding would work as follows:
luajava.bindClass("Foo$Bar"). This is further demonstrated in the
examples below.
A description of the complete Android Java API is beyond the scope of
this document. For a complete coverage of the Android API refer to
http://developer.android.com/reference/packages.html
Depending on the functionality that your application is going to access,
there may be additional restrictions that must be explicitly declared in
the AndroidManifest.xml file. Permissions can be added in the Advanced
Options section when exporting your Android project. The
android:debuggable option has been changed to false by default. To
change this, you will need to use your own custom manifest file. Export
your manifest file to view it by clicking the Export button under the
Manifest File tab. You can make changes to this file and then select it
as a custom manifest file when exporting to ensure the manifest file is
setup the way you want it to be.
Within the Android environment the Storyboard Engine execution takes
place outside of the main Android/Java event loop. When integrating with
the Android API's developers should always consider that they are using
the Android API as if they were executing in a background thread and act
accordingly. This may require the creation of additional Looper
message event handlers if callback event handlers are being used. For
more information on Android process model and multi-threading
considerations, refer to the Android documentation: http://developer.android.com/guide/components/processes-and-threads.html.
Android Lua Java API
The mapping of Lua referenced objects to Android Java objects is
relatively straightforward. All of the API functionality is accessed via
the luajava Lua global variable. This variable provides four functions
that can be used to access and manipulate standard Java objects and one
variable that provides the Android Activity that is required.
luajava.newInstance(className, ...)
This function creates a new Java object based on the fully qualified
class name. Any additional parameters that are provided are passed
through to the standard Java constructor.
The return value is a Lua variable that is a proxy to the Java object or
nil if the class could not be instantiated.
-- Create an instance of a Java string tokenizer
local strTk = luajava.newInstance("java.util.
StringTokenizer","a,b,c,d", ",")
while strTk:hasMoreTokens() do
print(strTk:nextToken())
end
-- Create a new Android Intent object (unpopulated)
local intent = luajava.newInstance
("android.content.Intent")
luajava.bindClass(className)
This function creates a reference to a Java class based on a fully
qualified class name. This is different from newInstance() in that a
new Java object is not created and the constructor is not invoked, but
simply a reference to the class is returned. Use this when you need
access to static fields or methods of a Java object.
The return value is a Lua variable that is a proxy to the Java Class
object specified or nil if the class could not be found.
-- Get the current system time
local sys = luajava.bindClass("java.lang.System")
print ( sys:currentTimeMillis() )
-- Parse a string into an Android Uri
local uriClass = luajava.bindClass("android.net.Uri")
local phoneURI = uriClass:parse("tel:6135951999")
luajava.new(classObject, ....)
This function is similar to the newInstance() function but rather than
taking a fully qualified class name it takes an existing Class
reference, generally obtained from calling
bindClass(). Additional parameters can be passed to the Java constructor..
The return value is a Lua variable that is a proxy to the Java object or
nil if the class could not be instantiated.
-- Create a new string instance
str = luajava.bindClass("java.lang.String")
strInstance = luajava.new(str)
luajava.createProxy(interfaceNames, luaObject)
If a Java API requires an interface to be implemented or provided as a
set of callbacks, then it is where the createProxy() function can be
used. The interfaceNames parameter is a
comma separated list of fully qualified Java interfaces that will be
implemented by the Lua variable luaObject. The names of the interface
methods must be present in the luaObject variable.
The return value is a Lua variable that can be passed to any function or
method that requires an implementation of that interface. If the
creation of the proxy fails, then nil is returned.
-- Create a Lua variable with the same interface as an ActionListener
local button_cb = {}
function button_cb.actionPerformed(ev)
-- I would do something interesting here ...
end
-- Map the Lua variable to the Java interface
buttonProxy = luajava.createProxy("java.awt.ActionListener", button_cb)
-- Use the newly created interface instance on a Java object
button = luajava.newInstance("java.awt.Button", "execute")
button:addActionListener(buttonProxy)
luajava.nativeActivity()
All significant interaction on an Android system involves working with
an Activity (see http://developer.android.com/reference/android/app/Activity.html)
Storyboard applications that are deployed to Android devices run as
native activities which is a special
class of the general Activity that allows those applications to interact
directly with the graphics context and are generally C/C++ applications
rather than pure Java applications.
The return value of this function is a Lua variable that is a proxy for
the NativeActivity Java class used by this application or nil if the
class could not be instantiated.
-- Start an activity specified by a previously created Intent object
local na = luajava.nativeActivity()
if(na ~= nil) then
na:startActivity(intent)
else
print("No Native Activity")
end
Storyboard Lua Android Example
This example demonstrates how a phone call could be invoked as part of a
Lua callback. In order for this example to work, the AndroidManifest.xml
file must be changed to give permission for calls to be made:
%<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
-- Log message routine to route diagnostic messages
local function lm(msg)
print(msg)
end
-- Call a selected phone number using the Android API
-- Input is the string number value that is to be called
local function call_phone_number(number)
if(luajava == nil) then
lm("No luajava Lua object")
return
end
local na = luajava.nativeActivity()
if(na == nil) then
lm("No native activity available")
return
end
local uriClass = luajava.bindClass("android.net.Uri")
if(uriClass == nil) then
lm("No java.lang.String object")
return
end
local phoneURI = uriClass:parse("tel:" .. tostring(number))
if(phoneURI == nil) then
lm("No java.net.URI object")
return
end
local intentClass = luajava.bindClass("android.content.Intent")
if(intentClass == nil) then
lm("No intent class")
return
end
local intent = luajava.newInstance("android.content.Intent",
intentClass.ACTION_CALL, phoneURI)
if(intent == nil) then
lm("No intent object")
return
end
lm("Calling " .. number)
na:startActivity(intent)
end
This example demonstrates how to create a new instance of a nested inner
class of a Java class. This example gets media metadata from the
android.provider.MediaStore.Audio.Media class, which is a nested class
of android.provider.MediaStore.Audio, which in turn is a nested class of
android.provider.MediaStore.
-- In order to pass array's to any of the Android Java API's we must explicitly create
-- a Java array from a Lua table and this function covers that work.
function make_array(dataClass, values)
local arrayClass = luajava.bindClass("java.lang.reflect.Array")
if(arrayClass == nil) then
print("Can't get array class")
return nil
end
local newTypedArray = arrayClass:newInstance(dataClass, #values)
if(newTypedArray == nil) then
print("Can't get array class")
return nil
end
for i=1,#values do
arrayClass:set(newTypedArray, i-1, values[i])
end
return newTypedArray
end
function get_album_files(album_id)
if (luajava == nil) then
return
end
if (luajava.bindClass == nil) then
return
end
local na = luajava.nativeActivity()
local mediastore = luajava.newInstance("android.provider.
MediaStore$Audio$Media")
local externalURI = mediastore.EXTERNAL_CONTENT_URI
local columns = {}
columns[1] = mediastore.TITLE_KEY
columns[2] = mediastore.DURATION
columns[3] = mediastore.TITLE
local stringClass = luajava.bindClass("java.lang.String")
local array = make_array(stringClass, columns)
local where = mediastore.ALBUM_KEY .. "=?"
local what = {}
what[1] = album_id
local whatArray = make_array(stringClass, what)
local cursor = na:managedQuery(externalURI, array, where, whatArray, nil);
local res = cursor_to_table(cursor)
return res
end