Archive for the 'AIR' Category

01
Feb

10 recipes on ADC Cookbooks covering AIR 2.0 new APIs

AIR 2.0 beta is available publicly and when I was asked by Adobe’s marketing team, as well as Apress editor to post some recipes for Flex and AIR in the Adobe Developer connection Cookbooks I decided to combine the two. Adobe are looking to have the Cookbooks gain momentum & I decided to lend a hand and released 10 recipes with applications I built with the new AIR 2.0 APIs while I was part of the Beta program. I revised the applications and they are running correctly on the latest built of AIR available for the pre-release users.

Adobe Developer connection Cookboks

These recipes are listed below:

1: SQLite Manager

SQLiteManager does just that and allows you to set the database settings and then access the manager from anywhere in your application. It makes the process simpler and integrates very well with micro architecture frameworks. View recipe.

2: Retrieve user’s Network Information

Create an example of displaying the user’s available networks… View recipe.

3: Retrieving DNS records

Take a look at the app example, which performs DNS and reverse DNS lookup. View recipe.

4: Launching and Interacting with Native Processes

The application below opens up a text file: foobar.txt using the MacOS TextEdit text editor. View recipe.

5: Recipe for using the File promises API

Application below allows dragging of items from a list into the user’s local machine and then the copying of the files. View recipe.

6: Open file with Default Application

An AIR application that you can browse for a supported file, and watch how the registered application opens the file. View recipe.

7: SimpleAudioRecorder - Microphone Access API

The following application lets you record from any microphone connected. 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 see the a visualization of your recording and save it as a Wave file. View recipe.

8: Mass Storage Device Detection

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. View recipe.

9: Multi-touch functionality

We first switch between the different options and than we listen to the gesture events which will fire and display the gesture information in the console. View recipe.

10: WebKit with the SquirrelFish Extreme

Using the same HTML component, take a look at the following example which allows you to test some of the new WebKit functionality. View recipe.

Cheers :)

12
Sep

AIR SQLite Manager API updated and allow using multiple tables and transactions

Back in January ‘09 I posted an API that makes working with an AIR application that works with an SQLite database and has many SQL commands and multiple tables simple. I have received feedback from developers that are using the API and have found it helpful. I kept getting requests to update the API to support multiple tables.

You need this API because working with an application that has many SQL commands and multiple tables can become challenging.

  • These commands may be initialized from different classes, and
  • we may want to keep the database connection open and avoid duplicating code.

SQLiteManager does just that and allows you to set the database settings and than access the manager from anywhere in your application. It makes the process simpler and integrates very well with micro architecture frameworks.

I finally got around to updating the API, and it now support the following:

  • Password encryption
  • Multiple tables
  • Common SQL commands
  • Transactions and rollback option
  • Better handling of results
  • Improved and optimized code

Let’s look at the implementation code. The application will hold two tables, one for users and one for orders, and we will be able to insert and read information as well as keep track of transactions and rollback in case there are errors or for any other reasons.

AIR SQLite Manager

AIR SQLite Manager

The first step is to set constants with all the names of all the user gesture SQL we will be running. This way we can track requests and use the same result handler for all of our SQL requests.


			// 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();

The creation CompleteHandler method will be called once the application has completed initialization. We will be setting the database information and starting the connection. Notice that you need to set the array Vector with all of the tables you will have in your application. The way it is working is in case the application doesn’t have the tables created already. They will be generated automatically for the user so you need to specifiy the SQL command to create these tables and the name of each table. The names can be anything; just use unique names.


			// 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 );
			}

Notice that in the previous method we had a call to set the listeners we will be using. Take a look at the events we will be listening to:


			// 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);
				});
			}

We need two methods to generate the insert SQL command for the two tables we have and make the request.


			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);
			}

Once SQL commands are requested all the results are processed in this implementation with the same handler called onSelectResult. Notice that each request had a unique name so we are able to match the request to the result and update the view as needed.


			// 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;
				}
			}

The last part is the view. We have two forms to submit data and data grids to show the results.


	<!-- 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>

To handle the transactions we have a check box and a button to roll back in case we need to roll back. There are a few reasons to roll back. For instance, a commit of a few SQL commands that depend on each other failed. For example, in this demo app we have a user and an order, and in case the user couldn’t be created we may want to roll back and remove the user’s order.


	<!-- 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;
				}"
				/>	

You can download and use the SWC from here (use eladlibair_1.1.swc 37kb):
http://eladlib.googlecode.com/files/SWCs.zip

Or download the source code of the API from here:
http://code.google.com/p/eladlib/source/browse/#svn/trunk/EladLibAIR/src/com/elad/framework/sqlite

The implementation example is here:
http://code.google.com/p/eladlib/source/browse/trunk/EladLibAIR/src/SqliteManager.mxml

16
Apr

Adobe AIR SQLite manager API for Adobe AIR 1.5 with password encryption

Back in January I posted a blog entry that show how to create a SQLite manger API that allow you to work with an application that has many SQL commands easily. These commands may be initialized from different classes and used in cases where we may want to keep the database connection open and avoid duplicating code. The orginal blog post can be found here.

SQLite Adobe AIR database

Adobe AIR 1.5 closed a security gap. The application is using the exact same database. Essentially any AIR application can read other applications database.

Adobe has updated the framework and now includes the ability to add additional properties to encrypt your SQLite database. Under the new security you can create an encrypted database and when attempting to open the database, your code must provide the database’s encryption key.

In the past few months I received many requests to add encryption to the API. I finally got a chance to update the code and have added the option to add a password to the database. The key is created using the Adobe’s EncryptionKeyGenerator class and is bond to Adobe’s license.

Here’s the method signature:


public function start(dbFullFileName:String, tableName:String, createTableStatement:String, password:String=null):void

In case you don’t need to include encryption to your database, just keep the password as “null”. Other than the start method and a method to generate a key I have updated. Additionally, I added custom events as well meta data for the event constants.


		public function start(dbFullFileName:String, tableName:String, createTableStatement:String, password:String=null):void
		{
			this.dbFullFileName = dbFullFileName;
			this.tableName = tableName;
			this.createDbStatement = createTableStatement;
			var encryptionKey:ByteArray = null;

			connection = new SQLConnection();
			sqlFile = File.applicationStorageDirectory.resolvePath(dbFullFileName);

			try
			{
				if (password != null)
				{
					encryptionKey = getEncryptionKey(password, sqlFile);
				}

				connection.open(sqlFile, SQLMode.CREATE, false, 1024, encryptionKey);

			    this.dispatchEvent(new DatabaseSuccessEvent(DatabaseSuccessEvent.DATABASE_CONNECTED_SUCCESSFULLY));
			}
			catch (error:SQLError)
			{
			    var errorMessage:String = "Error message:" + error.message;

			    if (error.details != "")
			    	errorMessage += ", Details:" + error.details;

			    fail(null, errorMessage);
			}
		}

The getEncryptionKey method takes the password and the File class validate the password that is 8-32 char with one letter lowercase letter as well as one upper case and one number. It return a ByteArray which being pass to the connection.open method.


		private function getEncryptionKey(password:String, sqlFile:File):ByteArray
		{
			var keyGen:EncryptionKeyGenerator = new EncryptionKeyGenerator();
			var encryptionKey:ByteArray;

			if (!keyGen.validateStrongPassword(password))
			{
				var errorMessage:String = "The password must be 8-32 char, " + "with one letter lowercase letter, " +
						"one upper case and one number";

				fail(null, errorMessage);

				return null;
			}

			encryptionKey = keyGen.getEncryptionKey(sqlFile, password);	

			return encryptionKey;
		}

