Tuesday, October 21, 2008

iPOJO on Android

Android is the Google OS for mobile phone (and the future GPhone). Android provides a Java-like virtual machine: Dalvik.

So, why not trying to execute an iPOJO-based application on the top of Android?
The idea was to embed an OSGi/iPOJO framework on Android and then to deploy an application on the top of the framework.

You can download the application in two parts:
the Android application embedding Felix/iPOJO : here
the SpellChecker application : here

The application is designed as follow:
  • The Android application creates and starts a OSGI/iPOJO framework (Felix + iPOJO + FileInstall)
  • The Android application tracks a ViewFactory service. This service is used to display the application GUI (the service allows creating the GUI main component).
  • The SpellChecker application (10 minutes tutorial) is deployed on OSGi, and use iPOJO. The only difference with the one form the 10 minutes tutorial is the GUI that uses Android HMI component (rather than Swing)
SpellChecker application bundles are deployed thanks to FileInstall. A specific folder is monitored in order to install/update/uninstall jar files contained in this folder.


Embedding Felix and iPOJO inside an Android Application


First, we need to create an Android application. This application creates a new Felix when the application is instantiated:

public synchronized void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
PrintStream out = new PrintStream(new OutputStream(){
ByteArrayOutputStream output = new ByteArrayOutputStream();
@Override
public void write(int oneByte) throws IOException {
output.write(oneByte);
if (oneByte == '\n') {
Log.v("out", new String(output.toByteArray()));
output = new ByteArrayOutputStream();
}
}});
System.setErr(out);
System.setOut(out);
m_configMap = new StringMap(false);
m_configMap.put(FelixConstants.LOG_LEVEL_PROP,String.valueOf(Logger.LOG_DEBUG));
m_configMap.put(DirectoryWatcher.DEBUG, "1");
// Configure the Felix instance to be embedded.
m_configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
File bundles = new File(FELIX_BUNDLES_DIR);
if (!bundles.exists()) {
if (!bundles.mkdirs()) {
throw new IllegalStateException("Unable to create bundles dir");
}
}
m_configMap.put(DirectoryWatcher.DIR, bundles.getAbsolutePath());
// Add core OSGi packages to be exported from the class path
// via the system bundle.
m_configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, ANDROID_FRAMEWORK_PACKAGES);
// Explicitly specify the directory to use for caching bundles.
try {
m_cache = File.createTempFile("felix-cache", null);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
m_cache.delete();
m_cache.mkdirs();
m_configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, m_cache.getAbsolutePath());
}

When the application starts, the Felix Framework is started with two Activators :
  • one installing bundles from resources
  • one installing bundles from a folder (FileInstall)
Then a service tracker is used to track arrivals and departures of the ViewFactory (provided by the SpellChecker GUI).

public synchronized void onStart() {
super.onStart();
setContentView(new View(this));
Resources res = getResources();
try {
List<BundleActivator> activators = new ArrayList<BundleActivator>();

// Plugs the bundle installer (install bundle from application resources)
activators.add(new Installer(res));
// Plugs the FileInstall activator
activators.add(new FileInstall());

m_felix = new Felix(m_configMap, activators);
m_felix.start();
} catch (BundleException ex) {
throw new IllegalStateException(ex);
}
try {
m_tracker = new ServiceTracker(m_felix.getBundleContext(),
m_felix.getBundleContext().createFilter("(" + Constants.OBJECTCLASS
+ "=" + ViewFactory.class.getName() + ")"),
new ServiceTrackerCustomizer() {

@Override
public Object addingService(ServiceReference ref) {
System.out.println("======= Service found !");
final ViewFactory fac =
(ViewFactory) m_felix.getBundleContext().getService(ref);
if (fac != null) {
runOnUiThread(new Runnable() {
public void run() {
setContentView(fac.create(ApacheFelix.this));
}
});
}
return fac;
}

@Override
public void modifiedService(ServiceReference ref,
Object service) {
removedService(ref, service);
addingService(ref);
}

@Override
public void removedService(ServiceReference ref,
Object service) {
m_felix.getBundleContext().ungetService(ref);
runOnUiThread(new Runnable() {
public void run() {
setContentView(new View(ApacheFelix.this));
}
});
}});
m_tracker.open();
} catch (InvalidSyntaxException e) {
e.printStackTrace();
}
}

