"adobe-air" Posts

Tip of the Day – Populate Version Number from an AIR Descriptor File in ANT

So I’ve seen this question pop up a few times.

Can I grab a version number from the app-descriptor via ant and add that to the AIR filename? Anyone know?

This is a pretty straight forward thing to do using Ant-Contrib tasks, that are publicly available.

You can download the example files –> here.

Let’s take a quick look at the ANT script.

<project name="PopulateVersion" default="populateVersion" basedir=".">
 
  <taskdef resource="net/sf/antcontrib/antcontrib.properties">
      <classpath>
          <pathelement location="./ant-contrib.jar"/>
      </classpath>
  </taskdef>
 
  <property name="app.descriptor" value="air-app.xml" />
 
  <target name="populateVersion">
 
    <echo message="Parsing application.version from ${app.descriptor}"/>
    <xmlproperty file="${app.descriptor}" prefix="airApp.appdescriptor"/>
    <propertycopy property="versionNumber" 
                  from="airApp.appdescriptor.application.versionNumber" 
                  override="true" />
    <echo message="Parsed application version: ${versionNumber}"/>  
 
    <propertyregex property="fileVersionNumber"
                  input="${versionNumber}"
                  regexp="\."
                  replace="_"
                  casesensitive="false" />
 
    <echo>Version Number: ${versionNumber}</echo>                    
    <echo>File Version Number: ${fileVersionNumber}</echo>
  </target>
 
</project>

As you can see it’s very straight forward to parse the version number out of the Application Descriptor. The only other thing we do is us RegEx to change the periods in the version number to underscores to make for more friendly file names.

Using the same concept it is very easy to also populate a Version file that gets compiled into your application which can include build numbers, or other interesting things… but we’ll save that for another day.

Finding celebrity locations via Twitter — Celebrity Exif data mining

:: UPDATE ::

Nate Beck gave a presentation on this topic at Ignite Seattle 13, here’s the video.


Last week one of my favorite video podcasts, Hak5, had a segment on Exif data mining (Episode 721). In the episode Rob Fuller (a.k.a. Mubix) shared his experience, how his images contained unwanted GPS information embedded in the Exif headers.

While mining Exif information from images is nothing new (Version 2.1 of the specification is dated June 12, 1998)… most consumers don’t realize the kind of information attached to their images by default.

In February of this year, Johannes Ullrich over at ISC published a paper titled Twitpic, EXIF and GPS: I Know Where You Did it Last Summer. Johannes explains:

“Modern cell phones frequently include a camera and a GPS. Even if a GPS is not included, cell phone towers can be used to establish the location of the phone. Image formats include special headers that can be used to store this information, so called EXIF tags.”

So after watching the segment, I was curious how widespread the issue is and decided to conduct my own investigation, codenamed Seeker.

Harvesting images

I decided to write a quick ruby script to harvest images from multiple image sites. The sites that I targeted were: Twitpic, Twitgoo, Tweetphoto, and yfrog. Some were easier to harvest than others, mostly due to easily accessible APIs.

After running the script on myself, a few of my friends and the team over at Hak5. I decided to widen my search to include public figures and celebrities.

Finding celebrities

At first I just scoured the internet looking for verified accounts for celebrities. I found a few sites that have lists of Twitter accounts for celebrities. My favorite one is WeFollow, which is owned by Digg.

So I wrote a script to collect Twitter account names from WeFollow. All I needed to provide the script was a category and a number of pages to scrape.

I edited the results and tossed in a few people that weren’t on WeFollow, and I came up with the following list of 147 celebrities.

Below is a glimpse of the log file while the script is running.

And in about 42 minutes… I had 11,688 photos from 147 Twitter handles. Not every celebrity on the list had images on those services. In fact I couldn’t detect images for 22 of the celebrities.

Processing 147 users
Starting twitpic
Finished twitpic -- 985.437336 seconds
Starting yfrog
Finished yfrog -- 980.306637 seconds
Starting tweetphoto
Finished tweetphoto -- 475.657377 seconds
Starting twitgoo
Finished twitgoo -- 85.571865 seconds
Total Time : 2526.98922 seconds

