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!

Introducing Mixing Loom – Runtime ActionScript Bytecode Modification

At this year’s 360|Flex conference in Denver, Mike Labriola and I unveiled a new project we’ve been working on called Mixing Loom. Our presentation was called “Planet of the AOPs” because Mixing Loom lays the foundation for true Aspect Oriented Programming (AOP) on the Flash Platform. Mixing Loom provides Flex and ActionScript applications the hooks they need to do bytecode modification either before runtime or at runtime. Through bytecode modification an application can apply a behavior across hierarchies of objects. There are a number of behaviors in a typical Flex application (such as logging, security, application configuration, accessibility, and styling) that could be represented as Aspects. Today these behaviors must either be included in every class that needs them or included way down the object hierarchy (i.e. UIComponent). With Mixing Loom a compiled SWF can be modified (applying necessary behaviors) after it’s been compiled or as it’s starting up.

If you are building Flex apps and want to take advantage of AOP then Mixing Loom is probably a bit lower level than what you need. Mixing Loom combined with AS3 Commons Bytecode provides the foundation for AOP systems to be built on top of. We hope that by providing developers the hooks to modify bytecode that frameworks will emerge that provide application developers higher level APIs based on AOP. As Mike says, “Mixing Loom kicks off the Summer of AOP.”

If you are one of those developers who likes getting dirty with bytecode modification then you might want to check out the slides from the “Planet of the AOPs” session:

If you are still following along and looking for more details on how to use Mixing Loom, then keep reading. Flex applications are broken into at least two pieces. The first piece is the thing that displays the loading / progress bar. That is located on the first “frame” of an application’s SWF file. The rest of the application is on the second frame of the main SWF and possibly in other SWF files like Modules and/or Runtime Shared Libraries (RSLs). Mixing Loom provides two ways to modify the bytecode of a running application. First, using a custom preloader an application can modify its second frame and/or any Flex Modules before they are loaded into the VM. The second way is to use LoomApplication and a custom preloader, which allows an application to modify its second frame, modules, and/or RSLs (even the signed Flex Framework RSLs). Let’s walk through a simple example of an application that uses a custom preloader to modify a string that exists in its second frame.

Let’s take a simple object Foo that has a getBar method, which returns a string “a bar”:

package blah
{
public class Foo
{
  public function getBar():String
  {
      return "a bar";
  }
}
}

And here is a simple application that just displays the results of calling an instance of Foo’s getBar method:

<?xml version="1.0"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark">
 
  <fx:Script>
    import blah.Foo;
  </fx:Script>
 
  <s:applicationComplete>
     var foo:Foo = new Foo();
     l.text = foo.getBar();
  </s:applicationComplete>
 
  <s:Label id="l"/>
 
</s:Application>

If you were to run this application as is then the Label would display “a bar” – as expected. But to give you an idea of how to do runtime bytecode modification let’s change the “a bar” string to something else. (BTW: If you are following along then you will need to pull down the mixingloom-core code from github and compile it on your own because we haven’t published a SWC for Mixing Loom yet.) The thing in Mixing Loom that actually does the bytecode modification is called a “Patcher” so we will need to create one of those that searches the bytecode for a string and then replaces that string. Before we do that, let me explain how a SWF file is structured. Every SWF file is a series of “tags”. There are many different tag types but the types we are interested in for bytecode modification are the ones that actually contain the ActionScript ByteCode (ABC). This is the DoABC tag – type 82. For a full list of SWF tags and their structures check out the SWF Spec. One of the tag types indicates an executable boundary called a Frame. As a SWF file is being loaded by Flash Player it is parsing it. When Flash Player parses a “ShowFrame” tag it knows it can load and run the preceding tags. The code doing the bytecode modification will be running on the first frame, which means that all of the tags to do the modification and those to display the Flex preloader will have already been loaded. That means we can’t modify those tags using this method at runtime. But we can modify the tags on the second frame of the SWF, which will be passed to our Patcher before they have actually been loaded.

Here is the code for the StringModifierPatcher:

package org.mixingloom.patcher
{
import flash.utils.ByteArray;
 
import org.as3commons.bytecode.tags.DoABCTag;
import org.as3commons.bytecode.util.AbcSpec;
 
import org.mixingloom.SwfContext;
import org.mixingloom.SwfTag;
import org.mixingloom.invocation.InvocationType;
import org.mixingloom.utils.ByteArrayUtils;
 
public class StringModifierPatcher extends AbstractPatcher
{
    public var originalString:String;
    public var replacementString:String;
 
    public function StringModifierPatcher(originalString:String, replacementString:String)
    {
        this.originalString = originalString;
        this.replacementString = replacementString;
    }
 
    override public function apply( invocationType:InvocationType, swfContext:SwfContext ):void
    {
        var searchByteArray:ByteArray = new ByteArray();
        AbcSpec.writeStringInfo(originalString, searchByteArray);
 
        var replacementByteArray:ByteArray = new ByteArray();
        AbcSpec.writeStringInfo(replacementString, replacementByteArray);
 
        for each (var swfTag:SwfTag in swfContext.swfTags)
        {
            if (swfTag.type == DoABCTag.TAG_ID)
            {
                swfTag.tagBody = ByteArrayUtils.findAndReplaceFirstOccurrence(swfTag.tagBody, searchByteArray, replacementByteArray);
            }
        }
 
        invokeCallBack();
    }
 
}
}

