Home  >  

AIR 2 Enhancements Complete Overview

Author photo
October 7, 2009 | | Comments (12)
AddThis Social Bookmark Button
Adobe just released public information about AIR 2.0 and added new capabilities that better tie with the operation systems which gives your application more control while increasing performance.

The new version will be deployed on Flash player 10 using Flex 4 SDK. The new features can be split into four main categories:

  1. New functionality
  2. Additional functionality to existing APIs
  3. Platform awareness related to APIs
  4. Optimization
Click the image to open a Flex application. To use the application, click each main category and browse to see the capability. higherlevel-arct.png

I would like to discuss these new features in more detail to give you a better understanding of what is possible with AIR 2.0 and how you can take advantage of these new features in building new applications or integrating these features into existing applications.

New functionality in AIR 2.0

AIR 2.0 adds new functionality that was not included in previous versions of AIR. They include:

  • New networking support - new socket class, wired and a wireless network interfaces, and DNS records.
  • Native processes - API that allows launching and interacting with native processes.
  • Screen reader - API that supports screen reader.
  • File Promises - API that allows you to drag virtual files to local file systems.

Additional Networking Support


IPv6 Support

AIR 2.0 adds the ability to support IPv6 to all network APIs. Currently, the Internet mostly uses IP version four (IPv4) which addresses format that is 20 years old. IPv4 uses a 32-bit architecture and is limited in the available address space to 4,294,967,296 or 232 unique IP addresses. Since many devices are consuming IP addresses, IPv6 was created to ensure that we do not run out of IP addresses. IPv6 is not as common as IPv4, and it is currently being used in some tests and early adopter networks. However, it is important to be able to access the IPv6 for networks that are using these IP addresses.

User Datagram Protocol

AIR 2.0 adds a new socket class in addition to the existing Socket class. The existing class allows the ability to access Transmission Control Protocol (TCP). In AIR 2.0 you can use a stateless protocol called User Datagram Protocol (UDP) with a new class called DatagramSocket class. DatagramSocket class allows the sending and receiving UDP packets.

Unlike TCP, UDP uses a transmission method that does not include implicit handshake and does not guarantee reliability, ordering, or data integrity. The UDP limitations mean that some data may be lost, out of order, duplicated, or missing. The advantage of the UDP protocol is that much of the overhead associated with processing is avoided, and it allows building applications, such as VoIP applications, in which it’s acceptable to lose data and speed is a key.

Another main advantage UDP has over TCP is that UDP servers can handle more requests from more clients. There are two classes that you can use:

  • flash.net.DatagramSocket - DatagramSocket, very similar to Socket class, provides the ability to send and receive UDP packets.
  • flash.net.ServerSocket - ServerSocket class that provides the server-side TCP socket methods
Network Information

A machine may have a wired and a wireless network interface, and AIR 2.0 allows you to access the machine’s information through a class called NetworkInfo. NetworkInfo class can be used to enumerate all the network interfaces on a machine.

The NetworkInfo class finds information about the machine’s network interfaces, such as the local IP address and wired or wireless networks. The application below finds all the interfaces on the machine and the IP addresses.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       initialize="initializeHandler()">
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import flash.net.NetworkInterface;
            import flash.net.NetworkInfo;
            
            private var arrayCollection:ArrayCollection = new ArrayCollection();
            
            protected function initializeHandler():void
            {
                 var networkInfo:NetworkInfo = new NetworkInfo();
                 var networkInterfaces:Vector.<NetworkInterface> = networkInfo.findInterfaces();
                 
                 networkInterfaces.forEach( function(element:NetworkInterface, index:int, arr:*):void
                 {
                      var object:Object = new Object();
                      object.active = element.active;
                      object.name = element.name;
                      
                      if ( element.addresses.length > 0 )
                          object.addresses = element.addresses[0].address;
                      else
                          object.addresses = "";
                      
                      arrayCollection.addItem( object );
                  });
                 
                 dataGrid.dataProvider = new ArrayCollection(arrayCollection.source);
             }

        ]]>
    </fx:Script>
    
    <mx:DataGrid id="dataGrid" width="400" height="300">
        <mx:columns>
            <mx:DataGridColumn dataField="name" />
            <mx:DataGridColumn dataField="active" width="80" />
            <mx:DataGridColumn dataField="addresses" />
        </mx:columns>
    </mx:DataGrid>
    
</s:WindowedApplication>
DNS Records

AIR 2.0 offers an API flash.net.dns.DNSResolver that allows the making of queries to find out Domain name system (DNS) information. Once information has been found, DNSResolverEvent is dispatched.

To understand how to use the new API you must first understand what DNS is and how it works. Each domain name on the web is pointing to an IP address. DNS converts domain names into IP addresses. In other words, the DNS allows the translation of the information from host name into IP addresses.

The IP address can be of type IPv4 (32-bits) or IPv6 (64-bits). AIR supported IPv4, and all the classes in AIR 2.0 are now supporting IPv6 protocol.

There are many record types that are stored in the resource records on DNS, and they include IPv4 address record, IPv6 address record, delegation name, DNS key records, and many others.

In this release AIR only supports the following DNS types:
  • flash.net.dns.ARecord - Class that returns 64-bits IPv6 address information. AAAA records are usually used to convert hostnames into IP addresses. flash.net.dns.AAAARecord - Same as ARecord but used to return a 32-bits IPv4 address.
  • flash.net.dns.MXRecord - Class that provides information regarding mapping a domain name to a list of mail exchange (MX) servers for that domain.
  • flash.net.dns.SRVRecord - SRV class that returns service location record information about the SRV records. It is used for newer protocols instead of creating protocol-specific records such as MX.
  • flash.net.dns.ResourceRecord - Data class for encapsulating the information in a DNS record.
  • flash.net.dns.PTRRecord - Class to allow accessing PTR record information. PTR record is usually used for performing reverse DNS lookups.