To see an example of implementation, take a look at the code below:


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009" layout="absolute"
	initialize="initializeHandler(event)">

	<Script>
		<![CDATA[
			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;

			private var database:SQLiteManager = SQLiteManager.getInstance();

			protected function initializeHandler(event:FlexEvent):void
			{
				database.addEventListener(DatabaseSuccessEvent.COMMAND_EXEC_SUCCESSFULLY, onSelectResult);
				database.addEventListener(DatabaseFailEvent.COMMAND_EXEC_FAILED, function(event:DatabaseFailEvent):void {
					trace("execution fail: "+event.errorMessage);
				});
				database.addEventListener(DatabaseFailEvent.DATABASE_FAIL, function(event:DatabaseFailEvent):void {
					trace("database fail: "+event.errorMessage);
				});
				database.addEventListener(DatabaseSuccessEvent.CREATING_DATABASE, function(event:DatabaseSuccessEvent):void {
					trace(event.message);
				});
				database.addEventListener(DatabaseSuccessEvent.DATABASE_CONNECTED_SUCCESSFULLY, onConnectedHandler);
				database.addEventListener(DatabaseSuccessEvent.DATABASE_READY, function():void { trace("database ready!"); } );

				// start database
				var password:String = "Pa55word";
				var createTableStatement:String = "CREATE TABLE Users(UserId VARCHAR(150) PRIMARY KEY, UserName VARCHAR(150))";
				database.start("Users.sql3", "Users", createTableStatement, password);
			}

			private function onConnectedHandler(event:DatabaseSuccessEvent):void
			{
				readEntries();
			}

			private function insertEntry():void
			{
				var sql:String =  "INSERT INTO Users VALUES('"+String(userId.text)+"','"+userName.text+"');";
				database.executeCustomCommand(sql);
			}

			private function readEntries():void
			{
				database.executeSelectAllCommand();
			}

			private function onSelectResult(event:StatementCompleteEvent):void
			{
				var result:Array = event.results.data;
				var rowsAffected:int = event.results.rowsAffected;  

				if (rowsAffected == 1)
					readEntries();

				if (result == null)
					return;

				var len:int = result.length;
				var dp:ArrayCollection = new ArrayCollection();

				for (var i:int; i<len; i++)
				{
					dp.addItem( {UserId: result[i].UserId, UserName: result[i].UserName} );
				}

				dataGrid.dataProvider = dp;
			}			

		]]>
	</Script>
	<Panel x="5" y="5" layout="absolute" height="356">

		<VBox horizontalScrollPolicy="off" verticalScrollPolicy="off">
			<!-- Form -->
			<Form width="414">
				<FormItem label="User ID:">
					<FxTextInput id="userId"/>
				</FormItem>
				<FormItem label="User Name:">
					<FxTextInput id="userName"/>
				</FormItem>
				<FormItem>
					<FxButton label="Insert Entry" click="insertEntry();"/>
				</FormItem>
			</Form>
		</VBox>

		<!-- Results -->
		<DataGrid x="19" y="123" id="dataGrid">
			<columns>
				<DataGridColumn headerText="User Id" dataField="UserId"/>
				<DataGridColumn headerText="User Name" dataField="UserName"/>
			</columns>
		</DataGrid>

	</Panel>

</WindowedApplication>

To download the complete code click here.

21
Feb

mp3tunes-as3-api open source API to use MP3Tunes in Flex/AIR/AS3 projects

mp3tunes is a service to load your audio files and share them between different devices such as: desktop, mobile, game consoles and the Web. I just completed creating a foundation API for AS3.0 so you can start using the API in your Flex/AIR/AS3.0 projects.
You can see the project in google code: http://code.google.com/p/mp3tunes-as3-api/
The SWC and ASDOC can be downloaded from here: http://mp3tunes-as3-api.googlecode.com/files/Archive.zip
And here’s a simple implementation of the API in Flex 4 (Gumbo). The code login into the MP3tunes Music API, retrieve album result, artist data, track data and plays the first audio file in your account.


<FxApplication xmlns="http://ns.adobe.com/mxml/2009"
	creationComplete="creationCompleteHandler(event)">
	<Script>
		<![CDATA[
			import flash.events.Event;
			import flash.media.Sound;
			import flash.net.URLRequest;
			import com.elad.MP3tunes.vo.TrackItemVO;
			import com.elad.MP3tunes.vo.TrackListVO;
			import com.elad.MP3tunes.events.TrackDataEvent;
			import com.elad.MP3tunes.vo.AlbumListVO;
			import com.elad.MP3tunes.events.AlbumDataEvent;
			import com.elad.MP3tunes.vo.ArtistItemVO;
			import com.elad.MP3tunes.vo.ArtistListVO;
			import mx.controls.Alert;
			import com.elad.MP3tunes.events.ArtistsResultEvent;
			import com.elad.MP3tunes.events.MusicEvent;
			import mx.events.FlexEvent;
			import com.elad.MP3tunes.Music;
			private var music:Music;
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				music = new Music("YOUR_DEVELOPER_KEY");
				music.addEventListener(MusicEvent.LOGIN_SUCCESSFULL, onLogin);
				music.addEventListener(MusicEvent.LOGIN_ERROR, function():void { Alert.show("Error login"); } );
				music.login("YOUR_USERNAME", "YOUR_PASSWORD");
			}
			private function onLogin(event:MusicEvent):void
			{
				music.removeEventListener(MusicEvent.LOGIN_SUCCESSFULL, onLogin);
				music.addEventListener(ArtistsResultEvent.ARTIST_RESULT_COMPLETED, onArtistsResult);
				music.addEventListener(ArtistsResultEvent.ARTIST_RESULT_ERROR, function():void { Alert.show("Error getting artist list"); });
				music.getMusicByArtists();
			}
			private function onArtistsResult(event:ArtistsResultEvent):void
			{
				music.removeEventListener(ArtistsResultEvent.ARTIST_RESULT_COMPLETED, onArtistsResult);
				var artistList:ArtistListVO = new ArtistListVO(event.artistList.list.source);
				var item:ArtistItemVO = artistList.getItem(0);
				music.addEventListener(AlbumDataEvent.ALBUM_DATA_COMPLETED, onAlbumDataComplete);
				music.addEventListener(AlbumDataEvent.ALBUM_DATA_ERROR, function():void { Alert.show("Error getting album list"); });
				music.getAlbumData(item.artistId);
			}
			private function onAlbumDataComplete(event:AlbumDataEvent):void
			{
				music.removeEventListener(AlbumDataEvent.ALBUM_DATA_COMPLETED, onAlbumDataComplete);
				music.removeEventListener(AlbumDataEvent.ALBUM_DATA_ERROR, onAlbumDataComplete);
				var albumList:AlbumListVO = new AlbumListVO(event.collection.list.source);
				var albumId:String = albumList.getItem(0).albumId;
				music.addEventListener(TrackDataEvent.TRACK_DATA_COMPLETED, onTrackDataComplete);
				music.addEventListener(TrackDataEvent.TRACK_DATA_ERROR, function():void { Alert.show("Error getting track data"); });
				music.getTrackData(albumId);
			}
			private function onTrackDataComplete(event:TrackDataEvent):void
			{
				music.removeEventListener(AlbumDataEvent.ALBUM_DATA_COMPLETED, onAlbumDataComplete);
				music.removeEventListener(TrackDataEvent.TRACK_DATA_ERROR, function():void { Alert.show("Error getting track data"); });
				var trackList:TrackListVO = new TrackListVO(event.collection.list.source);
				var trackItem:TrackItemVO = trackList.getItem(0);
				playSong(trackItem.downloadURL);
			}
			private function playSong(url:String):void
			{
				var sound:Sound = new Sound();
				var req:URLRequest = new URLRequest(url);
				sound.load(req);
				sound.play();
			}
		]]>
	<Script>
<FxApplication>
</p>
09
Feb

Using Pixel Bender to do heavy lifting calculations, makes Flash Player multi-thread.

In Flash 10 Adobe added a compiler to handle filters, which is possible through Pixel Bender kernels. Pixel Bender kernels is a program that calculate a single pixel at run time.

This is how it works. The Pixel Bender graph is an XML language for combining individual pixel-processing operations called kernels, which are in PBK file format.
We can create the PBK XML file using Pixel Bender toolkit. After our kernel is ready we can then export it as a byte-code called PBJ. PBJ can then be used in the Flash 10 player.
Flex Gumbo Bender shader is the Pixel Bender kernels, which uses a separate thread than Flash Player to calculate a single pixel.

You can use the following math functions for in pixel bender for calculations:

1. sin(x) - Trigonometric sine function.
2. cos(x) - Cosine function.
3. tan(x) – Tangent function.
4. asin(x) - Arcsine (inverse sine) function.
5. acos(x) - inverse cosine (arccosine) function.
6. atan(x) - Arctangent (inverse tangent) function.
7. atan(x, y) - Arctangent (inverse tangent) function.
8. exp(x) - exponential function.
9. log(x) – Logarithm function.
10. pow(x, y) - power of function.
11. reciprocal(x) - multiplicative inverse function.
12. sqrt(x) - square root function.