The StringModifierPatcher extends the Mixing Loom AbstractPatcher and takes two parameters, the originalString and the replacementString. The StringModifierPatcher has an apply method, which will be called by Mixing Loom during application startup. In the apply method the StringModifierPatcher creates a search ByteArray and a replacement ByteArray from the provided strings. Then it loops through each tag from the second frame of the SWF being loaded (ignoring everything that is not a DoABC tag) and then uses Mixing Loom’s ByteArrayUtils.findAndReplaceFirstOccurrence utility to replace the first occurrence of the search ByteArray with replacement ByteArray. Finally it notifies Mixing Loom that it is all done by calling the invokeCallBack method. So that is the simple example of actually modifying the application, but we still need to set the hooks in the main application so that Mixing Loom can do its thing.

Since this example only modifies frame 2 tags (no RSLs), we can just use a custom preloader to set up the Mixing Loom hooks. Here is the StringModifierPatcherPreloader:

package preloader {
import org.mixingloom.managers.IPatchManager;
import org.mixingloom.patcher.StringModifierPatcher;
import org.mixingloom.preloader.AbstractPreloader;
 
public class StringModifierPatcherPreloader extends AbstractPreloader {
    override protected function setupPatchers(manager:IPatchManager):void {
        super.setupPatchers(manager);
        manager.registerPatcher( new StringModifierPatcher("a bar", "not really a bar") );
    }
}
}

The StringModifierPatcherPreloader extends Mixing Loom’s AbstractPreloader and uses the setupPatchers method to register a new patcher. In this case the only patcher is an instance of the StringModifierPatcher that will search for the default “a bar” string and replace it with the “not really a bar” string.

The last thing to make this all work is to tell the main application to use the new preloader:

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               preloader="preloader.StringModifierPatcherPreloader">

Here is the result:

Exciting! Our application code just modified itself at startup! Now this is obviously a very trivial example but I hope it provides a basic understanding of how to use Mixing Loom as the foundation for AOP. Let’s walk through some other examples that are more exciting (and complex).

For the next example let’s do something a little more AOP-ish. There will be an XML configuration file that is loaded on startup that will specify some classes and methods to apply interceptors to. An interceptor is simply a method call injected into the body of a method. First, here is the FooInterceptor class:

package
{
import mx.core.FlexGlobals;
 
public class FooInterceptor
{
    public static function interceptAll():void
    {
        FlexGlobals.topLevelApplication.setStyle("backgroundColor", Math.random() * 0xffffff);
    }
}
}

For demo purposes this interceptor is very simple – it just changes the application’s background color. Here is the XML configuration file that the application will load on startup to determine where to apply the interceptor:

<interceptors>
    <interceptor>
        <swfTag>blah/Foo</swfTag>
        <methodEntryInvoker>
            <className>FooInterceptor</className>
            <methodName>interceptAll</methodName>
        </methodEntryInvoker>
    </interceptor>
</interceptors>

In this case it is saying to only apply the interceptor to the SWF tag with the name “blah/Foo”. In a debug version of the application the Foo class from above will be in its own SWF tag named “blah/Foo”. The reason that the SWF tag is specified in this case is because by doing this the application won’t need to deserialize and reserialize every class. The downside to doing things this way is that it won’t work if we create an optimized SWF where all of the frame 2 classes are contained in one SWF tag. With some more work in AS3 Commons Bytecode we could optimize things for this kind of use case. Volunteers? :) The methodEntryInvoker simply specifies the class and method name to call on method entry. This interceptor will be added to every method, on every class in the SWF tag with the name “blah/Foo”.

Now for the fun part… Here is the patcher that loads the XML config file, parses it, and then applies the interceptor:

package patcher {
import flash.events.Event;
import flash.events.TimerEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;
 
import org.as3commons.bytecode.abc.AbcFile;
import org.as3commons.bytecode.abc.InstanceInfo;
import org.as3commons.bytecode.abc.LNamespace;
import org.as3commons.bytecode.abc.MethodInfo;
import org.as3commons.bytecode.abc.Op;
import org.as3commons.bytecode.abc.QualifiedName;
import org.as3commons.bytecode.abc.enum.Opcode;
import org.as3commons.bytecode.io.AbcSerializer;
 
import org.mixingloom.SwfContext;
import org.mixingloom.SwfTag;
import org.mixingloom.invocation.InvocationType;
import org.mixingloom.patcher.AbstractPatcher;
 
import org.as3commons.bytecode.io.AbcDeserializer;
 
public class SampleXMLPatcher extends AbstractPatcher {
 
    public var url:String;
 
    private var swfContext:SwfContext;
 
    public function SampleXMLPatcher(url:String) {
        this.url = url;
    }
 
    override public function apply( invocationType:InvocationType, swfContext:SwfContext ):void {
        if (invocationType.type == InvocationType.FRAME2) {
 
            this.swfContext = swfContext;
 
            var urlLoader:URLLoader = new URLLoader();
            urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
            urlLoader.addEventListener(Event.COMPLETE, handleXMLLoad);
            urlLoader.load(new URLRequest(url));
        }
        else {
            invokeCallBack();
        }
    }
 
    private function handleXMLLoad(event:Event):void {
        var xmlData:XML = new XML((event.currentTarget as URLLoader).data as String);
 
        var swfTagName:String = xmlData.interceptor.swfTag;
        var methodEntryInvokerClassName:String = xmlData.interceptor.methodEntryInvoker.className;
        var methodEntryInvokerMethodName:String = xmlData.interceptor.methodEntryInvoker.methodName;
 
        var methodEntryInvokerClassQName:QualifiedName = new QualifiedName(methodEntryInvokerClassName, LNamespace.PUBLIC);
        var methodEntryInvokerMethodQName:QualifiedName = new QualifiedName(methodEntryInvokerMethodName, LNamespace.PUBLIC);
 
        for each (var swfTag:SwfTag in swfContext.swfTags) {
            if (swfTag.name == swfTagName) {
 
                // skip the flags
                swfTag.tagBody.position = 4;
 
                var abcStartLocation:uint = 4;
                while (swfTag.tagBody.readByte() != 0) {
                    abcStartLocation++;
                }
                abcStartLocation++; // skip the string byte terminator
 
                swfTag.tagBody.position = 0;
 
                var abcDeserializer:AbcDeserializer = new AbcDeserializer(swfTag.tagBody);
 
                var abcFile:AbcFile = abcDeserializer.deserialize(abcStartLocation);
 
                for each (var instanceInfo:InstanceInfo in abcFile.instanceInfo) {
 
                    for each (var methodInfo:MethodInfo in instanceInfo.methodInfo) {
                        var startIndex:uint = 0;
                        for each (var op:Op in methodInfo.methodBody.opcodes) {
                            startIndex++;
                            if (op.opcode === Opcode.pushscope) {
                                break;
                            }
                        }
 
                        var findOp:Op = new Op(Opcode.findpropstrict, [methodEntryInvokerClassQName]);
                        var getOp:Op = new Op(Opcode.getproperty, [methodEntryInvokerClassQName]);
                        var callOp:Op = new Op(Opcode.callproperty, [methodEntryInvokerMethodQName, 0]);
 
                        methodInfo.methodBody.opcodes.splice(startIndex, 0, findOp, getOp, callOp, new Op(Opcode.pop));
                    }
                }
 
                var abcSerializer:AbcSerializer = new AbcSerializer();
                var modifiedBytes:ByteArray = new ByteArray();
                modifiedBytes.writeBytes(swfTag.tagBody, 0, abcStartLocation);
                modifiedBytes.writeBytes(abcSerializer.serializeAbcFile(abcFile));
 
                swfTag.tagBody = modifiedBytes;
            }
        }
 
        invokeCallBack();
    }
}
}

The constructor for this patcher takes a URL, which is used to specify the XML config file. Then in the apply method, if the invocation type is “FRAME2″ (meaning the patcher is being applied to the SWF tags on the second frame of the loading SWF) then it uses URLLoader to load the XML config file. Notice that URLLoader is used, not HTTPService. That is because anything that goes into a patcher is put on the first frame of the SWF and if HTTPService was used here, then there would be a ton of additional classes (dependencies) that would need to also be moved to the first frame. While technically this would work, it’s not a good practice because the more that is on the first frame, the longer the user has to wait before the preloader shows up (remember: all of the frame must be transferred across the network before the frame is loaded into the VM). If the invocation type is not “FRAME2″ then the invokeCallBack method is called to tell Mixing Loom that this patcher is done with the current invocation. Side note: patchers can block for as long as they want. Nothing moves forward in Mixing Loom until a patcher calls the invokeCallBack method.

When the data for the XML file arrives it is parsed using the E4X library. Then new QualifiedName instances are created based on the interceptor’s class and method names. Now the SWF tag with the name specified in the XML file is processed. First it is deserialized by AS3 Commons Bytecode. This provides an object representation of the underlying ABC code contained in the SWF tag. Then for every class and method the interceptor is applied at the beginning of the method. Kinda. There are a few operations that must happen at the very beginning of the method. For each method being intercepted we need to move past the “pushscope” opcode before we can insert new opcodes. Then four new opcodes are spliced into the array of opcodes for the method: findpropstrict, getproperty, callproperty, and pop. Those four opcodes are the ABC equivalent of calling the static method on the specified interceptor class. In this case the rest of the opcodes in the method will be left alone. Finally the ABC is recreated using AS3 Commons Bytecode and the original SWF tag is overwritten and the invokeCallBack method is called.

Just like before we need a custom preloader to register the patchers:

package preloader {
import org.mixingloom.managers.IPatchManager;
import org.mixingloom.preloader.AbstractPreloader;
 
import patcher.SampleXMLPatcher;
 
public class SampleXMLPatcherPreloader extends AbstractPreloader {
 
		override protected function setupPatchers(manager:IPatchManager):void {
			super.setupPatchers(manager);
			manager.registerPatcher( new SampleXMLPatcher("interceptors.xml"));
		}
	}
}

Notice that a new instance of the SampleXMLPatcher is created and given the URL to the interceptor XML file. Here is a little test application containing a button that calls Foo’s getBar method every time it’s clicked:

<?xml version="1.0"?>
<?xml version="1.0"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               preloader="preloader.SampleXMLPatcherPreloader">
 
    <fx:Script>
        import blah.Foo;
        import FooInterceptor; FooInterceptor;
    </fx:Script>
 
 
    <s:Button label="call foo.getBar()" fontSize="32" top="20" horizontalCenter="0">
        <s:click>
                var foo:Foo = new Foo();
                foo.getBar();
        </s:click>
    </s:Button>
 
</s:Application>

Notice that since there wasn’t a reference anywhere else to the FooInterceptor we had to include one manually otherwise it will not exist in the compiled SWF. Here is a demo of that application:

Well, that was fun! And I hope you can see how Mixing Loom can be the foundation for doing AOP in Flex / ActionScript! But before I let you go I want to show you one more crazy thing we can do with Mixing Loom. Patchers can do just about anything they want since Mixing Loom provides hooks to modify the second frame, RSLs, and Modules. For instance, say there is a private method or property in the Flex framework that you need access to. One option is to use Monkey Patching to replace that class with one that you maintain. This is not a very maintainable way to get access to something that is private. Using Mixing Loom you can simply patch the class at runtime. Here is a simple (but impractical) example… The spark.components.Application class has a private method called “debugTickler” on it. Using the RevealPrivatesPatcher from Mixing Loom we can make that method public at runtime. First extend the base RevealPrivatesPatcher class and tell it only to apply the patcher on the “spark_” RSL:

package {
import org.mixingloom.SwfContext;
import org.mixingloom.invocation.InvocationType;
import org.mixingloom.patcher.RevealPrivatesPatcher;
 
public class MyRevealPrivatesPatcher extends RevealPrivatesPatcher {
 
    override public function apply( invocationType:InvocationType, swfContext:SwfContext ):void {
        if ((invocationType.type == InvocationType.RSL) && (invocationType.url.indexOf("spark_") >= 0)) {
            super.apply(invocationType, swfContext);
        }
        else {
            invokeCallBack();
        }
    }
}
}

Then create a custom preloader that registers a new instance of MyRevealPrivatesPatcher and tells it to reveal the “spark.components:Application” class and the “debugTickler” method:

package preloader {
import org.mixingloom.managers.IPatchManager;
import org.mixingloom.preloader.AbstractPreloader;
 
	public class RevealPrivatesPatcherPreloader extends AbstractPreloader {
 
		override protected function setupPatchers( manager:IPatchManager ):void {
			super.setupPatchers( manager );
			manager.registerPatcher( new MyRevealPrivatesPatcher("spark.components:Application", "debugTickler") );
		}
 
	}
}

Finally, use the LoomApplication and the custom preloader in order to have the hooks to patch RSLs:

<?xml version="1.0"?>
<ml:LoomApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                    xmlns:ml="library://ns.mixingloom.org/flex/spark"
                    xmlns:s="library://ns.adobe.com/flex/spark"
                    preloader="preloader.RevealPrivatesPatcherPreloader">
 
    <ml:applicationComplete>
            try {
                this['debugTickler']();
                l.text = "Yeah.  We just modified an RSL at runtime.";
            } catch (e:Error) {
                l.text = "booo";
            }
    </ml:applicationComplete>
 
    <s:Label id="l" text="nothing happened."/>
 
</ml:LoomApplication>

Notice that we can’t use the dot syntax “this.debugTickler()” to call the method since the compiler won’t let us do that. Instead we have to use the object key syntax “this[‘debugTickler’]()” in order to make the method call. Now watch as Mixing Loom’s magic wand modifies a signed Flex Framework RSL right before your very eyes:

Fun stuff!!! And there is more to come! We are working on ways to also modify the first frame of the SWF and to modify a SWF pre-runtime. But now it’s your turn! All of the code for everything you’ve seen here, as well as some other demos, and goodies is all on github. We’d love to see the community create some interesting and useful patchers! So fork away and have fun! Let me know if you have any questions.

P2P in Desktop, Mobile, and Tablet Flex Apps

Using the open source Flex SDK, developers can easily build desktop, mobile, and tablet applications that use Peer to Peer (P2P) communication. I’ve created a video that walks through demos and code illustrating how to use the P2P APIs in Adobe AIR applications. Check it out:

Grab the code for the demos in the video from github:

Just as the video shows, it’s incredibly easy to use the P2P APIs. Here is a quick walk through. First create a new NetConnection that is connected to “rtmfp:” like so:

localNc = new NetConnection();
localNc.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
localNc.connect("rtmfp:");

In the netStatus event listener wait for the “NetConnection.Connect.Success” event and then set up the NetGroup:

private function netStatus(event:NetStatusEvent):void
{                        
    switch(event.info.code)
    {
        case "NetConnection.Connect.Success":
            setupGroup();
            break;
    }
}
 
private function setupGroup():void
{
    var groupspec:GroupSpecifier = new GroupSpecifier(GROUP_ID);
    groupspec.ipMulticastMemberUpdatesEnabled = true;
    groupspec.multicastEnabled = true;
    groupspec.routingEnabled = true;
    groupspec.postingEnabled = true;
 
    // This is a multicast IP address. More info: http://en.wikipedia.org/wiki/Multicast_address
    groupspec.addIPMulticastAddress("239.254.254.2:30304");
 
    netGroup = new NetGroup(localNc, groupspec.groupspecWithAuthorizations());
    netGroup.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
}

Now handle a “NetGroup.SendTo.Notify” event when a message is received over the P2P connection:

// in the netStatus switch block
case "NetGroup.SendTo.Notify":
    var data:Object = event.info.message;
    break;

And finally to send a P2P message to everyone who is listening simply do:

netGroup.sendToAllNeighbors(objectToSend);

That’s it! Super simple and super fun! :)

Thanks to Tom Krcha for all of his great blogs on how to do this. Also thanks to Mark Dong and James Li (Flash Platform Evangelists in China) for helping me build P2Pong.

Install Adobe AIR on 64-bit Ubuntu 10.10

Right now Adobe AIR is only officially available for 32-bit Linux. But it does work on 64-bit Linux with the 32-bit compatibility libraries. There are several ways to install Adobe AIR on Linux. My preferred way on Ubuntu is to use the .deb package. However the .deb package distributed by Adobe can only be installed on 32-bit systems. Good news is that this can be easily fixed! To install the Adobe AIR .deb package on a 64-bit system just follow these steps:

  1. Download the Adobe AIR .deb file
  2. In a terminal window go to the directory containing the adobeair.deb file
  3. Create a tmp dir:
    mkdir tmp
  4. Extract the deb file to the tmp dir:
    dpkg-deb -x adobeair.deb tmp
  5. Extract the control files:
    dpkg-deb --control adobeair.deb tmp/DEBIAN
  6. Change the Architecture parameter from “i386″ to “all”:
    sed -i "s/i386/all/" tmp/DEBIAN/control
  7. Repackage the deb file:
    dpkg -b tmp adobeair_64.deb

Now you can install Adobe AIR on a 64-bit system! From the command line just do:

sudo dpkg -i adobeair_64.deb

That’s it! Let me know if you have any questions.

Install Flash Player on 64-bit Linux

UPDATE: This video was intended to show how to upgrade Flash to a new / experimental version. If you do not have Flash and want to see the video, first switch YouTube to default to HTML5 video and then watch this video on YouTube.

Adobe has just released a preview of a 64-bit Flash Player for Windows, Mac, and Linux! I created a quick video to show Linux users how to install Flash Player on a 64-bit system. Check it out:

Let me know how Flash Player “Square” works for you!

Building Client / Cloud Apps with Flash Builder for Force.com

I have a theory. The majority of people who use enterprise software today use old school Client / Server apps. We’ve been trying to move these apps to the web for more than ten years. The ease of deployment of web apps is a clear motivator. Yet the client capabilities of the plain old web browser have not been sufficient for many apps to make the leap. This is why I love Flex and the Flash Platform. It provides a way to use web technologies and the web deployment model but adds many of the critical things needed for mission critical apps that people use all day long.

But no one wants to go back to the Client / Server architecture. We want to embrace Cloud Computing architectures but not lose the client capabilities. What we really need is the Client / Cloud architecture. We need a web deployment model that provides ease of deployment but also the ability to install applications on our desktops and mobile devices.

This is why I’m so excited about the new Adobe Flash Builder for Force.com. In a nutshell this is a tool that Adobe and Salesforce.com built together to enable developers to build great software using Flex for the UI and Force.com for the Cloud back-end. It’s a wonderful combination of technologies that will help many Client / Server apps make the switch to Client / Cloud.

Applications created with Flash Builder for Force.com can be run in the browser, on the desktop, and on mobile devices. These applications can be assembled from the hundreds of Flex components that are out there (check out many of them in Tour de Flex).

Check out this video to see how to use Flash Builder for Force.com to build a simple app:

As you can see, it’s very easy to get started. But I wanted to go a step further and try to build something real–something that shows a genuine use case for extending beyond the out-of-the-box Salesforce.com UI. I wanted to keep it really simple so that I could post the code here. What I came up with is this (in user story form):

  • As a Salesforce.com user I want to take a photo, using my phone, of one of my contacts so that the photo can be saved to their contact record for future reference.
  • As a Salesforce.com user I want to see photos I’ve taken of my contacts so that I can be reminded of what they look like.