Take a look at the class below which performs DNS and reverse DNS lookup.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       height="287" width="492">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import flash.net.dns.SRVRecord;
            import flash.net.dns.PTRRecord;
            import flash.net.dns.MXRecord;
            import flash.net.dns.ARecord;
            import flash.events.Event;
            import flash.net.dns.AAAARecord;
            import flash.events.DNSResolverEvent;
            import flash.net.dns.DNSResolver;
            
            private var resolver:DNSResolver;
            
            public function startLookup( host:String, isARecordSelected:Boolean, isAAAARecordSelected:Boolean, isMXRecordSelected:Boolean, isSRVRecordSelected:Boolean ):void
            {
                 if (isARecordSelected) lookup( host + ".", ARecord );
                 if (isAAAARecordSelected) lookup( host, AAAARecord );
                 if (isMXRecordSelected) lookup( host, MXRecord );
                 if (isSRVRecordSelected) lookup( "_sip._tcp." + host + ".", SRVRecord );
             }
            
            public function lookup( host:String, recordType:Class):void
            {
                 resolver = new DNSResolver();
                 resolver.addEventListener( DNSResolverEvent.LOOKUP, lookupComplete );
                 resolver.addEventListener( ErrorEvent.ERROR, lookupError );
                 
                 resolver.lookup( host, recordType );
             }
            
            private function lookupComplete( event:DNSResolverEvent ):void
            {
                 resolver.removeEventListener( DNSResolverEvent.LOOKUP, lookupComplete );
                 resolver.removeEventListener( ErrorEvent.ERROR, lookupError );                
                 
                 setOutput( "Query string: " + event.host );
                 setOutput( "Record type: " +  flash.utils.getQualifiedClassName( event.resourceRecords[0] ) +
                     ", count: " + event.resourceRecords.length );
                 
                 for each( var record:* in event.resourceRecords )
                 {
                      if( record is ARecord ) setOutput( "ARecord: " + record.name + " : " + record.address );
                      if( record is AAAARecord ) setOutput( "AAAARecord: " + record.name + " : " + record.address );
                      if( record is MXRecord ) setOutput( "MXRecord: " + record.name + " : " + record.exchange + ", " + record.preference );
                      if( record is PTRRecord ) setOutput( "PTRRecord: " + record.name + " : " + record.ptrdName );
                      if( record is SRVRecord ) setOutput( "SRVRecord: " + record.name + " : " + record.target + ", " + record.port +
                          ", " + record.priority + ", " + record.weight );
                  }
             }
            
            private function setOutput(message:String):void
            {
                 resolver.removeEventListener( DNSResolverEvent.LOOKUP, lookupComplete );
                 resolver.removeEventListener( ErrorEvent.ERROR, lookupError );    
                 
                 output.text = message + "\n" + output.text;
             }
            
            private function lookupError( error:ErrorEvent ):void
            {
                 Alert.show("Error: " + error.text );
             }

        ]]>
    </fx:Script>
    
    <s:TextInput id="DomainTextInput" x="129" y="55" text="yahoo.com"/>
    
    <s:Button x="280" y="55" label="Lookup DNS Information" 
              click="startLookup(DomainTextInput.text, checkBoxARecord.selected, checkBoxAAAARecord.selected, 
                  checkBoxMXRecord.selected, checkBoxSRVRecord.selected)"/>
    
    <s:CheckBox id="checkBoxARecord" x="28" y="85" label="ARecord" selected="true"/>
    <s:CheckBox id="checkBoxAAAARecord" x="101" y="85" label="AAAARecord"/>
    <s:CheckBox id="checkBoxMXRecord" x="196" y="85" label="MXRecord"/>
    <s:CheckBox id="checkBoxSRVRecord" x="276" y="85" label="SRVRecord"/>
    
    <s:TextInput id="ipAddress" x="129" y="120"/>
    <s:Button x="280" y="120" label="Reverse DNS Lookup" width="152"
              click="lookup(ipAddress.text, PTRRecord )"/>
    
    <s:SimpleText x="28" y="21" text="DNS Lookup:" 
                fontSize="21"/>
    
    <s:TextArea id="output" x="28" y="158" width="399" height="104" text=""/>
    <s:RichText x="28" y="58" text="Host name: " height="15" fontSize="16"/>
    <s:RichText x="28" y="123" text="IP Address:" height="15" fontSize="16"/>
    
</s:WindowedApplication>

temp.png

Launching and Interacting with Native Processes



Similar to opening a file using default application, AIR 2.0 provides the ability for applications to launch native processes and interact with them. The following classes add support for these capabilities.
  • flash.desktop.NativeProcess - provides command line integration and general launching capabilities on the host OS. Once a process is launched the AIR application can monitor the standard input, output and error of the process.
  • flash.desktop.NativeProcessStartupInfo - provides basic information used to start a process on the host OS.
  • flash.events.NativeProcessExitEvent - event dispatched once process exits. It is possible that this event will never be dispatched if the child process outlives the AIR application.
This feature will only be available for applications that are installed using a native OS installer.

For the beta release you need to make sure you have the following tag in your descriptor:

<supportedProfiles>extendedDesktop</supportedProfiles>


The application below opens up a text file: foobar.txt using a text editor.


<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo">
    <fx:Script>
        <![CDATA[
            import flash.events.NativeProcessExitEvent;

            public function executeNativeProcess():void 
            {
                 var executable:File = new File("/Applications/TextEdit.app/Contents/MacOS/TextEdit");
                 var workingDirectory:File = new File("/");
                 
                 var nativeProcess:NativeProcess = new NativeProcess();
                 
                 if (NativeProcess.isSupported) 
                 {
                      trace("Native Process Supported");
                  }
                 
                 var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
                 nativeProcessStartupInfo.executable = executable;
                 nativeProcessStartupInfo.workingDirectory = workingDirectory;
                 
                 var args:Vector.<String> = new Vector.<String>();   
                 args.push("/Users/Elad/Desktop/foobar.txt"); // open file that was given with the executable application 
                 nativeProcessStartupInfo.arguments = args;
                 
                 nativeProcess.addEventListener( NativeProcessExitEvent.EXIT, onExitError );
                 
                 try {
                      nativeProcess.start(nativeProcessStartupInfo);
                  } catch (error:IllegalOperationError) {
                      trace("Illegal Operation: "+error.toString());
                  } catch (error:ArgumentError) {
                      trace("Argument Error: "+error.toString());
                  } catch (error:Error) {
                      trace ("Error: "+error.toString());
                  }
                 
                 if (nativeProcess.running) 
                 {
                      trace ("Native Process Support");
                  }       
             }
            
            public function onExitError(event:NativeProcessExitEvent):void 
            {
                 trace( "Native Process Exit code: "+event.exitCode );
             }            
            
        ]]>
    </fx:Script>
    
    <s:Button id="button"
              label="Open File foobar.txt with text editor"
              click="executeNativeProcess();"
              width="250" />
    
</s:WindowedApplication>

Screen Reader Support

AIR 2.0 allows support for building Flash-based applications that work with screen readers. The functionality is available on Windows OS only at this release. The following support has been added.
  • Runtime dialog boxes - Dialog boxes are readable by supported screen readers.
  • Flex components and containers - Flex components and containers are readable by screen readers.
Note that “Generate accessible SWF file” option in Flex Builder must be turned on in order to enable support for accessibility.
  1. Right-click on your Flex project (in the Flex Navigator), and select "Properties".
  2. Select "Flex Compiler".
  3. Select the "Generate accessible SWF" check box, and then click the "OK" button.

File promises API

File promises is a new API (URLFilePromise) that allows you to access resources at a certain URL and drag them out of the AIR application as a file promise into the local machine. Once the files are dropped the request will be made to download the file to your local machine.

URLFilePromise class implements the contract of IFilePromise using URLStream and URLRequest objects as the data source.

To create a URL file promise I create a URLFilePromise object, add the object to an array, and then start the drag passing the Clipboard object with the array of file promises. See below.


var items:Array = fileData.selectedItems;
var promises:Array = new Array();
                
for each (var item:Object in items) 
{
      var filePromise:URLFilePromise = new URLFilePromise();
      var request:URLRequest = new URLRequest(item.url);
                     
       filePromise.request = request;
       filePromise.relativePath = item.name;
       promises.push(filePromise);
  }
                
clipboard.setData(ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises);
NativeDragManager.doDrag(fileData, clipboard);     
When the user completes the drag task and drops the file on the local machine, the runtime downloads the data for each file promise.