The challenge with Flash related to single thread processing always caused issues, and while the Flash Player is processing information we cannot run another thread to do other things. We often find the player get “stuck” when doing heavy lifting. Pixel Bender can help in certain cases. I managed to create an example and an API that uses Pixel Bender to calculate information, while a video is playing. In the example here you can run calculations while the video is playing and compare performance with Flash Player doing the same calculation.

First play the video and hit the Pixel Bender calculation and while the video is playing calculations are being made in the background. The results are astonishing, using Flash Player to calculate 5M sine took about 10 seconds (on iMac 2.8GHz and 4GB memory) and the video paused. Using Pixel Bender the video had a light glitch and we received the results back for the 5M calculation after about 10 secounds, without the user noticing.


Pixel Bender for calculation

Create the pbj file in Pixel Bender Toolkit:


<languageVersion : 1.0;>
kernel SinCalculator
<
    namespace : "pixelBender";
    vendor : "Elad Elrom";
    version : 1;
    description : "Sin Calculator";
>
{
    input image1 src;
    output pixel3 result;

    void evaluatePixel()
    {
    	pixel1 value = pixel1(sin(sample(src, outCoord())));
    	result = pixel3(value, 0.0, 0.0);
    }
}

The API handle calculating with Pixel bender by splitting the process into chucks of 5,000 per kernal, since Pixel Bender produce an error message on large calculations.


package com.elad.framework.pixelBender
{
	import com.elad.framework.pixelBender.events.PixelBenderCalcEvent;

	import flash.display.Shader;
	import flash.display.ShaderJob;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	[Event(name="completed", type="com.elad.pixelBender.events.PixelBenderCalcEvent")]

	/**
	 *  The PixelBenderCalculator class is the base class for math calculation with pixel bender
	 */
	public class PixelBenderCalculator extends EventDispatcher
	{
		public var kernalClass:Class;
		public var numberCollection:Array;

		private var shader:Shader;
		private var shaderJob:ShaderJob;
		private var input:ByteArray;
		private var output:ByteArray;
		private var retCollection:Array;
		private var requestsCounter:Number;
		private var numberOfRequest:Number;

		/**
		 * Number of calculations per kernal
		 */
		private const COLLECTION_SIZE:int = 5000;

		/**
		 * Default constructor for the pixel bender calculator
		 *
		 * @param numberCollection	collection contain numbers
		 * @param kernalClass	the kernal class of type pbj to be used to run the calculation
		 *
		 */
		public function PixelBenderCalculator(numberCollection:Array, kernalClass:Class)
		{
			reset();

			this.kernalClass = kernalClass;
			this.numberCollection = numberCollection;

			requestsCounter = numberCollection.length/COLLECTION_SIZE;
		}

		/**
		 * Method to start the calculation
		 *
		 */
		public function start():void
		{
		    output = new ByteArray();
		    output.endian = Endian.LITTLE_ENDIAN;

		    var start:int = numberOfRequest*COLLECTION_SIZE;
		    var end:int = ( (numberOfRequest+1)*COLLECTION_SIZE > numberCollection.length) ? numberCollection.length : ((numberOfRequest+1)*COLLECTION_SIZE);

		    input = convertArrayToByteArray(numberCollection, start, end);
		    createShaderJob();
		    numberOfRequest++;
		}

		/**
		 * Creates a shader class based on the kernal to pass the numbers and start the calculations.
		 *
		 * @see flash.display.ShaderJob
		 * @see flash.display.Shader
		 *
		 */
		private function createShaderJob():void
		{
			var width:int = input.length >> 2;
		    var height:int = 1;

		    shader = new Shader(new kernalClass());
		    shader.data.src.width = width;
		    shader.data.src.height = height;
		    shader.data.src.input = input;			    

		    shaderJob = new ShaderJob(shader, output, width, height);
		    shaderJob.addEventListener(Event.COMPLETE, shaderJobCompleteHandler);
		    shaderJob.start();
		}

		/**
		 * Static method to convert the array given into byte array.
		 *
		 * @param array
		 * @return
		 *
		 */
		private static function convertArrayToByteArray(array:Array, start:int, end:int):ByteArray
		{
			var retVal:ByteArray = new ByteArray();
		    var number:Number;

		    retVal.endian = Endian.LITTLE_ENDIAN;

		    for (var i:int=start; i<end; i++)
		    {
		    	number = Number(array[i]);
		    	retVal.writeFloat(number);
		    }

		    retVal.position = 0;
		    return retVal;
		}

		/**
		 * Convert the a <code>ByteArray</code> into an <code>Array</code>
		 *
		 * @param byteArray
		 * @return an array collection
		 *
		 */
		private function addByteArrayToCollection(byteArray:ByteArray):void
		{
		    var length:int = byteArray.length;
		    var number:Number;

		    for(var i:int=0; i<length; i+=4)
		    {
				number = byteArray.readFloat();
				if(i % 3 == 0)
				{
		    		retCollection.push(number);
		  		}
		    }
		}

		/**
		 * Handler for the shader once job is completed.
		 *
		 * @param event
		 *
		 */
		private function shaderJobCompleteHandler(event:Event):void
		{
		    output.position = 0;
			addByteArrayToCollection(output);

			input = null;
			output = null;

			if (requestsCounter>numberOfRequest)
			{
				start();
			}
			else
			{
				calculationCompleted();
			}

		}

		/**
		 * Method to dispatch an event once calculation is completed and reset this class.
		 *
		 */
		private function calculationCompleted():void
		{
			this.dispatchEvent( new PixelBenderCalcEvent(retCollection) );

			reset();
		}

		/**
		 * Method to clean up this class so we are not using un-needed memory.
		 *
		 */
		public function reset():void
		{
			retCollection = new Array();
			numberCollection = new Array();
			requestsCounter = 0;
			numberOfRequest = 0;
			numberCollection = null;
		}
	}
}

The implimentation is straight forward:


<FxApplication xmlns="http://ns.adobe.com/mxml/2009" xmlns:local="*" viewSourceURL="srcview/index.html">

	<!-- 

	////////////////////////////////////////////////////////////////////////////////
	//
	//  Elad Elrom (elad@elromdesign.com)
	//  Copyright 2009 Elorm LLC,
	//  All Rights Reserved.
	//
	//  NOTICE: Elad Elrom permits you to use, modify, and distribute this file
	//  in accordance with the terms of the license agreement accompanying it.
	//
	////////////////////////////////////////////////////////////////////////////////

 	@author  Elad Elrom

	-->

	<Script>
		<![CDATA[

			import com.elad.framework.pixelBender.PixelBenderCalculator;
			import com.elad.framework.pixelBender.events.PixelBenderCalcEvent;

			import mx.collections.ArrayCollection;
			import mx.collections.IList;
			import mx.events.FlexEvent;

			[Embed(source="SinCalculator.pbj", mimeType="application/octet-stream")]
			private var kernalClass:Class;
			private var pixelBenderCalc:PixelBenderCalculator;

			protected function startCalculatorPixelBender():void
			{
				// create a number collection
				var numberCollection:Array = new Array();

				for (var i:int=0; i<5000000; i++)
				{
					numberCollection.push( i );
				}

				// calculate
				pixelBenderCalc = new PixelBenderCalculator(numberCollection, kernalClass);
				pixelBenderCalc.addEventListener(PixelBenderCalcEvent.COMPLETED, onComplete );

				pixelBenderCalc.start();
			}

			private function startCalculatorFlashPlayer():void
			{
				// create a number collection
				var numberCollection:Array = new Array();

				for (var i:int=0; i<5000000; i++)
				{
					numberCollection.push( Math.sin(i) );
				}

				list.dataProvider = new ArrayCollection(numberCollection);
			}			

			private function onComplete(event:PixelBenderCalcEvent):void
			{
				list.dataProvider = new ArrayCollection(event.numberCollection);
				pixelBenderCalc.removeEventListener(PixelBenderCalcEvent.COMPLETED, onComplete);
			}

		]]>
	</Script>

	<List id="list" width="200" height="531.5"/>
	<Button label="Calculate with Pixel Bender" width="200" height="20" y="545" click="startCalculatorPixelBender()"/>
	<Button label="Calculate with Flash Player" width="200" height="20" y="574" click="startCalculatorFlashPlayer()"/>

	<VideoDisplay id="vid" width="355" height="290"
		source="http://thehq.tv/wp-content/uploads/flv/super-street-fighter-2-hd-round-1-trailer.flv"
		autoPlay="false" x="209" y="-1"/>
	<Button label="Play" click="vid.play();" x="213" y="303"/>

	<local:MemoryDashBoard  x="211" y="343"/>