Extracting the Exif data

Now that I had 11,688 images, it was time to go through the images and see what kind of gems were hidden in the metadata.

So I wrote yet another ruby script which goes through each image and dumps all of the Exif data into a text file.

And the result…

44 users affected
125 users total
GPS count: 878
Total count: 11688
Percentage: 7.51%

Success! 878 images out of 11,688 have GPS data.

Visualizing the information

Now by day I’m an Adobe AIR developer, so naturally I decided to write a simple Flex 4 interface to help me visualize the information I collected, codenamed SeekAIR.

[note]
I was able to find personal addresses for some of the celebrities, but have opted not to share that information. The images I have chosen to share are public places where the location is obvious.
[/note]

Since it was Darren’s show that sent me on this investigation… let’s take a look at Darren’s images.

Darren Kitchen (hak5darren)

First I checked Darren’s TwitPic “Places I’ve Been” page to see if he has GPS enabled on purpose…

It doesn’t seem so.

Let’s open the photos up in SeekAIR and see what we can find…

These are just a couple of the images that I found from Darren which had GPS data encoded in them… I also learned that Darren took these photos on his Droid phone because that information was in the Exif data.

Not that Darren Kitchen isn’t a celebrity in my life… but let’s take a look at someone a little more interesting.

Adam Savage (donttrythis)

Another show that I love is MythBusters, so naturally Adam Savage was on my list of people. Again I checked Adam’s “Places I’ve Been” on TwitPic…

Same thing, apparently no images have locations. Again this is misleading, because many of Adam’s photos have GPS data in them… for example:

You may argue that Adam Savage isn’t a celebrity, and I’d have to fight you on that account. But in any case, let’s move on to another example.

Tom Hanks (tomhanks)

Once again to show I have nothing up my sleeve, let’s check Tom’s “Places I’ve Been” page.

Now let’s open the photos in SeekAIR…

This one shows Google street view of the location where the image was shot.

And this one shows Tom at Pixar.

Once again we have found GPS data hidden within these images. But… GPS data is not the only information that is included in Exif headers.

Britney Spears (britneyspears)

Britney didn’t have any GPS data in her photos, but nonetheless other information can be found in the Exif data.

This one made me laugh…

I mean, we all know that almost all celebrity photos have been Photoshopped, but this photo has the proof embedded right inside of it.

Collection Statistics

Now, you may be asking yourself exactly how many images are affected, so let’s take a look at the statistics.

Breakdown by Device

The following chart is a bit startling. I’m not going to draw any conclusions about it… perhaps the Apple iPhone is the most popular device among celebrities.

The article from ISC has a better chart showing the cross section by device for the general public.

Affected Files By Site

As you can see in the below chart, the majority of images came from TwitPic. It seems to be the most popular image service.

Affected Users By Site

You’ll notice the total users on this chart is 214; this is because some users had pictures on multiple image services. The blue bar represents the affected users out of the total users for the site.

Where to go from here

So what can we do to protect ourselves going forward? This issue affects everyone, not only celebrities. Consumers should be aware of what information is leaving their mobile devices.

Remove previous images

The thing about Twitter is that tweets expire. So the tweets that correspond with a particular image may no longer be available. According to the Twitter API Wiki:

“We also restrict the size of the search index by placing a date limit on the updates we allow you to search. This limit is currently around 1.5 weeks but is dynamic and subject to shrink as the number of tweets per day continues to grow.”

Turn off location services for the camera

Thankfully, the latest release of Apple iOS4 has the ability to turn off “Location Services” specifically for the camera application.

If you’re not an iPhone user, your device should have similar settings.

Scrub your images before you upload them

Since Seeker was just a weekend project, I haven’t gotten around to this yet… but ZaaLabs will be releasing a free Adobe AIR application to scrub Exif data from images before uploading them to these image services. Stay tuned.

Hey where can I get Seeker?

I will not be releasing the Seeker or SeekAIR code or applications to the public.

A note to the users mentioned in this post

ZaaLabs is willing to assist in identifying and removing affected images… Contact us.