Take a look at the application below which allows the dragging of items from a list into the user’s local machine and then the copying of the files.


<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo">
    <fx:Script>
        <![CDATA[
            import flash.desktop.URLFilePromise;
            import flash.desktop.NativeDragManager;
            import flash.desktop.ClipboardFormats;
            import flash.desktop.Clipboard;

            private var clipboard:Clipboard = new Clipboard();
            
            protected function onDragOut(event:MouseEvent):void
            {
                 var items:Array = fileData.selectedItems;
                 var promises:Array = new Array();
                 
                 for each (var item:Object in items) 
                 {
                      var filePromise:URLFilePromise = new URLFilePromise();
                      var request:URLRequest = new URLRequest(item.url);
                      
                      filePromise.request = request;
                      filePromise.relativePath = item.name;
                      promises.push(filePromise);
                  }
                 
                 clipboard.setData(ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises);
                 NativeDragManager.doDrag(fileData, clipboard);     
             }
            
            private function onDragOutComplete(event:NativeDragEvent):void
            {
             }            

        ]]>
    </fx:Script>

    <fx:Declarations>
        <mx:ArrayCollection id="arrColl">
            <mx:source>
                <fx:Array>
                    <fx:Object name="rhall.jpg" url="http://a1.twimg.com/profile_images/57117466/robert_m_hall_bio_photo_big_normal.jpg" />
                    <fx:Object name="bobjim.jpg" url="http://a1.twimg.com/profile_images/51723308/ryancampbell3_normal.jpg"/>
                    <fx:Object name="jenschr.jpg" url="http://a1.twimg.com/profile_images/43222252/jenschr_mugshot3_normal.jpg"/>
                    <fx:Object name="adamflater.jpg" url="http://a1.twimg.com/profile_images/21503622/Photo_8_normal.jpg"/>
                    <fx:Object name="reboog711.jpg" url="http://a1.twimg.com/profile_images/16984682/DSCF0044_normal.jpg"/>
                </fx:Array>
            </mx:source>
        </mx:ArrayCollection>
    </fx:Declarations>
    
    <mx:DataGrid id="fileData" dragEnabled="true"
                 dataProvider="{arrColl}" 
                 mouseMove="onDragOut(event)"
                 nativeDragComplete="onDragOutComplete(event)">
        <mx:columns>
            <mx:DataGridColumn dataField="name" />
            <mx:DataGridColumn dataField="url"/>
        </mx:columns>
    </mx:DataGrid>
    
</s:WindowedApplication>

temp.png

Additional functionality to existing API

In addition to adding new functionality, AIR 2.0 focuses on completing the existing APIs by adding methods to give you better control over the application. Here are some of the additional functionalities that were added to the existing API in AIR 2.0:
  • Transaction savepoints - new methods for SQLite database, the ability to create savepoints.
  • Default application - API that allows opening a file with the OS default application.
  • Vector printing - In AIR 1.5 you already had the ability to do vector printing, and in AIR 2.0 you can do the same with Mac OS.
  • IPv6 Support - AIR 2.0 adds the ability to support IPv6 to all network APIs.
  • Increased screen size - AIR 2.0 supports increasing the maximum size of the AIR windows.
  • Microphone ByteArray - AIR 2.0 allows accessing the data in the Microphone API.
  • Idle time-out - Ability to set the idle time-out settings (Win/Mac only).

Open file with Default Application

File API exposes a new method called openWithDefaultApplication() to open a file with the default OS program. When you use this method the file will open using the default application that is registered with the operating system. If the file is executable, then that executable program is launched.

Due to security concerns, AIR prevents you from using the File.openWithDefaultApplication() method to open certain files and will throw an exception.

On Windows, AIR prevents you from opening files that have certain file types. On Mac OS, AIR prevents you from opening files that will launch in specific applications. On Linux, AIR prevents you from opening a file that has the executable bit set, and you cannot open a file that will launch in certain applications. Take a look at the table below for examples of file types that are blocked.

Type Windows Application Mac OS Application Linux Application Executable File exe executable bit, .app extension /lib/ld.so UNIX shell script sh Terminal /bin/bash UNIX shell script sh Terminal /bin/bash UNIX ksh shell script ksh Terminal /bin/ksh

The application below allows you to browse for a file in your local system and then open the file with the default application. Notice the try…catch code which is needed in cases where a failure occurs due to a file that cannot be opened.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo">
    <fx:Script>
        <![CDATA[
            
            import flash.events.Event;
            import flash.events.MouseEvent;
            import mx.events.FlexEvent;
            
            private var file:File;
            
            private function buttonClickHandler(event:MouseEvent):void 
            {
                 file = new File();
                 file.addEventListener(Event.SELECT, onFileSelect);
                 file.browseForOpen("Browse for a file:");
             }
            
            private function onFileSelect(event:Event):void 
            {
                 richText.text = File(event.currentTarget).nativePath;
                 file.load();
                 
                 try {
                      file.openWithDefaultApplication();
                  }
                 catch(error:Error) {
                      trace("The file you selected is prohibited and cannot be opened");
                  }
             }

        ]]>
    </fx:Script>
    
    <s:Button id="button"
               label="Click to browse"
               click="buttonClickHandler(event);"
               width="134" />
    
    <s:RichText id="richText" x="0" y="29"/>
    
</s:WindowedApplication>

24839f0804.png

Microphone Access API

AIR 2.0 exposes access to the uncompressed PCM ByteArray data on the Microphone API. You can enable or disable the feature by setting or removing the SampleDataEvent.SAMPLE_DATA event listener on each Microphone.getMicrophone() instance.

I want to point out that the ability to access the ByteArray in certain APIs has been a long standing request to Adobe, and I will not be surprised if this feature is added to Flash Player altogether in a sub-version of Flash Player 10 or 11. Hopefully Adobe will continue the trend and allow the ability to access NetStream’s ByteArray.