</FxApplication>
23
Jan

Adobe AIR Mobile Touch and Multi-Touch screen applications

Today, there are already few systems that are using Adobe AIR to build a multi-touch GUI. There is an open project called Touchlib that allows listening to user gestures and building your own touch-screen. Intuilab has presented in Adobe MAX in Milan an application that takes full advantage of surface computers. The system integrates many contents such as: image, video, web content, Adobe pdf, Flash, Illustrator, Photoshop, Microsoft Office etc.

The idea behind Touch and Multi-touch computer screens is to follow the instructions of finger/s or hand/s and be able to track gestures. The idea is simple:

1. Register to receive gestures events.
2. Handle gesture events.
3. Interpret the gesture events.

Creating a touch screen application on UMPC can be done today, and we can rely on the UMPC device to handle the touch screen; however you may find that relying entirely on the device may be buggy.

For instance, the device may register a user gesture when your fingertip is on the device and didn’t even move your finger since a slight move also get registered, however you may not need your application to be that sensitive.

I recommend implementing your own instructions to register user gestures. Microsoft released the beta version of Windows 7 which track user gestures via WM_GESTURECOMMAND, WM_GESTURE and WM_TOUCH so I think it’s possible to communicate with these method using a proxy such as Merapi or you can create your Java proxy.

I have created a simple POC that I havn’t test much but it gives you an idea. The API is for touch screen application for mobile devices such as UMPC that register user gestures and can be expended to register multi-touch screen user gestures. The way it works is that the API translate the mouse events into user gestures based on time and movement.

Here’s a screen shot of the application on a UMPC:
AIR Touch Screen

And you can download it from here:
http://elromdesign.com/blog/Flex/TouchScreen/TouchScreen/Touchscreen.zip

Take a look at the class below that gives its own instructions on when to register user gestures.


package com.elad.framework.touchscreen
{
	import com.elad.framework.touchscreen.events.TouchEvent;
	import com.elad.framework.touchscreen.vo.TouchVO;

	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	import flash.utils.Timer;

	import mx.core.UIComponent;

	public class TouchManager extends EventDispatcher
	{
		private var moveTimer:Timer;
		private var previousX:int = 0;
		private var previousY:int = 0;
		private var component:UIComponent;

		public function TouchManager(component:UIComponent)
		{
			this.component = component;
		}

		public function start():void
		{
			initialize();
			this
		}

		public function stop():void
		{
			component.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
			component.removeEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);

			moveTimer.stop();
			moveTimer = null;
		}		

		protected function initialize():void
		{
			component.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
			component.addEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);
		}

		private function startTimer():void
		{
			moveTimer = new Timer(100,1000);
			moveTimer.start();
		}

		private function onMouseDownHandler(event:MouseEvent):void
		{
			startTimer();
			component.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);

			var touch:TouchVO = new TouchVO(this.previousX, this.previousY, event.localX, event.localY, this.moveTimer.currentCount);
			this.dispatchEvent( new TouchEvent(	TouchEvent.TOUCH_DOWN, touch ) );
		}

		private function onMouseUpHandler(event:MouseEvent):void
		{
			moveTimer.stop();
			component.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);

			var touch:TouchVO = new TouchVO(this.previousX, this.previousY, event.localX, event.localY, this.moveTimer.currentCount);
			this.dispatchEvent( new TouchEvent(	TouchEvent.TOUCH_UP, touch ) );
		}

		private function onMouseMoveHandler(event:MouseEvent):void
		{
			var isMove:Boolean = isTouchMove(event.localX, event.localY);
			var touch:TouchVO = new TouchVO(this.previousX, this.previousY, event.localX, event.localY, this.moveTimer.currentCount);

			if (isMove)
			{
				this.dispatchEvent( new TouchEvent(TouchEvent.TOUCH_DRAG, touch) );
			}
		}

		private function isTouchMove(x:int, y:int):Boolean
		{
			var retVal:Boolean = false;
			var ignore:int = 3;
			var isXmoved:Boolean;
			var isYmoved:Boolean;

			if (previousX != 0 && previousY != 0)
			{
				isXmoved = (x > previousX+ignore || x < previousX-ignore) ? true : false;
				isYmoved = (y > previousY+ignore || y < previousY-ignore) ? true : false;

				if ( isXmoved || isYmoved )
				{
					retVal=true;
				}
			}

			previousX = x;
			previousY = y;

			return retVal;
		}		

	}
}

And than we can implement the class with a simple interface:


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009" layout="absolute"
	width="800" height="600"
	initialize="initializeHandler()">

	<Script>
		<![CDATA[
			import com.elad.framework.touchscreen.vo.TouchVO;
			import com.elad.framework.touchscreen.events.TouchEvent;
			import com.elad.framework.touchscreen.TouchManager;
			import mx.collections.ArrayCollection;

			[Bindable]
			private var arrayCollection:ArrayCollection = new ArrayCollection;

			private var touch:TouchManager;

			protected function initializeHandler():void
			{
				touch = new TouchManager(this);
				touch.addEventListener(TouchEvent.TOUCH_DOWN, onMouseDownHandler);
				touch.addEventListener(TouchEvent.TOUCH_UP, onMouseUpHandler);
				touch.addEventListener(TouchEvent.TOUCH_DRAG, onMouseDragHandler);

				touch.start();
			}						

			private function onMouseDownHandler(event:TouchEvent):void
			{
				ellipse.visible = true;
				moveEllipse(event.touchVO.currentX, event.touchVO.currentY);
				registerLocation(ellipse.x, ellipse.y, "MouseDown", 0);
			}	

			private function onMouseUpHandler(event:TouchEvent):void
			{
				ellipse.visible = false;
			}					

			private function onMouseDragHandler(event:TouchEvent):void
			{
					moveEllipse(event.touchVO.currentX, event.touchVO.currentY);
					registerLocation(ellipse.x, ellipse.y, "MouseMove", event.touchVO.moveTimer);
			}

			private function registerLocation(x:int, y:int, type:String, time:int):void
			{
				arrayCollection.addItem({locationX: ellipse.x, locationY: ellipse.y, type: type, time: time});
				dg.dataProvider = arrayCollection;
			}

			private function moveEllipse(x:int, y:int):void
			{
				ellipse.x = x - ellipse.width/2;
				ellipse.y = y - ellipse.height/2;
			}			

		]]>
	</Script>

	<Group>
		<Ellipse height="80" width="80" id="ellipse" visible="false">
			<stroke>
				<SolidColorStroke color="0x000000" weight="2"/>
			</stroke>
		</Ellipse>
	</Group>

	<DataGrid x="341" y="4" id="dg" dataProvider="{arrayCollection}" height="410">
		<columns>
			<DataGridColumn headerText="X" dataField="locationX"/>
			<DataGridColumn headerText="Y" dataField="locationY"/>
			<DataGridColumn headerText="Type" dataField="type"/>
			<DataGridColumn headerText="Time" dataField="time"/>
		</columns>
	</DataGrid>

I am looking for a .NET developer to spare time to create a simple class that gets Windows 7 events and compile them into a DLL, so we can pick them up in a Java proxy, which will make a request every few millisecond and look for user gesture changes, so if you are interested, please reply in this blog post.

18
Jan

Adobe AIR SQLite Manager help you handle your database easily.

Working with Adobe AIR you often find yourself writing the same code over and over again to do common tasks such as:

1. Connecting to a database.
2. Execute common SQL commands such as “SELECT * FROM table”.
3. Execute custom SQL commands on tables.
4. Closing connection.

Additionally, user may erase the database or the database isn’t created yet, which requires you to check that the database exists and the table exists and if not create the table.

AIR APIs create the sql database automatically, if it doesn’t exists, but you need to handle the create table SQL command and execute it or copy a sql dabase with the table.

SQLite Manager easily manage these tasks automatically. Let’s take a look;