Tip of the Day – Creating an AIR application for a kiosk

Today I needed to prototype a chromeless AIR application for use in a kiosk. It’s actually a whole lot easier than I thought it would be.

Here’s a quick example of what you can do with Adobe AIR and FlexMDI.

airkiosktest

The cool thing about Adobe AIR is that it can run on Linux. So if I needed to deploy thousands of kiosks I could install Linux on them and save on windows licensing.

Here is the application source:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:flexmdi="flexlib.mdi.containers.*" 
    layout="absolute" backgroundColor="0x000000" showFlexChrome="false"
    applicationComplete="onReady()">
 
    <mx:Script>
        <![CDATA[
            import mx.core.UIComponent;
            import mx.controls.Button;
            import mx.core.Application;
            import mx.events.MenuEvent;
            import flexlib.mdi.containers.MDIWindow;
            import flash.display.StageDisplayState;
            import net.natebeck.BrowserWindow;
 
            [Bindable] public var ADOBE:String = "Adobe";
 
            // FlexMDI Manager layout types
            [Bindable] public var TILE:String = "Tile";
            [Bindable] public var TILE_FILL:String = "Tile and Fill";
            [Bindable] public var CASCADE:String = "Cascade";
 
            // File Menu
            [Bindable] public var EXIT:String = "Exit";
 
            [Bindable] public var buttons:Array = []
 
            private function onReady():void
            {
                // Enter Fullscreen Interactive State
                stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
 
            }
 
            public function openWindow(event:MenuEvent):void
            {
                var location:String = "";
 
                switch(event.label) {
                    case ADOBE:
                        location = "http://www.adobe.com";
                        break;
                    case CASCADE:
                        mdic.windowManager.cascade();
                        break;
                    case TILE:
                        mdic.windowManager.tile(false, 10);
                        break;
                    case TILE_FILL:
                        mdic.windowManager.tile(true, 10);
                        break;
                    case EXIT:
                        var exitingEvent:Event = new Event(Event.EXITING, false, true); 
                        this.nativeApplication.dispatchEvent(exitingEvent); 
                        if (!exitingEvent.isDefaultPrevented()) { 
                            this.nativeApplication.exit(); 
                        } 
                        break;
                }
 
                if(location.length > 0) {
                    var window:BrowserWindow = new BrowserWindow();
                    mdic.windowManager.add(window);
                    window.title = event.label;
                    window.location = location;
                    window.maximize();
                }
 
            }
        ]]>
    </mx:Script>
 
 
    <mx:ApplicationControlBar dock="true">
        <mx:MenuBar id="myMenuBar" labelField="@label" width="100%" height="100%"
            itemClick="openWindow(event);" >
            <mx:XMLList>
                <menuitem label="File" >
                    <menuitem label="Exit"/>
                </menuitem>
                <menuitem label="Launch" >
                    <menuitem label="Adobe"/>
                </menuitem>
 
                <menuitem label="Window" >
                    <menuitem label="Tile"/>
                    <menuitem label="Tile and Fill"/>
                    <menuitem label="Cascade"/>
                </menuitem>
 
            </mx:XMLList>
        </mx:MenuBar>
    </mx:ApplicationControlBar>
 
    <flexmdi:MDICanvas id="mdic"  top="10" bottom="10" left="10" right="10"/>
 
</mx:WindowedApplication>

And here is Browser.mxml

<?xml version="1.0" encoding="utf-8"?>
<flexmdi:MDIWindow 
    xmlns:flexmdi="flexlib.mdi.containers.*"
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    width="400" height="300">
 
    <mx:Script>
        <![CDATA[
            [Bindable] public var location:String = "";
        ]]>
    </mx:Script>
 
    <mx:HTML width="100%" height="100%" location="{location}" />
 
</flexmdi:MDIWindow>

The full application can be downloaded here.

:: Update ::

There seems to a be a little bug in the applicaiton… when you’re rendering HTML with Flash Content within it. It won’t display in StageDisplayState.FULL_SCREEN_INTERACTIVE mode. However, if you hit the ESC key… the flash content will show up. I’ll have to check and see if there is a bug filed on this.