The following application lets you record from your built-in or external microphone. While you record the audio you can see a bar that shows the volume of your voice as a graphic. Once you complete recording, you can playback and save it as a Wave file.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo" 
                       xmlns:ui="ui.*" width="312" height="240">
    <fx:Script>
        <![CDATA[
            
            import flash.events.StatusEvent;
            import flash.media.Microphone;
            import flash.media.SoundChannel;
            import flash.events.SampleDataEvent;
            import flash.media.Sound;
            import flash.events.Event;
            import ui.AudioVisualization;
            import flash.utils.Endian;
            import flash.utils.ByteArray;
            
            private var recordedData:ByteArray;
            private var playbackData:ByteArray;
            
            private var mic:Microphone;
            private var sound:Sound;
            private var file:File;
            
            private function onOff():void
            {
                 if (recordButton.selected)
                 {
                      recordData();
                      recordButton.label = "Stop";
                  }
                 else
                 {
                      stopRecording();
                      recordButton.label = "Record";
                  }
             }
            
            private function recordData():void
            {
                 recordedData = new ByteArray();
                 playbackData = new ByteArray();
                 
                 recordedData.endian = Endian.LITTLE_ENDIAN;
                 playbackData.endian = Endian.LITTLE_ENDIAN;
                 
                 mic = Microphone.getMicrophone();
                 mic.rate = 44;
                 
                 mic.addEventListener(SampleDataEvent.SAMPLE_DATA, dataHandler);            
             }
            
            private function stopRecording():void
            {
                 if (!mic)
                     return;
                 
                 mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, dataHandler);
             }
            
            private function dataHandler(event:SampleDataEvent):void
            { 
 
                 this.visualization.drawMicBar(mic.activityLevel, 0xFF0000);
                 
                 playbackData.writeBytes(event.data);
                 
                 //Endian workaround...
                 var rec:ByteArray=new ByteArray();
                 rec.endian=Endian.LITTLE_ENDIAN;
                 rec.writeBytes(event.data);
                 rec.position = 0;
                 
                 while (rec.bytesAvailable)
                 {
                      var val:int = rec.readFloat()*32767;
                      recordedData.writeShort(val);
                  }                 
             }
            
            private function playRecordedData():void
            {
                 playbackData.position = 0;
                 trace("Start playing " + (playbackData.bytesAvailable/4) + " total samples.");
                 sound = new Sound();
                 sound.addEventListener(SampleDataEvent.SAMPLE_DATA, playSoundHandler);
                 
                 var channel:SoundChannel;
                 channel = sound.play();
                 channel.addEventListener(Event.SOUND_COMPLETE, onPlaybackComplete);
                 
                 visualization.start();
             }
            
            private function onPlaybackComplete(event:Event):void 
            {
                 visualization.stop();
             }
            
            private function playSoundHandler(event:SampleDataEvent):void
            {
 
                 if (!playbackData.bytesAvailable > 0)
                     return;
                 
                 var length:int = 8192; 
                 for (var i:int = 0; i < length; i++)
                 {
                      var sample:Number = 0;
                      if (playbackData.bytesAvailable > 0)
                          sample = playbackData.readFloat();
                      
                      event.data.writeFloat(sample);
                      event.data.writeFloat(sample);
                  }
             }
            
            private function save():void
            {
                 file = new File();
                 file.browseForSave("Save your wav");
                 file.addEventListener( Event.SELECT, writeWav );
             }
            
            private function writeWav(evt:Event):void
            {
                 var outBytes:ByteArray = new ByteArray();
                 var SampleRate:int = 44100
                 var NumChannels:int = 1;
                 var BitsPerSample:int = 16;
                 var headerBytes:ByteArray = new ByteArray();
                 
                 headerBytes.endian = Endian.LITTLE_ENDIAN;                
                 headerBytes.writeMultiByte("RIFF","utf-8");
                 headerBytes.writeUnsignedInt( int(recordedData.length+43-8) );
                 headerBytes.writeMultiByte("WAVE","utf-8");
                 headerBytes.writeMultiByte("fmt ","utf-8");
                 headerBytes.writeUnsignedInt( 16 );
                 headerBytes.writeShort(1);
                 headerBytes.writeShort( NumChannels );
                 headerBytes.writeUnsignedInt( SampleRate );
                 headerBytes.writeUnsignedInt( SampleRate * NumChannels * (BitsPerSample/8) );
                 headerBytes.writeShort( NumChannels * ( BitsPerSample/8) );
                 headerBytes.writeShort( BitsPerSample );
                 headerBytes.writeMultiByte("data","utf");
                 headerBytes.writeUnsignedInt( recordedData.length );
                 
                 headerBytes.position = 0;
                 recordedData.position = 0;
                 
                 outBytes.writeBytes(headerBytes,0,headerBytes.length);
                 outBytes.writeBytes(recordedData,0,recordedData.length);
                 
                 outBytes.position = 0;
                 
                 var stream:FileStream = new FileStream();
                 stream.open( file, FileMode.WRITE );
                 stream.writeBytes(outBytes);
                 stream.close();
             }                
            
        ]]>
    </fx:Script>
    
    <mx:Button id="recordButton" label="Record" toggle="true" click="onOff()" x="135" y="7"/>
    <s:Button id="playButton" label="Play Recording" click="playRecordedData()" x="29"  y="7"/>
    <s:Button id="saveButton" label="Save" click="save()" x="208" y="7"/>
    
    <ui:AudioVisualization id="visualization" y="100" />
    
</s:WindowedApplication>

As you can see in Figure, you can start recording your voice. While you record your voice you can see a bar that moves with the intensity of your voice. That is possible through the AudioVisualization component.

24839f0805.png

Once the voice is recorded you can playback, see the equalizer, and save the file as WAV file.

24839f0806.png

Database Transaction Savepoints