Simple enough. So here is what I came up with:

To build these two apps I first downloaded and installed Flash Builder for Force.com. I used the Adobe AIR for Android prerelease to build the mobile app. Here is how I created these apps.

First I added a new field to Contact to store the photo. Salesforce.com doesn’t have a binary field so I used a large text field (32k limit). I’ll store the photo Base64 encoded.

Then in Salesforce.com I saved my enterprise.wsdl file. Check out a great video from Dave Carroll to see how to do this.

Now in Flash Builder for Force.com I created a new Force.com Flex Project for the mobile app. If you do this on your own and want to run on a mobile device then you will need to overlay the AIR for Android SDK on top of a Flex 4.1 SDK. Select Desktop Application as the app type. Replace WindowedApplication with just Application. And replace the F3DesktopApplication with F3WebApplication since F3DesktopApplication uses APIs that are not available on AIR for Android. (BTW: Flash Builder, Flex, and Force.com Flex Projects do not officially support mobile deployment yet. It works but there is no support and no guarantees.) If you are building a standard Web Application or Desktop Application then you can just leave the generated code as is.

Using the Data/Services wizard I connected to Salesforce.com using my enterprise.wsdl file. After the services and value objects have been generated I modified the Contact object and added a Bindable account property. The generated application already included the F3DesktopApplication Declaration used to connect to Salesforce. Due to an incompatibility with that API and AIR for Android I switched it to use F3WebApplication. In F3WebApplication’s loginComplete event handler I query Salesforce.com for Accounts and then Contacts, associate contacts with their account, and then store the contacts:

app.wrapper.query("select Id, Name from Account", new AsyncResponder(function(data:ArrayCollection, token:Object):void {
    accounts = data;
    app.wrapper.query("select Id, AccountId, FirstName, LastName, Phone, MobilePhone, Email, Title, Department, MailingCity, photoData__c from Contact", new AsyncResponder(function(data:ArrayCollection, token:Object):void {
        for each (var contact:Contact in data)
        {
            for each (var account:Account in accounts)
            {
                if (account.Id == contact.AccountId)
                {
                    contact.account = account;						
                }
            }
        }
        contacts = data;
    }, handleError));
}, handleError));

Notice in the query that I’m fetching photoData__c, which is the custom field I created on Contact to store the photo.

In the renderer for a contact I need to either display the photo if there is one or let the user add one. Here is the simple UI code to handle that:

<s:Group width="92" height="92" top="8" right="8">
    <s:Rect width="92" height="92">
        <s:fill>
            <s:SolidColor color="#cccccc"/>
        </s:fill>
    </s:Rect>
    <s:Label id="addPhoto" text="Add a photo" width="92" height="92" verticalAlign="middle" textAlign="center"/>
    <s:BitmapImage id="photo" width="92" height="92"/>
</s:Group>

When the contact is set I check to see if there is a photo and if so display it:

if (contact.photoData__c == null)
{
    photo.visible = false;
    return;
}
 
var decoder:Base64Decoder = new Base64Decoder();
decoder.decode(contact.photoData__c);
 
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(event:Event):void {
    photo.source = event.target.content;
    photo.visible = true;
});
loader.loadBytes(decoder.toByteArray());

The data from the photoData__c field is Base64 decoded and then displayed using the Flex BitmapImage component.

Now when the user clicks on the photo or empty photo box I use the AIR for Android CameraUI to grab a photo, resize it, covert it to a PNG, Base64 encode it, set it on the contact, and then save the contact to Salesforce.com:

if (CameraUI.isSupported)
{
    cameraUI = new CameraUI();
    cameraUI.addEventListener(MediaEvent.COMPLETE, function(event:MediaEvent):void {
        var loader:Loader = new Loader();
        loader.load(new URLRequest(event.data.file.url));
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(event:Event):void {
            var bitmap:Bitmap = event.target.content as Bitmap;
 
            var result:BitmapData = new BitmapData(46, 46, false);
            var matrix:Matrix = new Matrix();
            matrix.scale(46 / bitmap.width, 46 / bitmap.height);
            result.draw(bitmap, matrix);
 
            var pngEncoder:PNGEncoder = new PNGEncoder();
            var pngBytes:ByteArray = pngEncoder.encode(result);
 
            var base64Encoder:Base64Encoder = new Base64Encoder();
            base64Encoder.encodeBytes(pngBytes);
            var encodedImage:String = base64Encoder.flush();
 
            contact.photoData__c = encodedImage;
 
            displayPhoto();
 
            F3WebApplication.getInstance().wrapper.save(contact, new AsyncResponder(function(data:Object, token:Object=null):void {
            }, FlexGlobals.topLevelApplication.handleError));
        });
    });
    cameraUI.addEventListener(ErrorEvent.ERROR, function(event:ErrorEvent):void {
        FlexGlobals.topLevelApplication.handleError(event);
    });
    cameraUI.launch(MediaType.IMAGE);
}

