Extending AIR for Android

*** The following is totally unsupported by Adobe ***
*** UPDATE: Adobe has officially added native extensions to AIR. I highly recommend you use that approach instead of mine. ***

Adobe AIR provides a consistent platform for desktop and mobile apps. While consistency is very important there are times when developers need to extend beyond the common APIs. This article will walk you through how to integrate AIR for Android applications with other native APIs and functionality in the Android SDK. It covers three common use cases for native extensibility: System Notifications, Widgets, and Application Licensing.

If you’d like to follow along you will need the following prerequisites:

Before getting started, a little background will help. Android applications are distributed as APK files. An APK file contains the Dalvik executable (dex), which will run on an Android device inside the Dalvik VM. The Android SDK compiles a Java-like language to dex.

AIR for Android applications are also distributed as APK files. Inside of these APK files is a small bit of dex that bootstraps the AIR for Android runtime, which then loads and runs the SWF file that is also inside of the APK. The actual dex class that bootstraps the AIR application is dynamically generated by the adt tool in the AIR SDK. The class is named AppEntry and its package name depends on the AIR application ID, but it always begins with “air”. The AppEntry class checks for the existence of the AIR runtime and then launches the AIR application. The Android descriptor file in an AIR APK specifies that the main application class is the AppEntry class.

To extend AIR for Android applications to include native APIs and Android SDK functionality, you start by creating a SWF file using Flex and then copy that SWF file, the dex classes for AIR for Android, and the required resources into a standard Android project. By using the original AppEntry class you can still bootstrap the AIR application in the Android project but you can extend that class to gain a startup hook.

  1. To get started, download a package with the required dependencies for extending AIR for Android:
    http://www.jamesward.com/downloads/extending_air_for_android-flex_4_5-air_2_6-v_1.zip
  2. Next, create a regular Android project in Eclipse (do not create an Activity yet):
  3. Copy all of the files from the zip file you downloaded into the root directory of the newly created Android project. You will need to overwrite the existing files and update the launch configuration (if Eclipse asks you to).
  4. Delete the “res/layout” directory.
  5. Add the airbootstrap.jar file to the project’s build path. You can do that by right-clicking on the file, then select Build Path and then Add to Build Path.
  6. Verify that the project runs. You should see “hello, world” on your Android device. If so, then the AIR application is properly being bootstrapped and the Flex application in assets/app.swf is correctly being run.

    At this point if you do not need any custom startup hooks then you can simply replace the assets/app.swf file with your own SWF file (but it must be named app.swf). If you do need a custom startup hook then simply create a new Java class named “MainApp” that extends the air.app.AppEntry class.

  7. Override the onCreate() method and add your own startup logic before super.onCreate() is called (which loads the AIR app). Here is an example:
    package com.jamesward;
     
    import air.app.AppEntry;
    import android.os.Bundle;
     
    public class MainApp extends AppEntry {
     
    	@Override
    	public void onCreate(Bundle arg0) {
    		System.out.println("test test");
    		super.onCreate(arg0);
    	}
    }
  8. Open the AndroidManifest.xml descriptor file and tell it to use the new MainApp class instead of the original AppEntry class. First change the package to be the same as your MainApp’s package:
    <manifest package="com.jamesward" android:versionCode="1000000" android:versionName="1.0.0"
      xmlns:android="http://schemas.android.com/apk/res/android">

    Also update the activity to use the MainApp class (make sure you have the period before the class name):

    <activity android:name=".MainApp"

    You can also add any other permissions or settings you might need in the AndroidManifest.xml file.

  9. Save the changes and, when Eclipse prompts you, update the launch configuration.
  10. Run the application and you should again see “hello, world”. This time, however, in LogCat (command line tool or Eclipse view) you should see the “test test” output. Now that you have a startup hook, you can do some fun stuff!

System Notifications and Services