AIR 1.5 SQLConnection class supports transactions. Transactions allow the user to track SQL commands and commit or roll back when needed. AIR 2.0 adds new methods called savepoints(), setSavepoint(), releaseSavepoint(), and rollbackToSavepoint().

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       height="372" width="504"
                       creationComplete="creationCompleteHandler()">    
    
    <fx:Script>
        <![CDATA[
            
            import mx.rpc.events.ResultEvent;
            import mx.controls.Alert;
            import com.elad.framework.sqlite.vo.SqliteTableVO;
            
            import com.elad.framework.sqlite.events.DatabaseSuccessEvent;
            import com.elad.framework.sqlite.events.StatementCompleteEvent;
            import com.elad.framework.sqlite.events.DatabaseFailEvent;
            import mx.collections.ArrayCollection;
            import mx.events.FlexEvent;
            import com.elad.framework.sqlite.SQLiteManager;
            
            // SQL user gestures
            private const READ_ALL_USERS_INFO:String = "readAllUsersInfo";
            private const INSERT_USER_INFO:String = "insertUserInfo";
            private const INSERT_ORDER_INFO:String = "insertOrderInfo";
            private const READ_ALL_ORDERS_INFO:String = "readAllOrdersInfo";
            
            // holds the database manager singelton instance
            private var database:SQLiteManager = SQLiteManager.getInstance();

            //--------------------------------------------------------------------------
            //
            //  Methods
            //
            //--------------------------------------------------------------------------    
            
            // start database
            protected function creationCompleteHandler():void
            {
                 var password:String = null; // leave as null to have the database unsecure or set a password for secure connection. Example: "Pa55word";
                 var sqliteTables:Vector.<SqliteTableVO> = new Vector.<SqliteTableVO>;
                 
                 sqliteTables[0] = new SqliteTableVO( "Users", "CREATE TABLE Users(UserId INTEGER PRIMARY KEY, UserName VARCHAR(150)); " );
                 sqliteTables[1] = new SqliteTableVO( "Orders", "CREATE TABLE Orders(OrderId INTEGER PRIMARY KEY, UserId VARCHAR(150), OrderTotal DOUBLE);" );
                 
                 addListeners();
                 
                 database.start( "Users.sql3", sqliteTables, password, sqliteTables[0].tableName );
             }
            
            // Set all the listeners
            private function addListeners():void
            {
                 database.addEventListener(DatabaseSuccessEvent.DATABASE_CONNECTED_SUCCESSFULLY, function(event:DatabaseSuccessEvent):void
                 {
                      event.currentTarget.removeEventListener(event.type, arguments.callee);
                      database.executeSelectAllCommand( database.sqliteTables[0].tableName, READ_ALL_USERS_INFO );
                  });                
                 
                 database.addEventListener(DatabaseSuccessEvent.COMMAND_EXEC_SUCCESSFULLY, onSelectResult);
                 
                 database.addEventListener(DatabaseSuccessEvent.DATABASE_READY, function(event:DatabaseSuccessEvent):void { 
                      event.currentTarget.removeEventListener(event.type, arguments.callee);
                      trace("database ready!"); 
                  } );
                 database.addEventListener(DatabaseFailEvent.COMMAND_EXEC_FAILED, function(event:DatabaseFailEvent):void {
                      trace("SQL execution fail: "+event.errorMessage);
                  });
                 database.addEventListener(DatabaseFailEvent.DATABASE_FAIL, function(event:DatabaseFailEvent):void {
                      var message:String = "Database fail: "+event.errorMessage;
                      
                      if (event.isRolledBack)
                      {
                           message += "\nTransaction was rolled back";    
                       }
                      
                      Alert.show(message);
                  });    
                 database.addEventListener(DatabaseSuccessEvent.CREATING_DATABASE, function(event:DatabaseSuccessEvent):void {
                      event.currentTarget.removeEventListener(event.type, arguments.callee);
                      trace(event.message);
                  });                
             }    

            protected function insertDataClickHandler(event:MouseEvent):void
            {
                 var SQLStatementText:String = "INSERT INTO Users VALUES('" + userId.text + "','" + userName.text + "');'";
                 database.executeCustomCommand(SQLStatementText, INSERT_USER_INFO);
             }
            
            protected function insertOrderClickHandler(event:MouseEvent):void
            {
                 var SQLStatementText:String = "INSERT INTO Orders VALUES('" + ordersDataGrid.dataProvider.length+1 + "','" + IdComboBox.selectedItem.label + "','" + orderTotal.text + "');'";
                 database.executeCustomCommand(SQLStatementText, INSERT_ORDER_INFO);                
             }            

            //--------------------------------------------------------------------------
            //
            //  Handlers
            //
            //--------------------------------------------------------------------------    
            
            // handles results
            private function onSelectResult(event:StatementCompleteEvent):void
            {
                 var result:Array = event.results.data;
                 var rowsAffected:int = event.results.rowsAffected;  
                 
                 switch (event.userGestureName)
                 {
                      case null:
                      break;
                      case READ_ALL_USERS_INFO:
                          
                          if (result == null)
                              break;
                          
                          var len:int = result.length;
                          var dp:ArrayCollection = new ArrayCollection();
                          
                          for (var i:int; i<len; i++)
                          {
                               dp.addItem( { label: result[i].UserId, UserName: result[i].UserName } );
                               
                           }
                          
                          IdComboBox.dataProvider =  usersDataGrid.dataProvider = dp;    
                          
                          database.executeSelectAllCommand( this.database.sqliteTables[1].tableName, READ_ALL_ORDERS_INFO );
                          
                      break;
                      case INSERT_USER_INFO:
                          database.executeSelectAllCommand( this.database.sqliteTables[0].tableName, READ_ALL_USERS_INFO );
                      break;
                      case INSERT_ORDER_INFO:
                          database.executeSelectAllCommand( this.database.sqliteTables[1].tableName, READ_ALL_ORDERS_INFO );
                      break;
                      case READ_ALL_ORDERS_INFO:
                          
                          if (result == null)
                              break;
                          
                          len = result.length;
                          dp = new ArrayCollection();
                          
                          for (i = 0; i<len; i++)
                          {
                               dp.addItem( { OrderId: result[i].OrderId, OrderTotal: result[i].OrderTotal, UserId: result[i].UserId } );
                               
                           }
                          
                          ordersDataGrid.dataProvider = dp;    
                          
                      break;
                  }
             }

        ]]>
    </fx:Script>
    
    <!-- Users Form -->
    <mx:Form width="221" y="5">
        <mx:FormItem label="User ID:">
            <s:TextInput id="userId" width="85"/>
        </mx:FormItem>
        <mx:FormItem label="User Name:">
            <s:TextInput id="userName" width="85"/>
        </mx:FormItem>
        <mx:FormItem>
            <s:Button label="Insert User"
                      click="insertDataClickHandler(event)"/>
        </mx:FormItem>
    </mx:Form>
    
    <!-- Orders Form -->
    <mx:Form x="239" y="5"
             width="221">
        <mx:FormItem label="User Id">
            <mx:ComboBox id="IdComboBox" editable="true" width="85"></mx:ComboBox>
        </mx:FormItem>
        <mx:FormItem label="Order Total:">
            <s:TextInput id="orderTotal" width="85"/>
        </mx:FormItem>
        <mx:FormItem>
            <s:Button label="Insert Order"
                      click="insertOrderClickHandler(event)"/>
        </mx:FormItem>
    </mx:Form>            
        
    <!-- Results -->
    <mx:DataGrid id="usersDataGrid" x="16" y="123" height="145">
        <mx:columns>
            <mx:DataGridColumn headerText="User Id" dataField="label"/>
            <mx:DataGridColumn headerText="User Name" dataField="UserName"/>
        </mx:columns>
    </mx:DataGrid>
    <mx:DataGrid id="ordersDataGrid" x="231" y="123" width="231" height="145">
        <mx:columns>
            <mx:DataGridColumn headerText="Order Id" dataField="OrderId"/>
            <mx:DataGridColumn headerText="User Id" dataField="UserId"/>
            <mx:DataGridColumn headerText="Order Total" dataField="OrderTotal"/>
        </mx:columns>
    </mx:DataGrid>        
    
    <!-- Transactions -->
    <s:Button id="rollbackBtn" 
              x="119" y="283" 
              label="Rollback" 
              enabled="false"
              click="database.rollbackTransaction(new Responder(function(event:SQLEvent):void
                  {
                      Alert.show( 'Total number of changes being rolled back: ' + database.connection.totalChanges );
                })); 
                database.executeSelectAllCommand( this.database.sqliteTables[0].tableName, READ_ALL_USERS_INFO );
                database.executeSelectAllCommand( this.database.sqliteTables[1].tableName, READ_ALL_ORDERS_INFO );
                isTransactionCheckBox.selected=false;"/>

    <s:CheckBox id="isTransactionCheckBox" x="18" y="284" 
                label="isTransaction" 
                selected="false" 
                change="if ( isTransactionCheckBox.selected ) 
                {
                    database.beginTransaction();
                    rollbackBtn.enabled = true;
                    setSavePointBtn.enabled = true;
                    releaseSavePointBtn.enabled = true;
                    rollbackToSavePoint.enabled = true;
                }
                else
                {
                    database.stopTransactionAndCommit();
                    rollbackBtn.enabled = false;
                    setSavePointBtn.enabled = false;
                    releaseSavePointBtn.enabled = false;
                    rollbackToSavePoint.enabled = false;                
                }"
    />
    
    <s:Button id="setSavePointBtn" 
              x="20" y="312" 
              label="setSavePoint" 
              enabled="false"
              click="database.setSavepoint('point1');"/>
    
    <s:Button id="releaseSavePointBtn" 
              x="122" y="312" 
              label="ReleaseSavePoint" 
              enabled="false"
              click="database.releaseSavepoint('point1');"/>
    
    <s:Button id="rollbackToSavePoint" 
              x="249" y="312" 
              label="RollbackToSavePoint" 
              enabled="false"
              click="database.rollbackToSavepoint('point1', new Responder(function(event:SQLEvent):void
              {
                  Alert.show( 'Total number of transactions: ' + database.connection.totalChanges );
              }));
                  database.executeSelectAllCommand( this.database.sqliteTables[0].tableName, READ_ALL_USERS_INFO );
                  database.executeSelectAllCommand( this.database.sqliteTables[1].tableName, READ_ALL_ORDERS_INFO );
              "/>    
        