That’s it for the mobile app! I compiled it, exported it to an Android app, and then copied it to my phone. Pretty simple and as you can see it works! One limitation with my approach is the 32k limit of the photoData__c field. However, I think I could easily get around that by striping the Base64 encoded data across multiple fields. It’s not ideal but it would work.

To display the photo when I view a contact on Salesforce.com I created a very simple Flex app using another Force.com Flex Project. I could have also added photo upload to this application but chose to keep it simple. All it does is display the selected contact’s photo. Here is the complete code (after generating the required services in Flash Builder):

<?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"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:flexforforce="http://flexforforce.salesforce.com">
 
    <fx:Script>		
    import mx.rpc.AsyncResponder;
    import mx.utils.Base64Decoder;
    </fx:Script>
 
    <fx:Declarations>
        <flexforforce:F3WebApplication id="app" requiredTypes="Contact">
            <flexforforce:loginComplete>
                app.wrapper.query("select photoData__c from Contact where Id = '" + this.parameters.contactId + "'", new AsyncResponder(function(data:Object, token:Object):void {
                    if (data.length == 1)
                    {
                        if (data[0].photoData__c == null)
                        {
                            photo.visible = false;
                            noPhoto.visible = true;
                            return;
                        }
 
                        var decoder:Base64Decoder = new Base64Decoder();
                        decoder.decode(data[0].photoData__c);
 
                        var loader:Loader = new Loader();
                        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(event:Event):void {
                            photo.source = event.target.content;
                            photo.visible = true;
                        });
                        loader.loadBytes(decoder.toByteArray());
                    }
                }, function(fault:Object):void {
                    // ignored
                }));
            </flexforforce:loginComplete>
        </flexforforce:F3WebApplication>
    </fx:Declarations>
 
    <s:applicationComplete>
        app.serverUrl = this.parameters.serverUrl;
        app.loginBySessionId(this.parameters.sessionId);
    </s:applicationComplete>
 
    <s:Rect width="92" height="92">
        <s:fill>
            <s:SolidColor color="#cccccc"/>
        </s:fill>
    </s:Rect>
 
    <s:Label id="noPhoto" text="No Photo" width="92" height="92" textAlign="center" verticalAlign="middle" visible="false"/>
 
    <s:BitmapImage id="photo" width="92" height="92"/>
 
</s:Application>

Finally I created a custom S-Control to run the Flex app:

<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="ContactPhoto" width="92" height="92" 
codebase="https://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"> 
    <param name="movie" value="{!Scontrol.JavaArchive}" /> 
    <param name="flashvars" value="sessionId={!API.Session_ID}&serverUrl={!API.Partner_Server_URL_90}&contactId={!Contact.Id}" /> 
    <embed src="{!Scontrol.JavaArchive}" width="92" height="92" name="ContactPhoto" 
flashvars="sessionId={!API.Session_ID}&serverUrl={!API.Partner_Server_URL_90}&contactId={!Contact.Id}" 
type="application/x-shockwave-flash" 
pluginspage="http://www.adobe.com/go/getflashplayer"> 
    </embed> 
</object>

I uploaded the compiled Flex app to the S-Control and added it to the Contact page. And that’s it! In just a few hours I extended Force.com and built a cool mobile app. I could also have easily created a desktop widget for browsing contacts and adding photos. If you are looking for a fun project to use as a way to learn this stuff that would be a good one! :)

Here are some resources to help you get started with Flash Builder for Force.com:

Have fun building the next generation of software! Let me know how it goes.

Developing Mobile Flash / Flex – Scaling and Zooming

Mobile development with Flash and Flex is a new frontier, full of new adventures and discoveries. Recently I discovered something that might be useful to you. By default the mobile web browser on my Android 2.2 device scales a web page to make more room to display pages typically built for a desktop profile. Here is what a little test mobile Flex app I built looks like:

Strange! The width and height are larger than the screen resolution. You can fix this by adding the following to the HTML page:

<meta name="viewport" content="target-densityDpi=device-dpi; user-scalable=0;" />

And now it will look like:

Now that looks right! This also turns off the two-finger / pinch zooming feature for the page (which is more important for content that hasn’t been optimized for a mobile profile).

I hope this is useful for those of you embarking on new adventures with Flash / Flex on mobile devices!

amf.js – A Pure JavaScript AMF Implementation

I just finished the first version of a new pure JavaScript library for AMF. I’ve wanted to do this for a while but didn’t think it was possible since JavaScript doesn’t have a ByteArray. But then I came across this: “High Performance JavaScript Port of ActionScript’s ByteArray“. That became the basis for amf.js. Before I get into the gory details of how this works, check out some developer eye candy:
http://www.jamesward.com/demos/JSAMF/censusTest.html

Ok, hopefully that worked for you. I’ve tested this in the latest Chrome, Firefox, Safari, and IE and they all seem to work. It should also work on your iPad, iPhone, or Android device.