Note that the iPOJO bundle is deployed from application resource. The bundle was “dexed” and placed in the res/raw folder of the application project. Then, you can load the bundle with:

public void start(BundleContext arg0) throws Exception {

InputStream is = res.openRawResource(R.raw.ipojo);

Bundle bundle = arg0.installBundle(IPOJO_HTTP_PATH, is);
bundle.start();
}
Before the be installed, the application must be exported in a .apk file.
SpellChecker application
The application is basically the same than the one form the 10 minutes tutorial. There are two differences:
  • Bundles are “dexed”
  • The GUI bundle is implemented with Android GUI components
Another difference is about the optionality of the dependency on SpellChecker service. This dependency is now optional, and use two bind/unbind methods:
public synchronized void bindSpellChecker(SpellChecker sp) {
checker = sp;


if (activity != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
// Enable button
m_button.setEnabled(true);
m_result.setText("Enter words, and click on 'check'");
m_main.invalidate();
System.out
.println("==> Spell checker GUI receives
a new spell checker ...");
}
});
}
}

public synchronized void unbindSpellChecker(SpellChecker sp) {
checker = null;
if (activity != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
// Enable button
m_button.setEnabled(false);
m_result.setText("No Spellchecker available");
m_main.invalidate();
System.out
.println("=====> Spell checker GUI was
unbind from the spell checker");
}
});
}

Note that GUI modifications MUST be done in the UI Thread.


Starting the emulator


The application uses a SD card storage. So creates an SDCard iso:

mksdcard size filename

Then, launch the emulator (emulator must be accessible from your path) with:

emulator -shell -sdcard dev/android/sdcard1.iso

The emulator starts. When the shell is ready, change the permission access on /data/dalvik-cache:

chmod 777 /data/dalvik-cache

These permissions are needed to correctly load bundles.

Installing the application


To install the application (the .apk file), I use the Eclipse plugin (the apk file can be unsigned) or with the adb command (the apk file must be signed).
Once signed, you can deploy your application with:

adb install application.apk

Once installed, the application is available in the Android emulator menu

If you launch the application, a black screen appears … The Spellchecker application is not deployed!
So, to deploy the application, the bundles files must be placed in the /data/felix/bundles folder.
adb push SpellCheckGui\ for\ Android.jar /data/felix/bundles/SpellcheckGui.jar gui.jar
cd bundles
adb push spell.services.jar /data/felix/bundles/spell.services.jar

If you launch the application, the GUI appears, but you can’t use the check button. No spell checker services are available. Push the others jars in the same folder.
adb push spell.checker.jar \
/data/felix/bundles/spell.checker.jar
adb push spell.english.jar \
/data/felix/bundles/spell.english.jar

Once done, the check button becomes available. If you remove a the English dictionary, the check button becomes disabled:
adb shell rm /data/felix/bundles/spell.english.jar
adb push spell.english.jar /data/felix/bundles/spell.english.jar


Conclusion


This very simple application has shown that it is possible to create dynamic applications on the top of Android with OSGi and iPOJO. It sounds really promising …



References

11 comments:

Anonymous said...

im a complete novice and this doesn’t make sense to me is there a easy way to load this software on my g1 as spelling is my forte and am desperate to get a spell checker or predictive text add-on to my phone please help
Any answers to andym28@amarsland.adsl24.co.uk
Please

Anonymous said...

Hello,