</s:WindowedApplication>

Mac Vector Printing Support

AIR 1.5 supports vector-printing capability for Windows that is done utilizing the flash.printing.PrintJob class. AIR 2.0 adds the capability of doing vector printing on a MAC. See the link below for more information on how to use the vector printing in AIR:

http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7cc2.html

Idle time-out

AIR 2.0 adds a method to URLRequestDefaults, HTMLLoader, and URLRequest classes so you can set the idleTimeout (IDLE) property.

IDLE means no keystrokes or mouse movements. The acceptable range of values, are from 5 (5 seconds) through 86,400 (1 day).

The application below tracks the application idle and present status and sets the IDLE for HTMLLoader. If you set the IDLE too short, HTMLLoader will terminate.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       width="800" height="600"
                       initialize="initializeHandler()">
    <fx:Script>
        <![CDATA[
            import flash.events.Event;
            import flash.system.Capabilities;
            import flash.events.TimerEvent;
            import flash.utils.Timer;
            import flash.net.URLRequest;
            
            private var nativeApp:NativeApplication;
            private var html:HTMLLoader;
            
            private function initializeHandler():void
            {
                 html = new HTMLLoader();
                 
                 nativeApp = NativeApplication.nativeApplication;
                 nativeApp.idleThreshold = 7;
                 
                 nativeApp.addEventListener(Event.USER_IDLE, onUserIdleHandler);
                 nativeApp.addEventListener(Event.USER_PRESENT, onUserPresentHandler);
                 
                 html.idleTimeout = 7;
                 html.width = 800;
                 html.height = 800;
                 
                 var urlRequest:URLRequest = new URLRequest("http://adobe.com");
 
                 html.load(urlRequest);
                 
                 component.addChild(html);
             }
            
            private function onUserIdleHandler(evt:Event):void
            {
                 var lastUserInput:Number = NativeApplication.nativeApplication.timeSinceLastUserInput;
                 console.text = String( lastUserInput )+" sec since last present";
             }
            
            private function onUserPresentHandler(event:Event):void
            {
                 console.text = "Present";
             }
        ]]>
    </fx:Script>
    
    <mx:UIComponent id="component" x="0" y="15"/>
    
    <s:SimpleText x="5" y="2" text="Idle status: "/>
    <s:SimpleText id="console" x="74" y="2"/>
    
</s:WindowedApplication>

Increased Maximum Size of NativeWindow

NativeWindow maximum window size has been increased from 2880x 2880 pixels in AIR 1.5.2 to 4095x4095 pixels in AIR 2.0. This feature is useful and will allow using AIR on large screens. For instance, now that AIR is capable of registering multi-touch user gestures, you can create AIR applications on large screens. To test the feature, create a large application, and set the width and height to the dimensions. I would like to note that deploying the AIR application on a device that is smaller than the size you provided will set the size to the maximum possible size.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                          xmlns:s="library://ns.adobe.com/flex/spark" 
                                          xmlns:mx="library://ns.adobe.com/flex/halo"
                                          width="4095" height="4095" />

Platform awareness related APIs

One of the key elements in AIR 2.0 is moving toward an SDK that better ties into the operation system and gives your application more control. Additionally the new AIR 2.0 contains platform awareness in which the code base is more adaptable to constant changes in a device. This means that the program knows the user’s system capabilities and tracks changes in the user’s environment.
  • Mass storage - API that allows device detection so you can access networks and storage devices and recognize changes.
  • Multi touch screen - AIR 2.0 adds the ability to recognize multi touch gestures, one of the most important milestones in building a mobile application.

Mass Storage Device Detection



A missing element from previous versions of AIR was the ability to access and detect mass storage changes. In AIR 2.0 you can detect and access the device’s mass storage. There are two classes that enable you to do so:
  • flash.filesystem.StorageVolumeInfo - The StorageVolumeInfo is a singleton manager that keeps track of changes to the mass storage devices. Upon changes StorageVolumeChangeEvent gets dispatched. There are two types of events: storageVolumeMount and storageVolumeUnmount.
  • flash.filesystem.StorageVolume - The StorageVolume class holds information regarding the properties of the mass storage volume.
Using the new API, it is possible to query the name, label, root directory, and file system type of a volume. Additionally, it is possible to determine whether a volume is writable or removable. The following application will display the existing storage devices available as well as add and remove mass storage devices in case you add a new device or remove a device such as a USB key.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       creationComplete="creationCompleteHandler()">
    <fx:Script>
        <![CDATA[
            import flash.events.StorageVolumeChangeEvent;
            import mx.collections.ArrayCollection;
            
            [Bindable]
            private var storageCollection:ArrayCollection = new ArrayCollection();

            protected function creationCompleteHandler():void
            {
                 var storageVolumes:Vector.<StorageVolume> = StorageVolumeInfo.storageVolumeInfo.getStorageVolumes();
                 var length:int = storageVolumes.length;
                 
                 addEventListeners();
                 
                 for (var i:int = 0; i < length; i++)
                 {
                      addItem( storageVolumes[i] );
                  }
             }
            
            private function addEventListeners():void
            {
                 StorageVolumeInfo.storageVolumeInfo.addEventListener(StorageVolumeChangeEvent.STORAGE_VOLUME_MOUNT, 
                 function (event:StorageVolumeChangeEvent):void
                 {
                      addItem(event.storageVolume);
                  });
                 
                 StorageVolumeInfo.storageVolumeInfo.addEventListener(StorageVolumeChangeEvent.STORAGE_VOLUME_UNMOUNT, 
                 function (event:StorageVolumeChangeEvent):void
                 {
                      var nativePath:String = event.rootDirectory.nativePath;
                      removeItemByNativePath( nativePath );
                  });
             }
            
            private function addItem( storageVolume:StorageVolume ):void
            {
                 var object:Object = new Object();
                 
                 object = new Object();
                 object.name = storageVolume.name;
                 object.icon = storageVolume.rootDirectory.icon.bitmaps[2];
                 object.nativePath = storageVolume.rootDirectory.nativePath;
                 object.isWritable = storageVolume.isWritable;
                 object.isRemovable = storageVolume.isRemovable;
                 storageCollection.addItem( object );                
             }
            
            private function removeItemByNativePath( nativePath:String ):void
            {
                 var len:Number = this.storageCollection.length;
                 var object:Object;
                 
                 for ( var i:int=0; i<len; i++ )
                 {
                      object = this.storageCollection.getItemAt( i );
                      
                      if ( object.nativePath == nativePath )
                      {
                           this.storageCollection.removeItemAt( i );
                           break;
                       }
                  }
             }

        ]]>
    </fx:Script>
    
    <s:List x="87" y="28" 
            skinClass="components.DataList"
            dataProvider="{storageCollection}"/>