Our contract is pretty straight forward, we just need to ensure the connection starts and ends:


/*

     Copyright (c) 2009 Elad Elrom.  Elrom LLC. All rights reserved. 

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

     @author  Elad Elrom
     @contact elad.ny at gmail.com

 */
package com.elad.framework.sqlite
{
	/**
	 * Describes the contract for Objects that serve as a central point to access SQLite database.
	 *
	 * @author Elad Elrom
	 *
	 */
	public interface ISQLiteManager
	{
		function start(dbFullFileName:String, tableName:String, createTableStatement:String):void
		function close():void
	}
}

The SQLite manager handles all these common tasks. It’s a singleton to ensure we don’t open more than one connection at a time as well as avoiding placing the setting information over and over again. In case the database doesn’t exists it will be created automatically and in case the table doesn’t exists it will be generated by a SQL command.

Take a look at the SQLite manager:


/*

     Copyright (c) 2009 Elad Elrom.  Elrom LLC. All rights reserved. 

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

     @author  Elad Elrom
     @contact elad.ny at gmail.com

 */
package com.elad.framework.sqlite
{
	import com.elad.framework.sqlite.events.StatementSuccessEvent;

	import flash.data.SQLConnection;
	import flash.data.SQLStatement;
	import flash.errors.SQLError;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.SQLErrorEvent;
	import flash.events.SQLEvent;
	import flash.filesystem.File;

	/**
	 * <code>SQLiteManager</code> help handling the database connections and calls. You pass a start properties to the singleton and you
	 * can execute common commands or custom commands.
	 *
	 * @author elad
	 *
	 * @example The following code sets the volume level for your sound:
	 * <listing version="3.0">
	 * 		var database:SQLiteManager = SQLiteManager.getInstance();
	 * 		database.start("Users.sql3", "Users", "CREATE TABLE Users(userId VARCHAR(150) PRIMARY KEY, UserName VARCHAR(150))");
	 *
	 * 		database.addEventListener(SQLiteManager.COMMAND_EXEC_SUCCESSFULLY, onSelectResult);
	 * 		database.addEventListener(SQLiteManager.COMMAND_EXEC_FAILED, onFail);
	 *
	 * 		database.executeSelectAllCommand();
	 *
	 *		private function onSelectResult(event:StatementSuccessEvent):void
	 *		{
	 *		     var result:Array = event.results.data;
	 *		}
	 *
	 *		private function onFail(event:Event):void
	 *		{
	 *		     // handle fail
	 *		}
	 * </listing>
	 *
	 */
	public class SQLiteManager extends EventDispatcher implements ISQLiteManager
	{
		/**
		 * Database file name and extension
		 */
		public var dbFullFileName:String;

		/**
		 * Database Name
		 */
		public var tableName:String;

		/**
		 * SQL command to create the database
		 */
		public var createDbStatement:String;

		// datsbase apis instances
		protected var connection:SQLConnection;
		protected var statement:SQLStatement;
		protected var sqlFile:File;

		// repeated sql command
		protected var repeateFailCallBack:Function;
		protected var repeateCallBack:Function;
		protected var repeateSqlCommand:String = "";

		// events strings
		public static var COMMAND_EXEC_SUCCESSFULLY:String = "commandExecSuccesfully";
		public static var DATABASE_CONNECTED_SUCCESSFULLY:String = "databaseConnectedSuccessfully";
		public static var COMMAND_EXEC_FAILED:String = "commandExecFailed";
		public static var DATABASE_READY:String = "databaseReady";

		// Singleton instance.
		protected static var instance:SQLiteManager;

		/**
		 * Enforce singleton design pattern.
		 *
		 * @param enforcer
		 *
		 */
		public function SQLiteManager(enforcer:AccessRestriction)
		{
			if (enforcer == null)
				throw new Error("Error enforcer input param is undefined" );
		}

		/**
		 * Opens a database connection.
		 *
		 * @param dbFullFileName the database file name for instance: Users.sql
		 * @param tableName holds the database name, for instance: Users
		 * @param createTableStatement holds the create database statment for instance: CREATE TABLE Users(userId VARCHAR(150) PRIMARY KEY, UserName VARCHAR(150))
		 *
		 */
		public function start(dbFullFileName:String, tableName:String, createTableStatement:String):void
		{
			this.dbFullFileName = dbFullFileName;
			this.tableName = tableName;
			this.createDbStatement = createTableStatement;

			connection = new SQLConnection();
			sqlFile = File.applicationStorageDirectory.resolvePath(dbFullFileName);

			try
			{
			    connection.open(sqlFile);
			    this.dispatchEvent(new Event(DATABASE_CONNECTED_SUCCESSFULLY));
			}
			catch (error:SQLError)
			{
			    trace("Error message:", error.message);
			    trace("Details:", error.details);
			    fail();
			}
		}

		/**
		 * Close connection
		 *
		 */
		public function close():void
		{
			connection.close();
		}		

		/**
		 * Test the table to ensure it exists. Sends a fail call back function to create the table if
		 * it doesn't exists.
		 *
		 */
		public function testTableExists():void
		{
			var sql:String = "SELECT * FROM "+tableName+" LIMIT 1;";
			executeCustomCommand(sql, this.onDatabaseReady, this.createTable );
		}

		/**
		 * Method to create the database table.
		 *
		 */
		private function createTable():void
		{
		    statement = new SQLStatement();
		    statement.sqlConnection = connection;
		    statement.text = createDbStatement;
		    statement.execute();

		    statement.addEventListener(SQLEvent.RESULT, onDatabaseReady);
		}

		/**
		 * Common sql command: select all entries in database
		 *
		 * @param callback
		 * @param failCallback
		 *
		 */
		public function executeSelectAllCommand(callback:Function=null, failCallback:Function=null):void
		{
			var sql:String = "SELECT * FROM "+tableName+";";
			executeCustomCommand(sql, callback, failCallback);
		}

		/**
		 * Common sql command: delete all entries in database
		 *
		 * @param callback
		 *
		 */
		public function executeDeleteAllCommand(callback:Function=null):void
		{
			var sql:String = "DELETE * FROM "+tableName+";";
			executeCustomCommand(sql, callback);
		}		

		/**
		 * Method to execute a SQL command
		 *
		 * @param sql SQL command string
		 * @param callback success call back function to impliment if necessery
		 * @param failCallBack fail call back function to impliment if necessery
		 *
		 */
		public function executeCustomCommand(sql:String, callBack:Function=null, failCallBack:Function=null):void
		{
		    statement = new SQLStatement();
		    statement.sqlConnection = connection;

		    statement.text = sql;

		    if (callBack!=null)
		    {
		    	statement.addEventListener(SQLEvent.RESULT, callBack);
		    }
		    else
		    {
		    	statement.addEventListener(SQLEvent.RESULT, onStatementSuccess);
		    }

		    statement.addEventListener(SQLErrorEvent.ERROR, function():void {
		    		fail();
		    	});

			try
			{
			    statement.execute();
			}
			catch (error:SQLError)
			{
				this.handleErrors(error, sql, callBack, failCallBack);
			}
		}

		/**
		 * Utility method to clean bad characters that can break SQL commands
		 *
		 * @param str
		 * @return
		 *
		 */
		public static function removeBadCharacters(str:String):String
		{
			var retVal:String = str.split("'").join("&#8217;&rsquo;");
			return retVal;
		}

		// ------------------------------HANDLERS----------------------------		

		/**
		 * Method to handle SQL command that create the dataabase.
		 * If the method was created due to a fail SQL command method checks if need to repeate any SQL command.
		 *
		 * @param event
		 *
		 */
		private function onDatabaseReady(event:Event=null):void
		{
			var evt:Event = new Event(DATABASE_READY);
			this.dispatchEvent(evt);

			if (repeateSqlCommand != "")
			{
				this.executeCustomCommand(repeateSqlCommand, repeateCallBack, repeateFailCallBack);

				repeateSqlCommand = "";
				repeateFailCallBack = null;
				repeateCallBack = null;
			}
		}

		/**
		 * Handle successful calls
		 * @param event
		 *
		 */
		private function onStatementSuccess(event:SQLEvent):void
		{
			var results:Object = statement.getResult();
			var evt:StatementSuccessEvent = new StatementSuccessEvent(COMMAND_EXEC_SUCCESSFULLY, results);
			this.dispatchEvent(evt);
		}

		/**
		 * Error handler
		 *
		 * @param error
		 * @param sql
		 * @param callBack
		 * @param failCallBack
		 *
		 */
		private function handleErrors(error:SQLError, sql:String, callBack:Function, failCallBack:Function):void
		{
			trace("Error message:", error.message);
			trace("Details:", error.details);

		    if (error.details == "no such table: '"+tableName+"'")
		    {
		    	repeateSqlCommand = sql;
		    	repeateFailCallBack = failCallBack;
		    	repeateCallBack = callBack;
		    	createTable();
		    }
		    else
		    {
			    if (failCallBack != null)
			    {
			    	failCallBack();
			    }
			    else
			    {
			    	fail();
			    }
		    }
		}

		/**
		 * Handler for fail calls
		 *
		 * @param event
		 *
		 */
		private function fail(event:Event=null):void
		{
	        var evt:Event = new Event(COMMAND_EXEC_FAILED);
	        this.dispatchEvent(evt);

	        close();
		}

		/**
		 * Method function to retrieve instance of the class
		 *
		 * @return The same instance of the class
		 *
		 */
		public static function getInstance():SQLiteManager
		{
			if( instance == null )
				instance = new  SQLiteManager(new AccessRestriction());

			return instance;
		}

	}
}