in Felix 1.80 some changes where introduced, so that felix works an android phone with Android 1.5 without any hack so it is interesting.
Your example here is good, but it doesn't work anymore. I try to get it running since 3 days, many config-map settings where outdated, the jars seemed to be dexed with dx shipped with android 1.0m5, some pathes where wrong e.g. /data/felix/bundles is now /data/data/felix/files/bundles . I tried everything that came into my mind, but it still doesnt work. The Bundles are installed, but no GUI shows up. It remains the Black screen with the name of the app. Can you please update your sample to work with android 1.5r2 an felix-1.8.0,ipojo-1.2.0 and fileinstall-1.0.0. If interested in more details or discussion you can contact me: surealmn@googlemail.com
(english/german) bye matthias

Clement said...

Hi,

Yep, this post is a little bit outdated. The api used to embed Felix has changed as the OSGi R4.2 now define a standard API (see http://felix.apache.org/site/launching-and-embedding-apache-felix.html).

About the newest version of Felix supporting Android from scratch, I'm aware about that. Actually, I fixed the https://cwiki.apache.org/jira/browse/FELIX-1156 issue about that ;-)

ASAP, I will provide a new post about that...

Clement

Unknown said...

Hello,
thanks a lot for your answer. I just noticed short time after my comment 2 things: 1. felix 1.8.0 doesn't contain the android1.5 improvement. 2. you are this hero who did this great job. with this you rescued my Diplomarbeit partially, because it contains using felix and ipojo on android. At the end there shall be a prototype which is to be presented in my final diploma presentation on an real G1. you saved me the shock that it simply wouln'd have worked previously. It would be very nice if you will update/post new it soon, because its hard to find up-to-date tutorials about this stuff.
greatings matthias

Unknown said...

Hello again,

using the latest snapshot of felix (1.9.0) and after some changes in the code, I finally got this tutorial updated. You can find the code here


Thank you, Clement!


bye matthias

mingderwang said...

Need help!
I try to modify your SpellChekGui.java to a program (YQLGui.java) with network accessing requirement. Therefore, it needs org.apache.http.impl.client package for resolve. What can I do? Should I add the required bundle for DefaultHttpClient? or I can get that package originally from Android OS?

Error message as follows;


140 I/System.out( 394): ERROR: Error starting /data/data/de.mn.felixembedand/files/felix/newbundle/YQLGui.jar (org.osgi.f ramework.BundleException: Unresolved constraint in bundle YQLGui [16]: package; (package=org.apache.http.impl.client))
141 W/System.err( 394): org.osgi.framework.BundleException: Unresolved constraint in bundle YQLGui [16]: package; (packag e=org.apache.http.impl.client)
142 W/System.err( 394): at org.apache.felix.framework.Felix.resolveBundle(Felix.java:3393)
143 W/System.err( 394): at org.apache.felix.framework.Felix.startBundle(Felix.java:1597)
144 W/System.err( 394): at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1077)
145 W/System.err( 394): at org.apache.felix.framework.StartLevelImpl.run(StartLevelImpl.java:263)
146 W/System.err( 394): at java.lang.Thread.run(Thread.java:1096)
147 W/ActivityManager( 57): Activity idle timeout for HistoryRecord{44c86728 de.mn.felixembedand/.FelixEmbedAndStarter}

Clement said...

Hi,

You must add the 'org.apache.http.impl.client' to the exported package list from the system bundle.
This is done during the initialization of Felix.

Clement

mingderwang said...

It works, great thanks.

I add some following lines in FelixConfig.java file.

private static final String ANDROID_FRAMEWORK_PACKAGES_ext = (
"org.osgi.framework; version=1.4.0," +
"org.osgi.service.packageadmin; version=1.2.0," +

...// add these 3 lines.

"org.apache.http.impl.client; " +
"org.apache.http.client.methods; " +
"org.apache.http; " +

...

Tecnologia da Informação said...

Please, could you send me the codes of this example ? I would like to understand them better! e-mail: romulogadelhaml@gmail.com

Android app developer said...

This advice is actual interesting, I absolutely enjoyed, I would like get added advice about this, because is actual beautiful, acknowledgment for sharing

Chad Beaudin said...

Clement, thanks for all the time you have dedicated to this. Given the changes in Felix, iPojo, and Android, is this tutorial still valid? I just want to make sure before I set down the path of trying it out.