Once you attach a USB drive the volume will be added automatically to the list, and once you remove the USB drive it will be removed from the list.

24839f0807.png


Multi-touch functionality



MultitouchInputMode is the enumeration class that holds the three types of multi-touch hardware:

GESTURE = "gesture" NONE = "none" TOUCH_POINT = "touchPoint"

First you need to set the Multitouch class to the type of hardware. Than you can start listening to the events.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       creationComplete="creationCompleteHandler(event)">
    <fx:Script>
        <![CDATA[
            import mx.events.FlexEvent;

            protected function creationCompleteHandler(event:FlexEvent):void
            {
                 Multitouch.inputMode=MultitouchInputMode.TOUCH_POINT;
                 
                 addEventListener( GestureEvent.GESTURE_TWO_FINGER_TAP, onEvent );
                 addEventListener( TransformGestureEvent.GESTURE_ZOOM, onEvent );
                 addEventListener( TransformGestureEvent.GESTURE_PAN, onEvent );
                 addEventListener( TransformGestureEvent.GESTURE_ROTATE, onEvent );
                 addEventListener( TransformGestureEvent.GESTURE_PRESS_AND_TAP, onEvent );
             }
            
            protected function onEvent(event:Event):void
            {
                 trace( event.type );
             }

        ]]>
    </fx:Script>
    
</s:WindowedApplication>

Better Performance



One of the biggest complaints about AIR applications is that they consume too much CPU memory and have large runtime sizes. It is encouraging to see that in AIR 2.0 Adobe’s team put effort into increasing optimization and decreasing resources used by the AIR application:
  • File improvement - Smaller runtime size and CPU/memory improvements.
  • WebKit upgrade - WebKit HTML-rendering engine was updated to an optimized version.
  • Native Linux installed - Linux installer available as native .deb and .rpm installer. AIR will be available for Linux 64bits.

Decrease in resources used

AIR applications have been judged by many to consume too many resources from the machine they are installed on, and in AIR 2.0 Adobe has put extra effort into decreasing the runtime size and into decreasing the CPU/memory consumed by an AIR application. In fact, I have tested a simple application that includes a text field and noticed significant decrease in resources used. Look at the results below.

SDK CPU Threads Real Memory Virtual Memory
AIR 1.5 & SDK 3.4 2.5 6 30.20 MB 47.80 MB
AIR 2.0 & SDK 4.0 2.5 6 28.6 MB 41.7 MB

Activity Monitor running AIR 2.0 project

24839f0808.png

Activity Monitor running AIR 1.5 project

24839f0809.png

WebKit Performance Increase