class AccessRestriction {}

Here’s an example of implementing the SQL manager;


var database:SQLiteManager = SQLiteManager.getInstance();
database.start("Users.sql3", "Users", "CREATE TABLE Users(userId VARCHAR(150) PRIMARY KEY, UserName VARCHAR(150))");

database.addEventListener(SQLiteManager.COMMAND_EXEC_SUCCESSFULLY, onSelectResult);
database.addEventListener(SQLiteManager.COMMAND_EXEC_FAILED, onFail);

database.executeSelectAllCommand();

private function onSelectResult(event:StatementSuccessEvent):void
{
     var result:Array = event.results.data;
}

private function onFail(event:Event):void
{
     // handle fail
}

And here’s a custom SQL command using custom SQL command:


var sql:String =  "INSERT INTO Users VALUES('"+userVO.userId+"','"+userVO.userName+"');";

database.addEventListener(SQLiteManager.COMMAND_EXEC_SUCCESSFULLY, onInsertSuccess);
database.executeCustomCommand(sql);

Take a look at this example. Very simple and easy to insert and read from the SQLite database.


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009" layout="absolute"
	initialize="initializeHandler(event)">

	<Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.events.FlexEvent;
			import com.elad.framework.sqlite.events.StatementSuccessEvent;
			import com.elad.framework.sqlite.SQLiteManager;

			private var database:SQLiteManager = SQLiteManager.getInstance();

			protected function initializeHandler(event:FlexEvent):void
			{
				database.start("Users.sql3", "Users", "CREATE TABLE Users(UserId VARCHAR(150) PRIMARY KEY, UserName VARCHAR(150))");

				database.addEventListener(SQLiteManager.COMMAND_EXEC_SUCCESSFULLY, onSelectResult);
				database.addEventListener(SQLiteManager.COMMAND_EXEC_FAILED, function():void {
					trace("fail!");
				});

				readEntries();
			}

			private function insertEntry():void
			{
				var sql:String =  "INSERT INTO Users VALUES('"+String(userId.text)+"','"+userName.text+"');";
				database.executeCustomCommand(sql);
			}

			private function readEntries():void
			{
				database.executeSelectAllCommand();
			}

			private function onSelectResult(event:StatementSuccessEvent):void
			{
				var result:Array = event.results.data;
				var rowsAffected:int = event.results.rowsAffected;  

				if (rowsAffected == 1)
					readEntries();

				if (result == null)
					return;

				var len:int = result.length;
				var dp:ArrayCollection = new ArrayCollection();

				for (var i:int; i<len; i++)
				{
					dp.addItem( {UserId: result[i].UserId, UserName: result[i].UserName} );
				}

				dataGrid.dataProvider = dp;
			}			

		]]>
	</Script>
	<Panel x="5" y="5" layout="absolute" height="356">

		<VBox horizontalScrollPolicy="off" verticalScrollPolicy="off">
			<!-- Form -->
			<Form width="414">
				<FormItem label="User ID:">
					<FxTextInput id="userId"/>
				</FormItem>
				<FormItem label="User Name:">
					<FxTextInput id="userName"/>
				</FormItem>
				<FormItem>
					<FxButton label="Insert Entry" click="insertEntry();"/>
				</FormItem>
			</Form>
		</VBox>

		<!-- Results -->
		<DataGrid x="19" y="123" id="dataGrid">
			<columns>
				<DataGridColumn headerText="User Id" dataField="UserId"/>
				<DataGridColumn headerText="User Name" dataField="UserName"/>
			</columns>
		</DataGrid>

	</Panel>

</WindowedApplication>

SQLite Manager example

Click here to download the sample application

17
Jan

Just released - Adobe AIR YouTube Video Widget works Online and Offline. Watch a presentation video

Alpha version of YouTube Video Widget, allows you to search videos from YouTube and download them into your local drive. Once you go offline the application automatically aware of the change and switches to offline mode, which allow you to view the downloaded videos.

Application can be deployed on desktops or on a touch screen UMPC as well as future ARM devices that will support AIR 1.5.

Download the application from Adobe AIR Marketplace or from here.

Developed with Flex 4 (Gumbo), Flash Catalyst and AIR 1.5. The framework is Caringorm with the Presentation Model, allows us to easily create a template so the application changes based on platform.

YouTube AIR Widget Preview

Once you select a video you can download the video. Which will be saved on your local drive and information captured in SQLite database. In fact the application recognize automatically that you went offline.

YouTube AIR Widget Preview, download video.

I made a quick presentation that shows the application capabilities. Take a look at the video below:

The full tutorials and explanation on how to build applications like this one will be available in our new book which will be out in May by APress and can be purchased now for a limited discount price from Amazon.

14
Jan

Adobe AIR 1.5 DownloadManager API - download files from web / server to your local drive

I wrote couple of handy classes to handle retrieving / downloading of any type of files to a local directory in AIR. These classes are useful for building an AIR application that works online and offline. You can download multimedia, HTML and other file types. Once files are downloaded you can store the information in SQLite database and serve the information, once your system is not connected to the internet.

The core class “DownloadManager.as” allows you to download files to any location in your local drive using: “File.desktopDirectory.resolvePath(fileLocalLocation)” or you can also point to where the application directory sits: “File.applicationStorageDirectory.resolvePath(fileLocalLocation);”


		public function downloadFileFromServer(fileURL:String, fileLocalLocation:String):void
		{
			//var file:File = File.desktopDirectory.resolvePath(fileLocalLocation);
			var file:File = File.applicationStorageDirectory.resolvePath(fileLocalLocation);
			request = new URLRequest(fileURL);
	        fileStream.openAsync(file, FileMode.WRITE);

	        stream.addEventListener(ProgressEvent.PROGRESS, onDownloadProgress);
	        stream.addEventListener(Event.COMPLETE, onDownloadComplete);

	        stream.load(request);
		}

Notice we defined the steam event handlers and load the file. We now need to define event handler, to handle the progess during the download and once completed;


        private function onDownloadProgress(event:ProgressEvent):void
        {
            var byteArray:ByteArray = new ByteArray();
            var precent:Number = Math.round(bytesLoaded*100/bytesTotal);

            bytesLoaded = event.bytesLoaded;
            bytesTotal = event.bytesTotal;

            stream.readBytes(byteArray, 0, stream.bytesAvailable);
            fileStream.writeBytes(byteArray, 0, byteArray.length);

			var progressEvent:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS);
			progressEvent.bytesLoaded = bytesLoaded;
			progressEvent.bytesTotal = bytesTotal;

			this.dispatchEvent(progressEvent);
    	}

    	private function onDownloadComplete(event:Event):void
    	{
            fileStream.close();
            stream.close(); 

            stream.removeEventListener(ProgressEvent.PROGRESS, onDownloadProgress);
            stream.removeEventListener(Event.COMPLETE, onDownloadComplete);

			var completeEvent:Event = new Event(Event.COMPLETE);
			this.dispatchEvent(completeEvent);
    	}