AIR for Android applications don’t yet have an API to do Android system notifications. But you can add system notifications to your AIR for Android application through a startup hook. In order for the AIR application to communicate with the native Android APIs you must provide a bridge for the communication. The simplest way to create that bridge is using a network socket. The Android application can listen for data on the socket and then read that data and determine if it needs to display a system notification. Then the AIR application can connect to the socket and send the necessary data. This is a pretty straightforward example but some security (for instance a key exchange) should be implemented to insure that malicious apps don’t discover and abuse the socket. Also some logic to determine which socket should be used would likely be necessary.

  1. Inside the application section add a new Android Service:
  2. <service android:enabled="true" android:name="TestService" />
  3. Since this example uses a Socket you will also need to add the INTERNET permission:
  4. <uses-permission android:name="android.permission.INTERNET"/>
  5. You might also want to enable the phone to vibrate when there is a new notification. If so add that permission as well:
    <uses-permission android:name="android.permission.VIBRATE"/>
  6. Save your changes to AndroidManifest.xml.
  7. Next, create the background Java Service class, called TestService. This service will listen on a socket and when necessary, display an Android Notification:
    package com.jamesward;
     
    import java.io.BufferedInputStream;
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
     
    import android.app.Notification;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.Context;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.Looper;
    import android.util.Log;
     
    public class TestService extends Service
    {
      private boolean stopped=false;
      private Thread serverThread;
      private ServerSocket ss;
     
      @Override
      public IBinder onBind(Intent intent)
      {
        return null;
      }
     
      @Override
      public void onCreate()
      {
        super.onCreate();
     
        Log.d(getClass().getSimpleName(), "onCreate");
     
          serverThread = new Thread(new Runnable() {
     
            public void run()
            {
                    try
                    {
                            Looper.prepare();
                            ss = new ServerSocket(12345);
                            ss.setReuseAddress(true);
                            ss.setPerformancePreferences(100, 100, 1);
                            while (!stopped)
                            {
                                    Socket accept = ss.accept();
                                    accept.setPerformancePreferences(10, 100, 1);
                                    accept.setKeepAlive(true);
     
                                    DataInputStream _in = null;
                                    try
                                    {
                                            _in = new DataInputStream(new BufferedInputStream(accept.getInputStream(),1024));
                                    }
                                    catch (IOException e2)
                                    {
                                      e2.printStackTrace();
                                    }
     
                                    int method =_in.readInt();
     
                                    switch (method)
                                    {
                                      // notification
                                      case 1:
                                            doNotification(_in);
                                            break;
                                    }
                            }
                    }
                    catch (Throwable e)
                    {
                            e.printStackTrace();
                            Log.e(getClass().getSimpleName(), "Error in Listener",e);
                    }
     
                    try
                    {
                      ss.close();
                    }
                    catch (IOException e)
                    {
                      Log.e(getClass().getSimpleName(), "keep it simple");
                    }
            }
     
            },"Server thread");
          serverThread.start();
     
      }
     
      private void doNotification(DataInputStream in) throws IOException {
        String id = in.readUTF();
        displayNotification(id);
      }
     
      @Override
      public void onDestroy() {
              stopped=true;
              try {
                      ss.close();
              } catch (IOException e) {}
              serverThread.interrupt();
              try {
                      serverThread.join();
              } catch (InterruptedException e) {}
      }
     
      public void displayNotification(String notificationString)
      {
        int icon = R.drawable.mp_warning_32x32_n;
        CharSequence tickerText = notificationString;
        long when = System.currentTimeMillis();
        Context context = getApplicationContext();
        CharSequence contentTitle = notificationString;
        CharSequence contentText = "Hello World!";
     
        Intent notificationIntent = new Intent(this, MainApp.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
     
        Notification notification = new Notification(icon, tickerText, when);
        notification.vibrate = new long[] {0,100,200,300};
     
        notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
     
        String ns = Context.NOTIFICATION_SERVICE;
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
     
        mNotificationManager.notify(1, notification);
      }
     
    }

    This service listens on port 12345. When it receives some data it checks if the first “int” sent is “1”. If so, it then creates a new notification using the next piece of data (a string) that is received over the socket.

  8. Modify the MainApp Java class to start the service when the onCreate() method is called:
    	@Override
    	public void onCreate(Bundle savedInstanceState)
    	{
    		try
    		{
    			Intent srv = new Intent(this, TestService.class);
    			startService(srv);
    		}
    		catch (Exception e)
    		{
    			// service could not be started
    		}
     
    		super.onCreate(savedInstanceState);
    	}

    That is all you need to do in the Android application.

  9. Next, create a Flex application that will connect to the socket and send the right data. Here is some sample code for my Notifier.mxml class, which I used to test the Android service:
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
                           xmlns:s="library://ns.adobe.com/flex/spark">
     
      <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";
     
        global {
          fontSize: 32;      
        }
      </fx:Style>
     
      <s:layout>
        <s:VerticalLayout horizontalAlign="center" paddingTop="20"/>
      </s:layout>
     
      <s:TextInput id="t" text="test test"/>
     
      <s:Button label="create notification">
        <s:click>
          <![CDATA[
            var s:Socket = new Socket();
            s.connect("localhost", 12345);
            s.addEventListener(Event.CONNECT, function(event:Event):void {
              trace('connected!');
              (event.currentTarget as Socket).writeInt(1);
              (event.currentTarget as Socket).writeUTF(t.text);
              (event.currentTarget as Socket).flush();
              (event.currentTarget as Socket).close();
            });
            s.addEventListener(IOErrorEvent.IO_ERROR, function(event:IOErrorEvent):void {
              trace('error! ' + event.errorID);
            });
            s.addEventListener(ProgressEvent.SOCKET_DATA, function(event:ProgressEvent):void {
              trace('progress ');
            });
          ]]>
        </s:click>
      </s:Button>
     
    </s:Application>

    As you can see there is just a TextInput control that allows the user to enter some text. Then when the user clicks the Button the AIR for Android application connects to a local socket on port 12345, writes an int with the value of 1, writes the string that the user typed into the TextInput control, and finally flushes and closes the connection. This causes the notification to be displayed.

  10. Now simply compile the Flex app and overwrite the assets/app.swf file with the new Flex application. Check out a video demonstration of this code.

Widgets

Widgets in Android are the mini apps that can be displayed on the home screen of the device. There is a fairly limited amount of things that can be displayed in Widgets. So unfortunately Widgets can’t be built with AIR for Android. However a custom application Widget can be packaged with an AIR for Android application. To add a Widget to an AIR for Android application you can use the default AppEntry class instead of wrapping it with another class (MainApp in my example). (It doesn’t, however, do any harm to keep the MainApp class there.) To add a Widget simply add its definition to the AndroidManifest.xml file, create the Widget with Java, and create a corresponding layout resource.

  1. First define the Widget in the application section of the AndroidManifest.xml file:
    <receiver android:name=".AndroidWidget" android:label="app">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider" android:resource="@xml/airandroidwidget" />
    </receiver>
  2. You need an XML resource that provides metadata about the widget. Simply create a new file named airandroidwidget.xml in a new res/xml directory with the following contents:
    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="294dp"
        android:minHeight="72dp"
        android:updatePeriodMillis="86400000"
        android:initialLayout="@layout/main">
    </appwidget-provider>

    This tells the widget to use the main layout resource as the initial layout for the widget.

  3. Create a res/layout/main.xml file that contains a simple text display:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/widget"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ffffffff"
        >
    <TextView  
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="hello"
        />
    </LinearLayout>

    Next, you’ll need to create the AppWidgetProvider class specified in the AndroidManifest.xml file.

  4. Create a new Java class named AndroidWidget with the following contents:
    package com.jamesward;
     
    import android.app.PendingIntent;
    import android.appwidget.AppWidgetManager;
    import android.appwidget.AppWidgetProvider;
    import android.content.Context;
    import android.content.Intent;
    import android.widget.RemoteViews;
    import com.jamesward.MainApp;
     
    public class AndroidWidget extends AppWidgetProvider
    {
     
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
        {
            final int N = appWidgetIds.length;
     
            // Perform this loop procedure for each App Widget that belongs to this provider
            for (int i=0; i<N; i++)
            {
                int appWidgetId = appWidgetIds[i];
                Intent intent = new Intent(context, MainApp.class);
                intent.setAction(Intent.ACTION_MAIN);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
                views.setOnClickPendingIntent(R.id.widget, pendingIntent);
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
        }
    }

    This class will display the Widget when necessary and register a click handler that will open the MainApp application when the user taps on the Widget.

  5. Run the application to verify that it works.
  6. Now you can add the widget to the home screen by holding down on the home screen and following the Widget wizard.
  7. Verify that tapping the widget launches the AIR application.

Application Licensing

Android provides APIs to help you enforce licensing policies for non-free apps in the Android Market. You might want to go read up on Android Licensing before you give this one a try.

To add Application Licensing to you AIR for Android application you first need to follow the steps outlined in the Android documentation. The broad steps are as follows:

  1. Set up an Android Market publisher account
  2. Install the Market Licensing Package in the Android SDK
  3. Create a new LVL Android Library Project in Eclipse
  4. Add a Library reference in the Android project to the LVL Android Library
  5. Add the CHECK_LICENSE permission to your Android project’s manifest file:
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />

After completing these set up steps, you are ready to update the MainApp Java class to handle validating the license:

package com.jamesward;
 
import com.android.vending.licensing.AESObfuscator;
import com.android.vending.licensing.LicenseChecker;
import com.android.vending.licensing.LicenseCheckerCallback;
import com.android.vending.licensing.ServerManagedPolicy;
 
import air.Foo.AppEntry;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings.Secure;
 
public class MainApp extends AppEntry {
 
    private static final String BASE64_PUBLIC_KEY = "REPLACE WITH KEY FROM ANDROID MARKET PROFILE";
 
    // Generate your own 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
        -45, 12, 72, -31, -8, -122, 98, -24, 86, 47, -65, -47, 33, -99, -55, -64, -114, 39, -71, 47
    };
 
    private LicenseCheckerCallback mLicenseCheckerCallback;
    private LicenseChecker mChecker;
    private Handler mHandler;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new Handler();
        String deviceId = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
        mLicenseCheckerCallback = new MyLicenseCheckerCallback();
        mChecker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY);
        mChecker.checkAccess(mLicenseCheckerCallback);
    }
 
    private void displayFault() {
        mHandler.post(new Runnable() {
            public void run() {
                // Cover the screen with a messaging indicating there was a licensing problem
                setContentView(R.layout.main);
            }
        });
    }
 
    private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
        public void allow() {
            if (isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            // Should allow user access.
        }
 
        public void dontAllow() {
            if (isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            displayFault();
        }
 
        public void applicationError(ApplicationErrorCode errorCode) {
            if (isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
        }
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mChecker.onDestroy();
    }
}

Also add the following to a new res/layout/main.xml file in order to display an error when the license is denied:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
 
  <TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/license_problem"
    />
 
</LinearLayout>

The text to display uses a string resource named “license_problem”, which must be added to the res/values/strings.xml file:

<string name="license_problem">THERE WAS A PROBLEM LICENSING YOUR APPLICATION!</string>

When the application runs it will check for a valid license. If the license comes back as valid then the AIR application will start and run as usual. However, if there is an invalid license then the application will set the ContentView to the R.layout.main resource, which displays the error message defined in the “license_problem” resource. To simulate different responses you can change the “Test Response” in your Android Market profile.

The Gory Details
I’ve wrapped up a generated AppEntry class and its resources to make the process of extending AIR for Android fairly easy. If you are interested in seeing how that is done, I’ve posted all of the source code on github.

Here is an overview of the procedure:

  1. Use the AIR SDK to create an AIR for Android APK file.
  2. Use the dex2jar utility to convert the AppEntry dex classes into a JAR file.
  3. Pull the resource classes out of the JAR file so that they don’t conflict with the new resources.
  4. Use apktool to extract the original resources out of the AIR for Android APK file.
  5. Create a single ZIP file containing the airbootstap.jar file, resources, AndroidManifest.xml file, and assets.

Now you can simply copy and paste those dependencies into your Android project.

Conclusion
Hopefully this article has helped you to better understand how you can extend AIR for Android applications with Android APIs. There are still a number of areas where this method can be improved. For instance, I am currently working with the Merapi Project developers to get Merapi working with my method of extending AIR for Android. That will provide a better bridging technique for communicating between the AIR application and Android APIs. So stay tuned for more information about that. And let me know if you have any questions!

  • Dirk

    wow – very cool stuff! Would this technique also allow to listen for intents and then fire up an AIR application?

    • Sure! You can do anything you want.

    • Odys

      Hi James,
      It’s great!
      I think this is also a way to controlling individual swf playing.
      But is there a way that the AppEntry can be notified when swf playing ended?

      • Jason Jakob

        Sure, for example just have the air application send a socket message when a video_stopped event is fired from your media player.

  • Scoch

    Magic! Thanks and bravo!

  • Mike K

    Very, very cool! Thanks for sharing this info!!

  • Nice work James. This approach is much more straight forward. Although it would also be nice if this was just supported in AIR natively. ;)

  • Amazing solution. Thanks for posting.

  • Wow, this is great initiative by James Ward . Always wanted to part of such communities where people can learn and share the ideas in front of big players. And thanks James Ward for sharing this article :)

  • This is awesome! Do you think it would be possible to build …lets say…just for this example i’ll say a tic tac toe game with flex…but install as a live wallpaper? Do you think you would have an idea on how to do that? and have it be efficient and like, stop when its not being displayed and not burn the battery? That would be amazing and open up a ton of opportunities!

    • Are Live Wallpapers the same as Widgets? Is so then I don’t think that Flex apps can be run as Live Wallpapers. There are pretty heavy restrictions on what can be rendered in Widgets.

      • No.

        Live wallpapers:
        http://developer.android.com/resources/articles/live-wallpapers.html

        Think of it as like just running the swf as the desktop wallpaper.

        • Oh. That’s right. Live Wallpapers are the stuff beneath the Widgets. Looks like Live Wallpapers don’t have the same restrictions as Widgets. So it might be possible. The trick would be to see if somehow the AIR bootstrapping can render to a SurfaceView. I’ll look into this a bit more.SurfaceView

  • chris

    Realy good :)

    Tell me i have test to use only one socket client (in my Air application in the sample you recreate an s var every click ) and i’ dont understand why only the first call work ( i have remove the socket close )?

    • I’m not sure what you are asking. Can you post a code sample?

      • Andrew

        Do I need custom startup hooks for the app to have internet access?
        I have added permission for INTERNET but so far no luck. Th app starts up fine but without internet.

      • Andrew

        Check out slide 22 in this presentation: http://dl.google.com/io/2009/pres/W_0300_CodingforLife-BatteryLifeThatIs.pdf

        How would this approach to extending AIR for Android drain the battery?
        So far I am loving the capabilities I can use with this but am curiously concerned about it using too much resources.

        If the Service is constantly running but not doing anything until it receives socketData, should I be concerned?

    • hi chris
      I’ve moved the “Socket accept = ss.accept();” line in the service class to be outside (before) the while loop. This keeps this socket open and then the notification example works for more than once.

    • Leonardo Sobral

      I´m having the same issue with the socket, tried to connect just once but only works if you connect everytime you need to send a message…

      • Leonardo Sobral

        Got it working with Socket accept = ss.accept() change, thanks

  • Hi James,

    Is it possible to integrate C2DM (Google’s Cloud to Device Messaging (C2DM) Service) using your flex to Android bridge? If you could please provide small sample on how to approach that?

    Regards

    Sanchit

    • That should be possible but I don’t have a sample for that. If you give it a try then let me know what you find.

  • ArneO

    Any connection to sl4a would be very cool!

  • ozgur uksal

    Have a question. Why does flex4.5 have no option to install as an eclipse plug-in?

    • Glen Blanchard

      It is bundled in the standalone version now. Unfortunately you have to install the standalone first then inside the install there is another executable utilities\Adobe Flash Builder 4.5 Plug-in Utility.exe

  • This is really awesome! Thanks James!

    This means we can access things that Adobe AIR can’t provide yet:
    – Contact list
    – Battery status
    – Phone status

    Hope to see the ‘official way’ to include native app into Adobe AIR

  • Erik

    Would this be the preferred way to communicate via Bluetooth?

    • It would be the only way. :)

      • Erik

        Via a socket, right?

  • Hi James

    I had been looking at implementing this type of bridge for some time so this is a welcome piece of code. However perhaps I am missing something but I am getting a slight error (slight as in in works, I see “Hello World” but I get console error message of the following

    [2011-05-19 17:12:14 - ddms]null
    java.lang.NullPointerException
    	at com.android.ddmlib.Client.sendAndConsume(Client.java:572)
    	at com.android.ddmlib.HandleHello.sendHELO(HandleHello.java:142)
    	at com.android.ddmlib.HandleHello.sendHelloCommands(HandleHello.java:65)
    	at com.android.ddmlib.Client.getJdwpPacket(Client.java:671)
    	at com.android.ddmlib.MonitorThread.processClientActivity(MonitorThread.java:317)
    	at com.android.ddmlib.MonitorThread.run(MonitorThread.java:263)
     
    [2011-05-19 17:12:14 - ddms]null
    java.lang.NullPointerException
    	at com.android.ddmlib.Client.sendAndConsume(Client.java:572)
    	at com.android.ddmlib.HandleHello.sendHELO(HandleHello.java:142)
    	at com.android.ddmlib.HandleHello.sendHelloCommands(HandleHello.java:65)
    	at com.android.ddmlib.Client.getJdwpPacket(Client.java:671)
    	at com.android.ddmlib.MonitorThread.processClientActivity(MonitorThread.java:317)
    	at com.android.ddmlib.MonitorThread.run(MonitorThread.java:263)

    For the record I am working in AIR 2.6 (installed the latest runtime to the emulator)

    Any thoughts on this? Bear in mind too I normally work in Actionscript with back end of C# so Java is something that I am coming around to … reluctantly.

    Thanks in advance

    Gerry

    • I’ve never seen that error before. Perhaps a device restart will fix it? Or maybe you need a new version of the Android tools?

  • murat celep

    James
    Thanks a lot for this helpful post. I really like the idea however as long as it is not officially supported by adobe, this method can’t find its way into any serious,large-scale application.
    Given the speed the mobile operating systems change, a third-party runtime such as air will always be several steps behind the native-supported features.
    Are there any internal discussions about providing this nice “hack” of yours as an official feature for the android platform?

    • They are looking at the possibility of officially adding native extensions to AIR. That would make this workaround unnecessary.

    • Jason Jakob

      If you know anything about Android architecture you will see that essentially the entire java layer does communications with the C++ lower level in a similar fashion. Java does not natively talk to devices and services it’s all done through RPC calls. So in this case we are just adding another higher layer of similar brokering.

  • Peter

    Maybe a N00b question but, Would it be possible to just use the service without the startup hook and swf swap?

  • Craig

    I was doing something similar using just apktool that Elad Elrom posted here and it has been working great but the main issue is that if the phone doesn’t have Air for Android installed at the time of install it will just outright crash (instead of prompting to install like a typical Air for Android app). Does using the dex2jar method suffer from this same issue?

    • It shouldn’t have any problems dealing with AIR not being installed.

      • Whitney Wilson

        hi, I am searching for a method of creating an Android Air App that does not bring up the ‘install air’ popup. Maybe the app’s apk has an Adobe Air installer in it, or something. I am wondering if your technique would help with this? or could you point me in the right direction? thanks.

        I am using Flash to export the apk currently.

  • Michael

    Very nice. I got the notification working but can’t seem to get a Toast Notification to work. Can you post some more examples?

    • What do you mean by “Toast Notification”?

      • Michael

        FROM: http://developer.android.com/guide/topics/ui/notifiers/toasts.html

        A toast notification is a message that pops up on the surface of the window. It only fills the amount of space required for the message and the user’s current activity remains visible and interactive. The notification automatically fades in and out, and does not accept interaction events.

        • Oh. Cool! I haven’t seen that API on Android. It seems like that should work fine. The only possible issue is that maybe you don’t have access to the UI Thread from the Service. If that is the case then I’m not exactly sure how to deal with it since I haven’t tried this myself.

          • Andrew

            I accidentally misspelled app.swf one time and the application loaded with no swf to see.
            It was then I was able to see my Toast notifications.
            So, it is like the are being loaded underneath the swf.

            Rename you app.swf once and try it. See if you get the same results.
            How to fix this, I have no idea.

            On a side note.
            I am able to push data from the swf to the android native functions, like notifications.. but how does one go the opposite direction?
            For example, getting the contactList and pushing that data back to the swf.
            I assume through the Socket but I have had no luck so far.

          • CG

            Making a toast actually worked fine for me with a Xoom on 3.1.

            I created a couple of menu items (…which also work! sweet…) and did this on selecting an item.

            Toast.makeText(this, “Put your message here”, Toast.LENGTH_SHORT).show();

  • Jon

    Awesome Idea! However, I am having trouble debugging the original swf once it’s been packaged. Do you have any idea how to do this?

    • I think that AIR looks for a resource file “debugger” specified in the raws.xml file. If that file exists then the debugger starts. But I haven’t confirmed this yet with AIR 2.6.

  • Matt

    Very impressive work. Have you made an ios version yet? :-)

    • I’ve looked into it and it might be impossible. :( But maybe someone will find a way!

  • arun

    Good tutorial. though i want slightly different version, Here is what I want to achieve, I have my
    1) custom Air App
    2) Custom Android app.

    I want the android app to start the air app, Also there are some arguments that needs to be passed to air app. Once air app is done, that activity should “finish” and android app should be active.

    To achieve this, is the above mentioned method of sending data the only way or is there any other native methods?
    Can I just use startactivity in android app with a intent having package as Air app. Though still the question is how do i pass arguments.

    Any suggestions?

    -Arun

    • All of that should be possible but I’m not sure how exactly to do it. Let me know if you figure it out.

      • fgarzonhz

        Hi James, maybe you know, we are building an ane for Android, so, inside Adrnoid code we need to call .jar, but we are getting error, so the flow will be: Flex->ANE->Jar->Jar. Thanks.

  • manjunath

    can we make adobe air mobile app to AutoStart?. i have a situation where my adobe mobile app should run automatically at mobile startup.

    any advice will be appreciated.

    • I’m not sure if Android has an API for that. But if it does, then yes.xkcd.com

    • Jason Jakob

      In a similar solution you can have a native android app with properties set to start on boot up which then launches your air application via an intent with a custom url. You need to have an intent filter added to the air application to react to the custom url. Android OS will take care of the rest. see this link for instructions on how to configure your air app to react to the intent call. http://stackoverflow.com/questions/5591086/passing-parameters-from-a-java-activity-to-adobe-air-app

  • I Did It!!!!

    Thank you jamesward.

    You gave me great hopful possibility!!!

  • Andrew

    Can this be used to access the Contacts List on the device?
    If so, does anyone have an example of doing it?

  • Hi =)
    You’re creating a widget with a text content inside.
    But is there a way to embed the swf directly in the widget ?

    Thank you for you great post ;)
    Anthony

  • Hi James,
    Your solution is brilliant.
    However, I couldn’t manage to use a swf that had a “landscape” in its app.xml. I’ve looked it up and the problem seems to be this:
    link
    In the last comment there the guy said that the extension file was not up-to-date. Can it be that I need a newer airbootstap.jar file?
    Thanks
    Yair

    • Possibly. I created this one based on AIR 2.6. Check out the “Gory Details” in the post to see how you can build one yourself.

      • Thanks James,
        I’ve tried creating the jar myself using flex sdk 4.5.1. It works the same – and has the same problem. If I add a true tag to the app.xml, it crashes when I try to run it on my device.
        Do you by any chance have another idea?
        Thanks
        Yair

        • No. Can you try to get an error message out? Maybe logcat or something will help.

          • Hope I got the right lines:
            logcat.txt

          • Looks like it can’t find a resource. Not sure why.

          • After playing with the app.xml, I’ve decided it is useless. So I’ve tried something else. We published a game for iOS written in Flash – Hungry Choo Choo, and now we’re working on its Android version. It works well, landscaped. So I’ve used apktool to break it apart, and looked at its app.xml. It was almost empty… So I’ve looked at the android manifest file, found these new lines inside the activity tag, and used it in my app:
            <meta-data android:name="aspectRatio" android:value="landscape" />
            <meta-data android:name="autoOrients" android:value="false" />
            <meta-data android:name="fullScreen" android:value="true" />

            Finally success…
            Thanks again.

  • CG

    This works great!

    But since it uses standard SWFs, doesn’t that mean it only supports Flash Player targeted features? How do we integrate AIR app features?

  • CG

    Also – how might this work for iOS now that we have the universal packager?

    • I don’t know of a way to do this for iOS.

  • Hi ,
    nice article , but I can’t find this file airbootstrap.jar in your zip files.
    Could you send me or I missing something?

    Thank you,
    Dimitar

  • It seems that there is a minor issue, we should also copy lib folder from AIR apk file, so Google market could filter some devices which CPU model is lower than ARM v7. As you know, these devices can’t run AIR application.

  • Chaco

    Great piece of work, thanks James!
    BTW, I double checked the Merapi project and it seems like it’s been inactive for a couple years, but looking forward to hearing some news soon.

  • Pingback: Android AIR Bridge « The Talking Head Blog()

  • Thanks for a great tutorial – nice work and very informative. Using your project as a starting point I managed to get a working example of an AIR Android bridge working by adapting Merapi in places and using serialisation and deserialisation from GraniteDS instead of BlazeDS. If you are interested there is an example at http://javadz.wordpress.com. The code for the bridge is available here http://code.google.com/p/android-java-air-bridge/ if you want to have some fun playing around with it – no support provided though :)

  • Erik

    How do you hear back from the Android side?

    Running a serversocket in Air? What port, etc. would you use?

    • Erik

      Ah, I see. You close the as3 socket in your example, but it could be kept open. And probably more tinkering on the android side to keep a read/write thread open.

  • jack

    Thanks for the great tutorial James. I have a noobie question, maybe someone here can help.

    I’m adding a splash screen to my app which I made the main activity. I was wondering how to set up another activity that will display the .SWF?

    Right now I have the second activity’s ‘android:name’ referring to a .java file that looks exactly like the class given in step 7 of the tutorial, is that a good starting point?

    It’s my 3rd day writing apps using Eclipse, so any help would be appreciated.

    • Seems like that should be fine.

      • Hi, This is really a great post. It would be great if you can help me with some problems.

        I’ve created a separate splash screen and then the MainApp activity starts. But am getting a black screen while its loading the AIR. Is there anyway to show a splash image on the MainApp itself while loading the AIR in the background? This way, I can get rid of the SplashScreen activity from my app and just directly use the MainApp and the boot time will also be faster.

        Right now I have the same splash screen for android and the air app. Still am getting a black screen between both the screens.

        Also when I use the SplashScreen as separate activity, and when I try to load the application from a NOTIFICATION, the app crashes. It would be great if I can just use the MainApp activity with a splashImage while loading the AIR and have a seamless transition between the switching.

        Thanks in advance.

        • jack

          I’ve got the same problem Manu George, I also get a black screen when trying to move this functionality to an activity besides main. (main is loading my splash screen as well).

  • Hi, I just stumbled on this article and don’t understand really whats going on but i think you might be able to help me. I am developing in Flash CS5 with AIR for Android installed. My project is all in flash currently and not flex, but I desperately need one thing from the native android API, the Compass. I just need a simple listener to update a variable in my flash that is the direction the phone is facing.. I am already using the accelerometer events to track the phone’s angles on the x and y..

    Is this possible? please help.. the above example is 90% greek to me.

    • You should be able to do that basically using the steps I outlined above. Sorry I don’t have a more streamlined way to do that for you.

    • Ben

      In response to eliddell, keep persisting. I’m in the same boat as you – a Flash developer creating AIR for android apps with no expeience of Flex, Java or Eclipse. It’s taken me a coupe of weeks to get some promising results from this tutorial, but well worth it. I’m using this extending air method to be able to use android market licensing and in-app billing. My experience so far with this tutorial is that it is written for developers with a higher level of coding than just ActionScript. Basically the way I got this tutorial to work was to follow all the steps (one section at a time – ie don’t progress until you’ve got the notifications example working). But there are a few things that I had to change in the code to get it to work. For example the “import android.content.Intent;” line is missing from the MainApp code. If you download the source files after you have set up the project (run through all the steps in the tutorial), then copy and paste the code across form the downloaded source, to your project, this should fix all of those issues. Also, you can create the app.swf in Flash instead of Flex. Just use the code within the CDATA and create your own button with a label (eg “cn_btn”) and add cn_btn.addEventListener(MouseEvent.CLICK, openSocket); to your code. Then create an input TextField labelled “t”. My comments probably seem overly simple to developers that really know what they are doing – I’m just trying to share my experience to help others who have struggled to get their head around this. Many thanks to James Ward for extending the bridge.

  • This is amazing work! Very impressed…

    Thanks so much, managed to get the notifications running so quickly after thinking it was impossible!

  • Pingback: How to open a PDF in a external viewer from a AIR app running on Android. « Splashdust.net()

  • Eddy

    Im developing in Flash Professional CS5.5, im only interested in getting the application licensing working. Is this possible? or do i have to be building the project in flex/flash builder? I have a CS5.5 license and would like to not have to get Flash Builder. Any help would be greatly appreciated.

  • Vova Leskiv

    Brilliant work James, thanks!

    BTW, I have a library lib\armeabi-v7a\libNativeABI.so in my Flex APK. Do you know what is it and should I include it somehow in my Android porject too?

  • Ravin D

    Wow! Thanks a lot. We have been scratching our heads for a solution. :-D

  • Pingback: Extending AIR for Android « Flex Black Belt()

  • sh

    Err…sry but does this not defeat the purpose…u might aswell program ur android app using java. Nevertheless ty for the article.

  • Andrew

    Certainly works for me.
    Access to all the goodies of Android and yet can still primarily code in AS3.
    I use it for Contacts, Cameras and Notifications primarily but likely more features in the future.
    Helps bring earlier versions of Android up to speed as well.
    Thank you James and other contributors.

  • Tashi

    Great work…but i am not geeting “Hello World”

    Please help

  • Pingback: air for androidとNativeアプリケーションを組み合わせる – | .dev():クラスメソッド開発ブログ()

  • mihau

    Hi, i have some problems with this app,
    Im trying communicate my air app and android app.
    first i still have only one answer while trying to send more than one times.
    I’ve moved the “Socket accept = ss.accept();” line before the while loop – but its still doesnt work.

    And second question – ohw to make communication from java android app to AIR android app.

    Im trying to communicate on 12345 and “localhost” – but it doesnt work,
    so i find that it should be 10.0.2.2 ip – is that right ?

    all things im doing on emulator – platform 2.3.3

    • mihau

      OK, some other things:
      communication AIR->Service going more than once, but Notifications is displayed only once.
      I still have no idea how to send data from Service to AIR app, what should be port and ip

  • Would it be possible to add an admob advertisement to my game with this method? That would be really good news if this works. I know there is another way to do it for air ( StageWebView), but AFAIK the accounts got canceled due to click fraud.

  • Ricardo Faria

    Hi.

    I’ve using your System Notifications “hack” and so far so good.

    Only one thing missing, being able to pass parameters from the notification to my air app. When The user clicks on the notification, my app is activated, but I want to know from which notification. Can you help me do that? Add info to the notification intent so I can read it from InvokeEvent arguments? Other option?

    Thanks

    Ricardo

  • Pingback: Admob with Air for Android using the native Android API | Rough Sea Games()