WebKit (http://webkit.org/) is an open source browser that has been available since AIR 1.0. WebKit engine renders HTML and executes Javascript. Webkit is the engine that drives Safari, claimed by Apple and others to be the fastest browser. Adobe AIR 2.0 uses the same branch of WebKit as Safari 4 beta: http://trac.webkit.org/browser/releases/Apple/Safari%204%20Public%20Beta. The most significant feature of using this branch of WebKit is the usages of SquirrelFish Extreme (SFX), which has been integrated into the WebKit engine and increases the overall performance of Webkit.

Using a newer branch of WebKit provides faster performance and additional functionality.
  • SquirrelFish Extreme - the new WebKit engine resulting in faster performance. According to SunSpider, the performance is more than double the speed as compared to the regular SquirrelFish. The reason SFX is faster is mainly due to bytecode optimizations, polymorphic inline caching, a lightweight JIT compiler, and an expression engine that uses the JIT infrastructure.
  • CSS Transitions - Webkit added built-in animations using CSS. By describing how to animate from an old value to a new value over time you can create animations.
  • CSS Transformations - transformations, via the -webkit-transform property, allows you to scale, rotate, and skew blocks of elements.
  • CSS Animations - animation that uses the -webkit-transition tag and lets you set timings for fades, rotation, expansion, collapses, and others.
  • CSS Gradients - You can create gradients in CSS. There are two types of gradients: linear gradients and radial gradients. The syntax is as follows:
  • -webkit-gradient(, [, ]?, [, ]? [, ]*)
  • Webkit CSS selectors - You can access the DOM faster and easier using the Selectors API. It allows you to select elements within a document using CSS.
WebKit is embedded into AIR and lets you create HTML/JS/PDF objects and render them as Flash objects. You can then manipulate these objects just like any other object in Flex.

Take a look at a simple application that uses the HTML tag and displays HTML content.

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                          xmlns:s="library://ns.adobe.com/flex/spark" 
                                          xmlns:mx="library://ns.adobe.com/flex/halo"
                                          width="800" height="600">
    
    <mx:HTML id="htmlObject" location="http://google.com" 
                         width="400" height="400"  
                         x="126" y="404" rotation="280"/>

</s:WindowedApplication>
Using the same HTML component, take a look at the following example which allows you to test some of the new WebKit functionality:

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       width="800" height="600"
                       initialize="initializeHandler(event)">
    <fx:Script>
        <![CDATA[
            import flash.utils.Timer;
            import mx.core.UIComponent;
            import flash.events.Event;
            import mx.controls.HTML;
            import mx.events.FlexEvent;

            
            private var htmlTransitions:XML =
                <html>
                    <div onmouseover="this.style.opacity = 0;" onmouseout="this.style.opacity=1" 
                        style="-webkit-transition: opacity 1s linear; background-color: #efefef; border:5px solid black;">
                        CSS Transitions Example
                    </div>
                </html>;
            
            private var html:HTML;
            
            private var timer:Timer = new Timer(1, 10000);
            
            protected function initializeHandler(event:FlexEvent):void
            {
                 html = new HTML();
                 html.width  = 400; 
                 html.height = 400;
                 html.addEventListener( Event.COMPLETE, onComplete );
                 component.addChild( html );
             }
            
            private function loadHTMLCode(htmlText:XML):void
            {
                 timer.start();
                 html.htmlText = htmlText;
             }
            
            private function loadHTMLPage(location:String):void
            {
                 timer.start();
                 html.location = location;
             }
            
            private function onComplete(event:Event):void
            {
                 trace("page loaded after: ");
                 label.text = "HTML code executed in " + this.timer.currentCount + " seconds";
                 timer = new Timer(1, 10000);
             }

        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:RadioButtonGroup id="radiogroup"/>
    </fx:Declarations>

    <mx:UIComponent id="component" width="400" height="400"  x="10" y="42"/>
    
    <s:RadioButton x="10" y="10" label="Transitions" groupName="radiogroup" click="loadHTMLCode(htmlTransitions);"/>
    <s:RadioButton x="103" y="10" label="Animations" groupName="radiogroup" click="loadHTMLPage('asset/Animation.html')"/>
    <s:RadioButton x="275" y="10" label="Gradient" groupName="radiogroup" click="loadHTMLPage('asset/Gradient.html')"/>
    <s:RadioButton x="193" y="10" label="Transform" groupName="radiogroup" click="loadHTMLPage('asset/Transform.html')"/>
    <s:RadioButton x="353" y="10" label="Selectors" groupName="radiogroup" click="loadHTMLPage('asset/Selectors.html')"/>
    
    <mx:Label id="label" x="9" y="450" width="374"/>
    
</s:WindowedApplication>
There are two methods: loadHTMLCode and loadHTMLPage. These methods let you either load the HTML as inline code or load an HTML page. Webkit transitions

Since there is not much HTML code for the transition example I am embedding inline HTML using the HTML component.

24839f0811.png


WebKit Animation
For the HTML implementation I have used mostly open source HTML code that I have modified to better fit the presentation. Since the animations are nested inside an object in the Flash VM, the experience of the animation is a bit choppy. I am not sure how much you are really going to use these types of animation, especially when Flash provides much better APIs to handle animations. Listed below is the code for the Animation.html page.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <style type="text/css" media="screen">
      @-webkit-keyframes pulse {
        0% {
           background-color: red;
           opacity: 1.0;
           -webkit-transform: scale(1.0) rotate(0deg);
         }
        33% {
           background-color: blue;
           opacity: 0.75;
           -webkit-transform: scale(1.1) rotate(-5deg);
         }
        67% {
           background-color: green;
           opacity: 0.5;
           -webkit-transform: scale(1.1) rotate(5deg);
         }
        100% {
           background-color: red;
           opacity: 1.0;
           -webkit-transform: scale(1.0) rotate(0deg);
         }
       }

      .pulsedbox {
        -webkit-animation-name: pulse;
        -webkit-animation-duration: 4s;
        -webkit-animation-iteration-count: infinite;
        -webkit-animation-timing-function: ease-in-out;
       }
      
      div {
         background-color: red;
         width: 40%;
         padding: 0.2em 1em;
         margin: 6em;
       }
    </style>
  </head>
  
  <body>
    <div class="pulsedbox">
      <p>
        WebKit Animation example
      </p>
    </div>
  </body>
  
</html>
24839f0812.png

WebKit Gradients

AIR’s WebKit version supports gradient in CSS. Just as there are in many other design programs, there are two types of gradients: linear and radial. See syntax: -webkit-gradient(, [, ]?, [, ]? [, ]*)

The code for the Gradient.html page is listed below.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
 <html>
     
     <head>
        <style>
            div { }
                .radial::after { width:150px; height:150px; border:2px solid black;
                    content: -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)),
                                -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)),
                                -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)),
                                -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700));
                     display: block;
                 }
                .linear::after { width:130px; height:130px; border:2px solid black; 
                    content: -webkit-gradient(linear, left top, left bottom, from(#00abeb), to(#fff), color-stop(0.5, #fff), color-stop(0.5, #66cc00));
                    display: block;
                 }
            }
        </style>
    </head>
    
    <body>
        <div class="radial">WebKit CSS Gradient Radial Example</div>
        <br/>
        <div class="linear">WebKit CSS Gradient Linear Example</div>
    </body>
</html>
24839f0813.png

WebKit selectors

The new version of the WebKit provides an addition to the traditional DOM. The new API comes in handy when you need to retrieve certain elements or collections of elements. The selectors provide more functionality and the ability to retrieve a list of functions as well as other features. See the Selectors.html.

<!DOCTYPE html>
<html>
    <head>
    </head>
    
    <body>
        <p id="text1" />
        <p class="text2" />
        
        <script>
            document.querySelector("p#text1").innerHTML = "Selectors example using Id";
            document.querySelector("p.text2").innerHTML = "Selectors example using class";
        </script>
        
    </body>
</html>

24839f0814.png


WebKit Transform

WebKit supports CSS transforms. The syntax is as follow: -webkit-transform. Using the WebKit transform you can set boxes to be scaled, rotated, skewed or translated. See the Transform.html content below.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
    
<html>
    <head>
        <style type="text/css">
          .showbox {
             float: left;
             margin: 4em 1em;
             width: 40px;
             height: 20px;
             border: 2px solid green;
             background-color: #fff;
             line-height: 20px;
             text-align: center;
           }
        </style>
    </head>
    
    <body>
        <div class="showbox" style="-webkit-transform: translate(2em,0);">1</div>
        <div class="showbox" style="border-color: red; -webkit-transform: rotate(50deg);">2</div>
        <div class="showbox" style="-webkit-transform: translate(-3em,1em);">3</div>
        <div class="showbox" style="-webkit-transform: scale(2);">4</div>
    </body>
    
</html>

24839f0815.png



In this article I gave you a comprehensive overview of AIR 2. I showed you the new functionality in AIR 2.0 such as the new Networking class, launching and interacting with Native Processes, and screen reader support. I covered the additional functionality to existing API that AIR supports such as the open file with default application, Access to ByteArray for the Microphone API, and Database Transaction Savepoints support. I showed you how to tie to APIs that are related to Platform awareness such as Mass Storage device detection and multi touch screen, and finally I spoke about the performance improvements in AIR 2.0 such as decrease in resources used and the new WebKit.

In a new book we are releasing next year we will have more information about AIR and AIR 2 new APIs. Pre-order from Amazon here.

Follow Elad on Twitter: http://twitter.com/EladElrom

Read more from Elad Elrom. Elad Elrom's Atom feed

Comments

12 Comments

V1 said:

Did the webkit version also include webkit 3D transforms?

Cesare said:

Great list of new features! When the beta will be available?

gg said:

Thanks a lot !

Sameer said:

Amazing new features.

Elad, big thank you for writing the demo applications... I can wait to try them all.

-Sam

Sameer said:

Oops! A silly typo in last message.... I meant to say that I can't wait to try the demos!

Steven Sacks said:

Unfortunately, I cannot launch your examples because they claim I don't have the latest player. I have the latest version of the Flash player installed 10,0,2,54 in both Firefox and Chrome on Windows XP and your detect script fails in both browsers.

I even double-checked at Adobe's player version detect page.
http://kb2.adobe.com/cps/155/tn_15507.html

Elad Elrom said:

To lunch these examples you need the AIR 2 runtime, AIR 2 SDK and Flex 4 Beta. I believe that the release date is Q1 2010 for the AIR 2 Beta.

Tomas Sancio said:

Just optimization and seamless cross-platform compatibility. The rest is icing for the cake.

Scott Barnes said:

Elad,

Is it possible with AIR 2.0 to tap into the Windows Powershell (Command Prompt with C# access) and communicate with it back and forth?

Anand said:

Elad,

Too good.I have tried few examples.working fine.

Could you post an ServerSocket sample.

Thanks in advance

makubex said:

Hi..
I was just playing with AIR 2.0, I can't make the new openWithDefaultApplication() work. I get the following error.. "Property openWithDefaultApplication not found on flash.filesystem.File and there is no default value."

Hernan said:

Hi, I'm getting the same error when using openWithDefaultApplication, 'Property openWithDefaultApplication not found on flash.filesystem.File and there is no default value.'

Leave a comment


Tag Cloud

iPad

What's your take on the iPad? (Putting aside the Flash/iPad flame war)

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.