Here’s a little MXML component that implements the download manager and the ReadDirectoryHelper.
Once we init the application, we check if the file exists and download the file if it doesn’t exists. Additionally, we are using the download manager with a ProgressBar to show the download progress.


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009"
	layout="absolute"
	initialize="init()">

	<Script>
		<![CDATA[
			import com.elad.framework.utils.ReadDirectoryHelper;
			import com.elad.framework.utils.DownloadManager;
			import mx.controls.Alert;

			private function init():void
			{
				var array:Array = ReadDirectoryHelper.getDirectoryFiles("/Users/elad/Desktop/tmp/");
				var isExists:Boolean = ReadDirectoryHelper.isFileExists(array, "file.flv");

				if (isExists == false)
					downloadFile();
				else
					Alert.show("File Already exists");
			}

			private function downloadFile():void
			{
		        var downloadHelper:DownloadManager = new DownloadManager();

		        downloadHelper.addEventListener(ProgressEvent.PROGRESS, onDownloadProgress);
		        downloadHelper.addEventListener(Event.COMPLETE, onDownloadComplete);
		        downloadHelper.downloadFileFromServer("http://samples.mplayerhq.hu/FLV/asian-commercials-are-weird.flv",
		        "/Users/elad/Desktop/tmp/file.flv");
			}

	        private function onDownloadProgress(event:ProgressEvent):void
	        {
                var value:Number = event.bytesLoaded;
                var total:Number = event.bytesTotal;
                var precent:Number = Math.round(value*100/total);

				if (progressBar.minimum==0)
				{
					progressBar.minimum = value;
					progressBar.maximum = total;
				}

				progressBar.label = "Progress "+precent+"%";
                progressBar.setProgress(value, total);
        	}

        	private function onDownloadComplete(event:Event):void
        	{
 				Alert.show("Download Completed!");
        	}

		]]>
	</Script>

	<ProgressBar id="progressBar"
		x="4" y="63"
		minimum="0"
		label="Progress 0%"
		mode="manual" />

</WindowedApplication>

Download Files from Web to local drive Adobe AIR

To download the source code of the AIR application click here.

05
Jan

Cross Platform AIR Application on Flex Gumbo - Context Awareness Manager to gather, detect and translate system info

The cure in adopting AIR as a cross platform application as well as creating mobile applications is to be able to know the user’s system capabilities as well as track changes in its environment.

The AIR Context Awareness Manager can help you develop a cross-platform application which is operation system (OS) independent.

AIR Context Awareness Manager API can be utilized to gather and track information available in our application. We can split the information gathered into three categories:

1. System capability and support.
2. User activity.
3. Track changes in system.

As new capabilities will be available we can add them to the manager.

Download SWC, example and source code from Google code:
http://code.google.com/p/contextawarenessmanager/

Adobe AIR is available on PC, MAC and Linux, and as Adobe Open Screen Project materializes into a reality with the announcement of Flash 10 and AIR available on mobile devices, the challenge will increase.

Platforms are usually capable of adjusting to many changes in the device on their own. For instance, system track network errors and displaying an error message, however, our application cannot rely entirely on the platform to adjust correctly and we may want to make changes in the application once changes in the device occur. For instance, our AIR application can go into offline mode once we are not connected to the internet.

In essence our AIR application should have the ability to react and adjust during the entire lifetime of the application, additionally our application is responsible for hiding issues and constraints of the mobile device from the user. For instance, user doesn’t care that our system has low network download speed and will blame our application on choppy video.

In order to achieve that kind of awareness in our application, we should be able to be aware of the changes as well as translate the context. That’s where Context Awareness comes into place. Context Awareness means that once we get information regarding the application platform and as changes are made in the platform environment we need to translate the information and understand what it means as well as react accordingly.

Most of the system capability information is available through the flash.system.Capabilities API. The Capabilities that API provides are properties that describe the system and flash player that are hosting the application. ContextVO will hold the capability properties as well as other properties.

Detecting System Capabilities


          public function ContextVO()
          {
               avHardwareDisable = Capabilities.avHardwareDisable;
               hasAccessibility = Capabilities.hasAccessibility;
               hasAudio = Capabilities.hasAudio;
               hasAudioEncoder  = Capabilities.hasAudioEncoder;
               hasEmbeddedVideo = Capabilities.hasEmbeddedVideo;
               hasMP3 = Capabilities.hasMP3;
               hasPrinting = Capabilities.hasPrinting;
               hasScreenBroadcast = Capabilities.hasScreenBroadcast;
               hasScreenPlayback = Capabilities.hasScreenPlayback;
               hasStreamingAudio = Capabilities.hasStreamingAudio;
               hasVideoEncoder = Capabilities.hasVideoEncoder;
               isDebugger = Capabilities.isDebugger;
               language = Capabilities.language;
               localFileReadDisable = Capabilities.localFileReadDisable;
               manufacturer = Capabilities.manufacturer;
               os = Capabilities.os;
               osName = Capabilities.os.substr(0, 3).toLowerCase();
               pixelAspectRatio = Capabilities.pixelAspectRatio;
               playerType = Capabilities.playerType;
               screenColor = Capabilities.screenColor;
               screenDPI = Capabilities.screenDPI;
               screenResolutionX = Capabilities.screenResolutionX;
               screenResolutionY = Capabilities.screenResolutionY;
               serverString = Capabilities.serverString;
               version = Capabilities.version;
          }

Detecting System Support


private function setSystemSupportCapability():void
{
     contextVO.supportsDockIcon = NativeApplication.supportsDockIcon;
     contextVO.supportsMenu = NativeApplication.supportsMenu;
     contextVO.supportsSystemTrayIcon = NativeApplication.supportsSystemTrayIcon;
     contextVO.supportsMenu = NativeWindow.supportsMenu;
     contextVO.supportsNotification = NativeWindow.supportsNotification;
     contextVO.supportsTransparency = NativeWindow.supportsTransparency;
     contextVO.systemMaxSize = NativeWindow.systemMaxSize;
     contextVO.systemMinSize = NativeWindow.systemMinSize;
}

Detecting User Presence


private function detectUserPresence():void
{
     nativeApp.idleThreshold = idleThresholdTime;
     nativeApp.addEventListener(Event.USER_IDLE, onUserIdleHandler);
     nativeApp.addEventListener(Event.USER_PRESENT, onUserPresentHandler);
}
private function onUserIdleHandler(evt:Event):void
{
     var lastUserInput:Number = NativeApplication.nativeApplication.timeSinceLastUserInput;
     var event:Event = new Event(USER_IDLE, true);
     contextVO.isUserPresent = false;
     contextVO.lastUserInput = lastUserInput;
     this.dispatchEvent(event);
}
private function onUserPresentHandler(evt:Event):void
{
     var event:Event = new Event(USER_PRESENT, true);
     contextVO.isUserPresent = true;
     this.dispatchEvent(event);
}

Detecting network connectivity changes


private function detectNetworkChanges():void
{
     nativeApp.addEventListener(Event.NETWORK_CHANGE, onNetworkStatusChange);
}
private function onNetworkStatusChange(evt:Event):void
{
     var event:Event = new Event(NETWORK_CHANGE, true);
     contextVO.isNetworkChanged = true;
     this.dispatchEvent(event);
}

Detecting HTTP connectivity


private function detectHTTPConnectivity():void
{
     monitor = new URLMonitor(new URLRequest(siteToTrack));
     monitor.addEventListener(StatusEvent.STATUS, onHTTPConnectivityChange);
     monitor.start();
}
private function onHTTPConnectivityChange(evt:StatusEvent):void
{
    var event:Event;
    contextVO.isHTTPAvaliable = monitor.available;
    event = (monitor.available) ? new Event(HTTP_CONNECTIVITY_TRUE, true) :
         new Event(HTTP_CONNECTIVITY_FALSE, true);</p>
<p>     this.dispatchEvent(event);
}

Detecting socket connectivity


private function detectSocketConnectivity():void
{
     socketMonitor = new SocketMonitor(siteSocketMonitor,portToCheck);
     socketMonitor.addEventListener(StatusEvent.STATUS, onSocketStatusChange);
     socketMonitor.start();
}
private function onSocketStatusChange(evt:StatusEvent):void
{
     var event:Event;
     contextVO.isSocketMonitorAvailable = socketMonitor.available;
     event = (socketMonitor.available) ? new Event(SOCKET_CONNECTIVITY_TRUE, true) :
         new Event(SOCKET_CONNECTIVITY_FALSE, true);
     this.dispatchEvent(event);
}