Now for those gory details… AMF is a protocol initially created in Flash Player as a way to serialize data for storage on disk or transfer over a network. Typically in web apps we use text-based serialization protocols (like JSON or RESTful XML) for data transfer. But there are some advantages to using binary protocols – primarily much better performance. There are two versions of the AMF protocol, AMF0 and AMF3. Both are publicly documented by Adobe and numerous server-side implementations of AMF exist. AMF is just a serialization technology, not a transport. So you can put AMF encoded data into any transport (like HTTP / HTTPS). Typically Flash Player is the client that reads / writes AMF data.

I recently had a conversation with Stephan Janssen who runs Parleys.com (an amazing Flex app), which started me on this fun project. The Parleys.com PC-profile web client and the Adobe AIR desktop client both use BlazeDS and AMF as the primary serialization protocol for moving data between client and server. This is a great choice for those clients because it makes the apps snappy. But for the HTML5 client Stephan wants to reuse his AMF endpoints. This is where amf.js comes in.

Flash Player has a ByteArray API that can be used for a lot of amazing things. One of those things is to read and write AMF. If you have an object in Flash Player and you create a new ByteArray and then call “byteArray.writeObject(myObject)” you will get a ByteArray with the AMF representation of that object. Likewise if you get some AMF and you call “byteArray.readObject()” you get the object(s) from the AMF. In Flex there are high level APIs (like RemoteObject, Consumer, etc.) that use this native AMF support in Flash Player.

To create a pure JavaScript AMF library the first thing that is needed is a pure JavaScript ByteArray library since JavaScript doesn’t natively have one. I used one from adamia.com since it was similar to the ByteArray in Flash Player, seemed fast, and seemed to parse floats correctly. This ByteArray has some of the basic functions like readByte, readFloat, etc. But what about that cool readObject function? Well, that has to be built from scratch. And it should support both AMF0 and AMF3.

Using the AMF specs and code from BlazeDS & pyamf as a reference I was able to add the other functions to the ByteArray. But there was a problem. Using XMLHttpRequest as the method of getting the AMF was not working right. Some bytes were incorrect. It turns out XMLHttpRequest uses UTF-8 and that screws up some of the bytes above 128. I tried other charsets and each one would change some range of bytes. That is not good because I need the bytes to be exactly what the server sent. Then I came across this gem:

//XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
req.overrideMimeType('text/plain; charset=x-user-defined');

Using the “x-user-defined” charset left the bytes alone. Perfect! Except that IE doesn’t support the req.overrideMimeType function. But IE does actually have a real ByteArray available in req.responseBody via VBScript. For now in IE I just change the ByteArray into a string (like req.responseText in the other browsers) although a lot of optimization could be done to just use the VBScript ByteArray directly.

Right now amf.js is just a basic JavaScript library for reading AMF data. It doesn’t support using a BlazeDS MessageBrokerServlet yet because I need to be able to assemble a AMF object in JavaScript and send that in the HTTP request to the servlet. But it works fine with a custom servlet that uses BlazeDS’s AMF library to just write AMF into the HTTP response. It should also work with pyamf, AMFPHP, and other AMF server libraries.

To use amf.js start by dumping some AMF into an HTTP response. In Java with BlazeDS I did this:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
    response.setHeader("Content-Type", "application/x-amf;charset=x-user-defined");
    ServletOutputStream out = response.getOutputStream();
    ActionMessage requestMessage = new ActionMessage(MessageIOConstants.AMF3);
    MessageBody amfMessage = new MessageBody();
    amfMessage.setData(someSerializableObject);
    requestMessage.addBody(amfMessage);
    AmfMessageSerializer amfMessageSerializer = new AmfMessageSerializer();
    amfMessageSerializer.initialize(SerializationContext.getSerializationContext(), out, new AmfTrace());
    amfMessageSerializer.writeMessage(requestMessage);
    out.close();
}

In a HTML web page add the amf.js script:

<script type="text/javascript" src="amf.js"></script>

In JavaScript make a XHR request for some data:

var url = "TestServlet";
var req;
 
function getAMF()
{
    if (window.ActiveXObject)
    {
        req = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest)
    {
        req = new XMLHttpRequest();
        //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
        req.overrideMimeType('text/plain; charset=x-user-defined');
    }
    req.onreadystatechange = processReqChange;
    req.open("GET", url, true);
    req.send(null);
}

And when the response comes back decode the AMF:

function processReqChange()
{
    if (req.readyState == 4)
    {
        if (req.status == 200)
        {
            var o = decodeAMF(req.responseText).messages[0].body;
        }
        else
        {
            alert("There was a problem retrieving the data:\n" + req.statusText);
        }
    }
}

For details on how to support IE, check out the source code for censusTest.html.

While amf.js works in my tests there is more work to be done. I need to add the write functions so that AMF can be sent to the server. Then supporting BlazeDS’s MessageBrokerServlet should be pretty straightforward. I’d also like to create pure JavaScript implementations of Flex’s RemoteObject, Consumer, and Producer APIs. Also, I need people to test amf.js with their AMF to make sure that I’ve implemented things correctly. All of the code is on github.com so go ahead and fork it! Let me know what you think.