Detecting Local Drives


private function detectLocalDrivers():void
{
     contextVO.currentAvaliableDrives = (contextVO.osName=="mac") ?
          new File('/Volumes/').getDirectoryListing() : File.getRootDirectories() ;
}
public function refreshLocalDrives():void
{
     detectLocalDrivers();
}

Detecting WindowedApplication movement


private function detectWindowedApplicationMovment():void
{
     Application.application.addEventListener(NativeWindowBoundsEvent.MOVING, onWindowedApplicationMovment);
}
private function onWindowedApplicationMovment(evt:NativeWindowBoundsEvent):void
{
     var event:Event = new Event(NATIVE_WINDOW_MOVED, true);
     contextVO.windowPositionAfterBounds = evt.afterBounds;
     contextVO.windowPositionBeforeBounds = evt.beforeBounds;
     this.dispatchEvent(event);
}

Getting the runtime version and patch level


private function setRuntimeInformation():void
{
     contextVO.getRuntimeVersion = nativeApp.runtimeVersion;
     contextVO.getRuntimePatchLevel = nativeApp.runtimePatchLevel;
}

I have created an example of a monitor application that will display the changes across the application. Take a look at the code below:

Context Awareness Manager


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009"
	layout="absolute"
	creationComplete="init()">

	<Script>
		<![CDATA[
			import com.elad.framework.utils.ContextAwarenessManager;

			private var contextAwareness:ContextAwarenessManager = ContextAwarenessManager.getInstance();

			private function init():void
			{
				contextAwareness.addEventListener(ContextAwarenessManager.USER_IDLE, onUserHandler);
				contextAwareness.addEventListener(ContextAwarenessManager.USER_PRESENT, onUserHandler);
				contextAwareness.addEventListener(ContextAwarenessManager.HTTP_CONNECTIVITY_TRUE, onHTTPConnectivityChange);
				contextAwareness.addEventListener(ContextAwarenessManager.HTTP_CONNECTIVITY_FALSE, onHTTPConnectivityChange);
				contextAwareness.addEventListener(ContextAwarenessManager.NATIVE_WINDOW_MOVED, onWindowedApplicationMovment);
				contextAwareness.addEventListener(ContextAwarenessManager.NETWORK_CHANGE, onNetworkStatusChange);
				contextAwareness.addEventListener(ContextAwarenessManager.SOCKET_CONNECTIVITY_FALSE, onSocketStatusChange);
				contextAwareness.addEventListener(ContextAwarenessManager.SOCKET_CONNECTIVITY_TRUE, onSocketStatusChange);

				contextAwareness.start();
				textArea.text = "Start Tracking";
			}

			private function onUserHandler(evt:Event):void
			{
				textArea.text += "\nisUserPresent: "+contextAwareness.contextVO.isUserPresent.toString();
				textArea.text += "\nlastUserInput: "+contextAwareness.contextVO.lastUserInput.toString();
			}

			private function onNetworkStatusChange(evt:Event):void
			{
				textArea.text += "\nisNetworkChanged: "+contextAwareness.contextVO.isNetworkChanged.toString();
			}

			private function onHTTPConnectivityChange(evt:Event):void
			{
			    textArea.text += "\nisHTTPAvaliable: "+contextAwareness.contextVO.isHTTPAvaliable.toString();
			}

			private function onSocketStatusChange(evt:Event):void
			{
				textArea.text += "\nisSocketMonitorAvailable: "+contextAwareness.contextVO.isSocketMonitorAvailable.toString();
			}

			private function onWindowedApplicationMovment(evt:Event):void
			{
				textArea.text += "\nwindowPositionAfterBounds: "+contextAwareness.contextVO.windowPositionAfterBounds.toString();
				textArea.text += "\nwindowPositionBeforeBounds: "+contextAwareness.contextVO.windowPositionBeforeBounds.toString();
			}

			private function showCapabilitiesClickHandler(event:MouseEvent):void
			{
				textArea.text += "\navHardwareDisable: "+contextAwareness.contextVO.avHardwareDisable.toString();
				textArea.text += "\nhasAccessibility: "+contextAwareness.contextVO.hasAccessibility.toString();
				textArea.text += "\nhasAudio: "+contextAwareness.contextVO.hasAudio.toString();
				textArea.text += "\nhasAudioEncoder : "+contextAwareness.contextVO.hasAudioEncoder.toString();
				textArea.text += "\nhasEmbeddedVideo: "+contextAwareness.contextVO.hasEmbeddedVideo.toString();
				textArea.text += "\nhasMP3: "+contextAwareness.contextVO.hasMP3.toString();
				textArea.text += "\nhasPrinting: "+contextAwareness.contextVO.hasPrinting.toString();
				textArea.text += "\nhasScreenBroadcast: "+contextAwareness.contextVO.hasScreenBroadcast.toString();
				textArea.text += "\nhasScreenPlayback: "+contextAwareness.contextVO.hasScreenPlayback.toString();
				textArea.text += "\nhasStreamingAudio: "+contextAwareness.contextVO.hasStreamingAudio.toString();
				textArea.text += "\nhasVideoEncoder: "+contextAwareness.contextVO.hasVideoEncoder.toString();
				textArea.text += "\nisDebugger: "+contextAwareness.contextVO.isDebugger.toString();
				textArea.text += "\nlanguage: "+contextAwareness.contextVO.language.toString();
				textArea.text += "\nlocalFileReadDisable: "+contextAwareness.contextVO.localFileReadDisable.toString();
				textArea.text += "\nmanufacturer: "+contextAwareness.contextVO.manufacturer.toString();
				textArea.text += "\nos: "+contextAwareness.contextVO.os.toString();
				textArea.text += "\nosName: "+contextAwareness.contextVO.osName.toString();
				textArea.text += "\npixelAspectRatio: "+contextAwareness.contextVO.pixelAspectRatio.toString();
				textArea.text += "\nplayerType: "+contextAwareness.contextVO.playerType.toString();
				textArea.text += "\nscreenColor: "+contextAwareness.contextVO.screenColor.toString();
				textArea.text += "\nscreenDPI: "+contextAwareness.contextVO.screenDPI.toString();
				textArea.text += "\nscreenResolutionX: "+contextAwareness.contextVO.screenResolutionX.toString();
				textArea.text += "\nscreenResolutionY: "+contextAwareness.contextVO.screenResolutionY.toString();
				textArea.text += "\nserverString: "+contextAwareness.contextVO.serverString.toString();
				textArea.text += "\nversion: "+contextAwareness.contextVO.version.toString();
			}

			private function showSystemSupportClickHandler(event:MouseEvent):void
			{
				textArea.text += "\nsupportsDockIcon: "+contextAwareness.contextVO.supportsDockIcon.toString();
				textArea.text += "\nsupportsMenu: "+contextAwareness.contextVO.supportsMenu.toString();
				textArea.text += "\nsupportsSystemTrayIcon: "+contextAwareness.contextVO.supportsSystemTrayIcon.toString();
				textArea.text += "\nsupportsNotification: "+contextAwareness.contextVO.supportsNotification.toString();
				textArea.text += "\nsupportsTransparency: "+contextAwareness.contextVO.supportsTransparency.toString();
				textArea.text += "\nsystemMaxSize: "+contextAwareness.contextVO.systemMaxSize.toString();
				textArea.text += "\nsystemMinSize: "+contextAwareness.contextVO.systemMinSize.toString();
			}

		]]>
	</Script>

	<VBox width="100%" height="100%" >
		<TextArea id="textArea" width="100%" height="95%" />

		<HBox>
			<Button label="Show System Capabilities" click="showCapabilitiesClickHandler(event)" />
			<Button label="Show System Support" click="showSystemSupportClickHandler(event)" />
		</HBox>

		<HBox>
			<Button label="Start Tracking" click="contextAwareness.start(); textArea.text+='\nStart Tracking';" />
			<Button label="Stop Tracking" click="contextAwareness.stop(); textArea.text+='\nStop Tracking';" />
			<Button label="Clear TextArea" click="textArea.text=''" />
		</HBox>		

	</VBox>

</WindowedApplication>

Download SWC, example and source code from Google code:
http://code.google.com/p/contextawarenessmanager/