You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by sh...@apache.org on 2012/02/17 02:38:49 UTC

[7/27] Rename PhoneGap to Cordova.

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVSound.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVSound.h b/CordovaLib/Classes/CDVSound.h
new file mode 100755
index 0000000..465c92a
--- /dev/null
+++ b/CordovaLib/Classes/CDVSound.h
@@ -0,0 +1,115 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+
+#import <Foundation/Foundation.h>
+#import <AudioToolbox/AudioServices.h>
+#import <AVFoundation/AVFoundation.h>
+
+#import "CDVPlugin.h"
+
+
+
+
+enum CDVMediaError {
+	MEDIA_ERR_ABORTED = 1,
+	MEDIA_ERR_NETWORK = 2,
+	MEDIA_ERR_DECODE = 3,
+	MEDIA_ERR_NONE_SUPPORTED = 4
+};
+typedef NSUInteger CDVMediaError;
+
+enum CDVMediaStates {
+	MEDIA_NONE = 0,
+	MEDIA_STARTING = 1,
+	MEDIA_RUNNING = 2,
+	MEDIA_PAUSED = 3,
+	MEDIA_STOPPED = 4
+};
+typedef NSUInteger CDVMediaStates;
+
+enum CDVMediaMsg {
+	MEDIA_STATE = 1,
+	MEDIA_DURATION = 2,
+    MEDIA_POSITION = 3,
+	MEDIA_ERROR = 9
+};
+typedef NSUInteger CDVMediaMsg;
+
+@interface CDVAudioPlayer : AVAudioPlayer
+{
+	NSString* mediaId;
+}
+@property (nonatomic,copy) NSString* mediaId;
+@end
+
+#ifdef __IPHONE_3_0
+@interface CDVAudioRecorder : AVAudioRecorder
+{
+	NSString* mediaId;
+}
+@property (nonatomic,copy) NSString* mediaId;
+@end
+#endif
+	
+@interface CDVAudioFile : NSObject
+{
+	NSString* resourcePath;
+	NSURL* resourceURL;
+	CDVAudioPlayer* player;
+#ifdef __IPHONE_3_0
+	CDVAudioRecorder* recorder;
+#endif
+}
+
+@property (nonatomic, retain) NSString* resourcePath;
+@property (nonatomic, retain) NSURL* resourceURL;
+@property (nonatomic, retain) CDVAudioPlayer* player;
+
+#ifdef __IPHONE_3_0
+@property (nonatomic, retain) CDVAudioRecorder* recorder;
+#endif
+
+@end
+
+@interface CDVSound : CDVPlugin <AVAudioPlayerDelegate, AVAudioRecorderDelegate>
+{
+	NSMutableDictionary* soundCache;
+    AVAudioSession* avSession;
+}
+@property (nonatomic, retain) NSMutableDictionary* soundCache;
+@property (nonatomic, retain) AVAudioSession* avSession;
+
+- (void) play:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) pause:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) stop:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) release:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) getCurrentPosition:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) prepare:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (BOOL) hasAudioSession;
+
+// helper methods
+- (CDVAudioFile*) audioFileForResource:(NSString*) resourcePath withId: (NSString*)mediaId;
+- (BOOL) prepareToPlay: (CDVAudioFile*) audioFile withId: (NSString*)mediaId;
+- (NSString*) createMediaErrorWithCode: (CDVMediaError) code message: (NSString*) message;
+
+- (void) startAudioRecord:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) stopAudioRecord:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVSound.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVSound.m b/CordovaLib/Classes/CDVSound.m
new file mode 100644
index 0000000..e615e56
--- /dev/null
+++ b/CordovaLib/Classes/CDVSound.m
@@ -0,0 +1,593 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+
+#import "CDVSound.h"
+#import "CDVViewController.h"
+
+#define DOCUMENTS_SCHEME_PREFIX		@"documents://"
+#define HTTP_SCHEME_PREFIX			@"http://"
+
+@implementation CDVSound
+
+@synthesize soundCache, avSession;
+
+// Maps a url for a resource path
+// "Naked" resource paths are assumed to be from the www folder as its base
+- (NSURL*) urlForResource:(NSString*)resourcePath
+{
+	NSURL* resourceURL = nil;
+    NSString* filePath = nil;
+	
+    // first try to find HTTP:// or Documents:// resources
+    
+    if ([resourcePath hasPrefix:HTTP_SCHEME_PREFIX]){
+        // if it is a http url, use it
+        NSLog(@"Will use resource '%@' from the Internet.", resourcePath);
+        resourceURL = [NSURL URLWithString:resourcePath];
+    } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) {
+        filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/",[CDVViewController applicationDocumentsDirectory]]];
+       NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath);
+    } else {
+        // attempt to find file path in www directory
+        filePath = [self.commandDelegate pathForResource:resourcePath];
+        if (filePath != nil) {
+            NSLog(@"Found resource '%@' in the web folder.", filePath);
+        }else {
+            filePath = resourcePath;
+            NSLog(@"Will attempt to use file resource '%@'", filePath);
+            
+        }
+
+    }
+    // check that file exists for all but HTTP_SHEME_PREFIX
+    if(filePath != nil) {
+        // try to access file
+        NSFileManager* fMgr = [[NSFileManager alloc] init];
+        if (![fMgr fileExistsAtPath:filePath]) {
+            resourceURL = nil;
+            NSLog(@"Unknown resource '%@'", resourcePath);
+        } else {
+            // it's a valid file url, use it
+            resourceURL = [NSURL fileURLWithPath:filePath];
+        }
+        [fMgr release];
+    }     
+	return resourceURL;
+}
+
+// Creates or gets the cached audio file resource object
+- (CDVAudioFile*) audioFileForResource:(NSString*) resourcePath withId: (NSString*)mediaId
+{
+	BOOL bError = NO;
+	CDVMediaError errcode = MEDIA_ERR_NONE_SUPPORTED;
+    NSString* errMsg = @"";
+	NSString* jsString = nil;
+	CDVAudioFile* audioFile = nil;
+	NSURL* resourceURL = nil;
+	
+	if ([self soundCache] == nil) {
+		[self setSoundCache: [NSMutableDictionary dictionaryWithCapacity:1]];
+	}else {
+		audioFile = [[self soundCache] objectForKey: mediaId];
+	}
+	if (audioFile == nil){
+		// validate resourcePath and create
+	
+		if (resourcePath == nil || ![resourcePath isKindOfClass:[NSString class]] || [resourcePath isEqualToString:@""]){
+			bError = YES;
+			errcode = MEDIA_ERR_ABORTED;
+			errMsg = @"invalid media src argument";
+		} else {
+			resourceURL = [self urlForResource:resourcePath];
+		}
+
+		if (resourceURL == nil) {
+			bError = YES;
+			errcode = MEDIA_ERR_ABORTED;
+			errMsg = [NSString stringWithFormat: @"Cannot use audio file from resource '%@'", resourcePath];
+		}
+		if (bError) {
+			jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: errcode message: errMsg]];
+			[super writeJavascript:jsString];
+		} else {
+			audioFile = [[[CDVAudioFile alloc] init] autorelease];
+			audioFile.resourcePath = resourcePath;
+			audioFile.resourceURL = resourceURL;
+			[[self soundCache] setObject:audioFile forKey: mediaId];
+		}
+	}
+	return audioFile;
+}
+// returns whether or not audioSession is available - creates it if necessary 
+- (BOOL) hasAudioSession
+{
+    BOOL bSession = YES;
+    if (!self.avSession) {
+        NSError* error = nil;
+        
+        self.avSession = [AVAudioSession sharedInstance];
+        if (error) {
+            // is not fatal if can't get AVAudioSession , just log the error
+            NSLog(@"error creating audio session: %@", [[error userInfo] description]);
+            self.avSession = nil;
+            bSession = NO;
+        }
+    }
+    return bSession;
+}
+// helper function to create a error object string
+- (NSString*) createMediaErrorWithCode: (CDVMediaError) code message: (NSString*) message
+{
+    NSMutableDictionary* errorDict = [NSMutableDictionary dictionaryWithCapacity:2];
+    [errorDict setObject: [NSNumber numberWithUnsignedInt: code] forKey:@"code"];
+    [errorDict setObject: message ? message : @"" forKey: @"message"];
+    return [errorDict JSONString];
+    
+}
+
+- (void) play:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+	
+	NSString* mediaId = [arguments objectAtIndex:1];
+	BOOL bError = NO;
+	NSString* jsString = nil;
+	
+	CDVAudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:2] withId: mediaId];
+	
+	if (audioFile != nil) {
+		if (audioFile.player == nil){
+            bError = [self prepareToPlay:audioFile withId:mediaId];
+		}	
+		if (!bError){
+			// audioFile.player != nil  or player was sucessfully created
+            // get the audioSession and set the category to allow Playing when device is locked or ring/silent switch engaged
+            if ([self hasAudioSession]) {
+                NSError* err = nil;
+                [self.avSession setCategory:AVAudioSessionCategoryPlayback error:nil];
+                if (![self.avSession  setActive: YES error: &err]){
+                    // other audio with higher priority that does not allow mixing could cause this to fail
+                    NSLog(@"Unable to play audio: %@", [err localizedFailureReason]);
+                    bError = YES;
+                }
+            }
+            if (!bError) {
+                NSLog(@"Playing audio sample '%@'", audioFile.resourcePath);
+                NSNumber* loopOption = [options objectForKey:@"numberOfLoops"];
+                NSInteger numberOfLoops = 0;
+                if (loopOption != nil) { 
+                    numberOfLoops = [loopOption intValue] - 1;
+                }
+                audioFile.player.numberOfLoops = numberOfLoops;
+                
+                if(audioFile.player.isPlaying){
+                    [audioFile.player stop];
+                    audioFile.player.currentTime = 0;
+                }
+                [audioFile.player play];
+                double position = round(audioFile.player.duration * 1000)/1000;
+                jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"Cordova.Media.onStatus", mediaId, MEDIA_DURATION, position, @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING];
+                [super writeJavascript:jsString];
+                
+            }
+        }
+        if (bError) {
+			/*  I don't see a problem playing previously recorded audio so removing this section - BG
+			NSError* error;
+			// try loading it one more time, in case the file was recorded previously
+			audioFile.player = [[ AVAudioPlayer alloc ] initWithContentsOfURL:audioFile.resourceURL error:&error];
+			if (error != nil) {
+				NSLog(@"Failed to initialize AVAudioPlayer: %@\n", error);
+				audioFile.player = nil;
+			} else {
+				NSLog(@"Playing audio sample '%@'", audioFile.resourcePath);
+				audioFile.player.numberOfLoops = numberOfLoops;
+				[audioFile.player play];
+			} */
+			// error creating the session or player
+			jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: MEDIA_ERR_NONE_SUPPORTED message: nil]];
+			[super writeJavascript:jsString];
+		}
+	}
+	// else audioFile was nil - error already returned from audioFile for resource
+	return;
+}
+- (BOOL) prepareToPlay: (CDVAudioFile*) audioFile withId: (NSString*) mediaId
+{
+    BOOL bError = NO;
+    NSError* playerError = nil;
+    
+    // create the player
+    NSURL* resourceURL = audioFile.resourceURL;
+    if ([resourceURL isFileURL]) {
+        audioFile.player = [[[ CDVAudioPlayer alloc ] initWithContentsOfURL:resourceURL error:&playerError] autorelease];
+    } else {
+        NSURLRequest *request = [NSURLRequest requestWithURL:resourceURL];
+        NSURLResponse *response = nil;
+        NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&playerError];
+        if (playerError) {
+            NSLog(@"Unable to download audio from: %@", [resourceURL absoluteString]);
+        } else {
+            audioFile.player = [[[ CDVAudioPlayer alloc ] initWithData:data error:&playerError] autorelease];
+        }
+    }
+    
+    if (playerError != nil) {
+        NSLog(@"Failed to initialize AVAudioPlayer: %@\n", [playerError localizedFailureReason]);
+        audioFile.player = nil;
+        if (self.avSession) {
+            [self.avSession setActive:NO error:nil];
+        }
+        bError = YES;
+    } else {
+        audioFile.player.mediaId = mediaId;
+        audioFile.player.delegate = self;
+        bError = ![audioFile.player prepareToPlay];
+    }
+    return bError;
+}
+
+// if no errors sets status to starting and calls successCallback with no parameters
+// Calls the success call back immediately as there is no mechanism to determine that the file is loaded
+// other than the return from prepareToPlay.  Thus, IMHO not really worth calling
+- (void) prepare:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	NSString* callbackId = [arguments objectAtIndex:0]; 
+    
+    NSString* mediaId = [arguments objectAtIndex:1];
+    BOOL bError = NO;
+    CDVMediaStates state = MEDIA_STARTING;
+    NSString* jsString = nil;
+    
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+    if (audioFile == nil) {
+        // did not already exist, try to create
+        audioFile = [self audioFileForResource:[arguments objectAtIndex:2] withId: mediaId];
+        if (audioFile == nil) {
+            // create failed
+            bError = YES;
+        } else {
+            bError = [self prepareToPlay:audioFile withId:mediaId];
+        }
+    } else {
+        // audioFile already existed in the cache no need to prepare it again, indicate state
+        if (audioFile.player && [audioFile.player isPlaying]) {
+            state = MEDIA_RUNNING;
+        }
+    }
+    
+    if (!bError) {
+        
+        // NSLog(@"Prepared audio sample '%@' for playback.", audioFile.resourcePath);
+        CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);\n%@", @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, state, [result toSuccessCallbackString:callbackId]];
+        
+	} else {
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: MEDIA_ERR_NONE_SUPPORTED message: nil]];   
+    }
+    if (jsString) {
+        [super writeJavascript:jsString];
+    }
+	
+}
+
+
+
+- (void) stop:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+	NSString* mediaId = [arguments objectAtIndex:1];
+    CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+	NSString* jsString = nil;
+
+	if (audioFile != nil && audioFile.player!= nil) {
+        NSLog(@"Stopped playing audio sample '%@'", audioFile.resourcePath);
+        [audioFile.player stop];
+        audioFile.player.currentTime = 0;
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+	}  // ignore if no media playing 
+    if (jsString){
+        [super writeJavascript: jsString];
+    }
+}
+
+- (void) pause:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+	NSString* mediaId = [arguments objectAtIndex:1];
+    NSString* jsString = nil;
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+	
+	if (audioFile != nil && audioFile.player != nil) {
+            NSLog(@"Paused playing audio sample '%@'", audioFile.resourcePath);
+			[audioFile.player pause];
+            jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, MEDIA_PAUSED];
+	} 
+    // ignore if no media playing
+      
+    
+    if (jsString){
+        [super writeJavascript: jsString];
+    }
+
+
+}
+- (void) seekTo:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	//args:
+	// 0 = callbackId
+    // 1 = Media id
+    // 2 = path to resource
+    // 3 = seek to location in milliseconds
+	
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+	NSString* mediaId = [arguments objectAtIndex:1];
+
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+    double position = [[arguments objectAtIndex:3 ] doubleValue];
+	
+    if (audioFile != nil && audioFile.player != nil && position){
+        double posInSeconds = position/1000;
+        audioFile.player.currentTime = posInSeconds;
+        NSString* jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%f);", @"Cordova.Media.onStatus", mediaId, MEDIA_POSITION, posInSeconds];
+
+        [super writeJavascript: jsString];
+        
+    }
+    
+	return;
+    
+}
+
+- (void) release:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+    NSString* mediaId = [arguments objectAtIndex:1];
+
+	if (mediaId != nil){
+		CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+		if (audioFile != nil){
+			if (audioFile.player && [audioFile.player isPlaying]){
+				[audioFile.player stop];
+			}
+			if(audioFile.recorder && [audioFile.recorder isRecording]){
+				[audioFile.recorder stop];
+			}
+            if (self.avSession) {
+                [self.avSession setActive:NO error: nil];
+                self.avSession = nil;
+            }
+			[[self soundCache] removeObjectForKey: mediaId];
+			NSLog(@"Media with id %@ released", mediaId);
+		}
+	}
+}
+
+- (void) getCurrentPosition:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	//args:
+	// 0 = callbackId
+    // 1 = Media id
+	
+	NSString* callbackId = [arguments objectAtIndex:0];
+	NSString* mediaId = [arguments objectAtIndex:1];
+#pragma unused(mediaId)
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+    double position = -1;
+	
+    if (audioFile != nil && audioFile.player != nil && [audioFile.player isPlaying]){ 
+            position = round(audioFile.player.currentTime *1000)/1000;
+    }
+    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble: position];
+	NSString* jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%.3f);\n%@", @"Cordova.Media.onStatus", mediaId, MEDIA_POSITION, position, [result toSuccessCallbackString:callbackId]];
+    [super writeJavascript:jsString];
+    
+	return;
+		
+}
+
+- (void) startAudioRecord:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+	
+	NSString* mediaId = [arguments objectAtIndex:1];
+	CDVAudioFile* audioFile = [self audioFileForResource:[arguments objectAtIndex:2] withId: mediaId];
+    NSString* jsString = nil;
+    NSString* errorMsg = @"";
+    
+	if (audioFile != nil) {
+		
+		NSError* error = nil;
+
+		if (audioFile.recorder != nil) {
+			[audioFile.recorder stop];
+			audioFile.recorder = nil;
+		}
+        // get the audioSession and set the category to allow recording when device is locked or ring/silent switch engaged
+        if ([self hasAudioSession]) {
+            [self.avSession setCategory:AVAudioSessionCategoryRecord error:nil];
+            if (![self.avSession  setActive: YES error: &error]){
+                // other audio with higher priority that does not allow mixing could cause this to fail
+                errorMsg = [NSString stringWithFormat: @"Unable to record audio: %@", [error localizedFailureReason]];
+                jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: MEDIA_ERR_ABORTED message: errorMsg] ];
+                [super writeJavascript:jsString];
+                return;
+            }
+        }
+        
+        // create a new recorder for each start record 
+        audioFile.recorder = [[[CDVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error] autorelease];
+        
+		if (error != nil) {
+			errorMsg = [NSString stringWithFormat: @"Failed to initialize AVAudioRecorder: %@\n", [error  localizedFailureReason]];
+			audioFile.recorder = nil;
+            if (self.avSession) {
+                [self.avSession setActive:NO error:nil];
+            }
+			jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: MEDIA_ERR_ABORTED message: errorMsg]];
+			
+		} else {
+			audioFile.recorder.delegate = self;
+			audioFile.recorder.mediaId = mediaId;
+			[audioFile.recorder record];
+			NSLog(@"Started recording audio sample '%@'", audioFile.resourcePath);
+            jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING];
+		}
+	}
+    if (jsString) {
+       [super writeJavascript:jsString]; 
+    }
+	return;
+}
+
+- (void) stopAudioRecord:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	NSString* callbackId = [arguments objectAtIndex:0];
+#pragma unused(callbackId)
+	NSString* mediaId = [arguments objectAtIndex:1];
+
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+    NSString* jsString = nil;
+	
+	if (audioFile != nil && audioFile.recorder != nil) {
+		NSLog(@"Stopped recording audio sample '%@'", audioFile.resourcePath);
+		[audioFile.recorder stop];
+        // no callback - that will happen in audioRecorderDidFinishRecording
+	} 
+    // ignore if no media recording
+    if (jsString) {
+        [super writeJavascript:jsString]; 
+    }}
+
+- (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag
+{
+
+	CDVAudioRecorder* aRecorder = (CDVAudioRecorder*)recorder;
+	NSString* mediaId = aRecorder.mediaId;
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+	NSString* jsString = nil;
+
+	
+	if (audioFile != nil) {
+		NSLog(@"Finished recording audio sample '%@'", audioFile.resourcePath);
+    }
+    if (flag){
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+    } else {
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: MEDIA_ERR_DECODE message:nil]];
+    }
+    if (self.avSession) {
+        [self.avSession setActive:NO error:nil];
+    }
+    [super writeJavascript:jsString];
+	
+}
+
+- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag 
+{
+	CDVAudioPlayer* aPlayer = (CDVAudioPlayer*)player;
+	NSString* mediaId = aPlayer.mediaId;
+	CDVAudioFile* audioFile = [[self soundCache] objectForKey: mediaId];
+    NSString* jsString = nil;
+		
+	if (audioFile != nil) {
+		NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath);
+    }
+    if (flag){
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"Cordova.Media.onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+        
+    } else {
+        jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%@);", @"Cordova.Media.onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode: MEDIA_ERR_DECODE message:nil]];
+        
+    }
+    if (self.avSession) {
+        [self.avSession setActive:NO error:nil];
+    }
+    [super writeJavascript: jsString];
+	
+}
+
+- (void) onMemoryWarning
+{
+	[[self soundCache] removeAllObjects];
+	[self setSoundCache: nil];
+    [self setAvSession: nil];
+	
+	[super onMemoryWarning];
+}
+- (void) dealloc
+{
+    [[self soundCache] removeAllObjects];
+	[self setSoundCache: nil];
+    [self setAvSession: nil];
+    
+    [super dealloc];
+}
+@end
+
+@implementation CDVAudioFile
+
+@synthesize resourcePath;
+@synthesize resourceURL;
+@synthesize player;
+#ifdef __IPHONE_3_0
+@synthesize recorder;
+#endif
+
+- (void) dealloc
+{
+	self.resourcePath = nil;
+    self.resourceURL = nil;
+    self.player = nil;
+    self.recorder = nil;
+    
+	[super dealloc];
+}
+
+@end
+@implementation CDVAudioPlayer
+@synthesize mediaId;
+- (void) dealloc
+{
+    self.mediaId = nil;
+	
+	[super dealloc];
+}
+
+@end
+
+@implementation CDVAudioRecorder
+@synthesize mediaId;
+- (void) dealloc
+{
+    self.mediaId = nil;
+	
+	[super dealloc];
+}
+
+@end
+

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVSplashScreen.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVSplashScreen.h b/CordovaLib/Classes/CDVSplashScreen.h
new file mode 100644
index 0000000..e2a6d4c
--- /dev/null
+++ b/CordovaLib/Classes/CDVSplashScreen.h
@@ -0,0 +1,30 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "CDVPlugin.h"
+
+
+@interface CDVSplashScreen : CDVPlugin {
+}
+
+- (void) show:(NSArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) hide:(NSArray*)arguments withDict:(NSMutableDictionary*)options;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVSplashScreen.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVSplashScreen.m b/CordovaLib/Classes/CDVSplashScreen.m
new file mode 100644
index 0000000..cce13ab
--- /dev/null
+++ b/CordovaLib/Classes/CDVSplashScreen.m
@@ -0,0 +1,51 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+
+#import "CDVSplashScreen.h"
+#import "CDVViewController.h"
+
+@implementation CDVSplashScreen
+
+
+- (void) __show:(BOOL)show
+{
+    // Legacy support - once deprecated classes removed, clean this up
+    id<UIApplicationDelegate> delegate = [[UIApplication sharedApplication] delegate];
+    
+    if ([delegate respondsToSelector:@selector(viewController)]) {
+        id vc = [delegate performSelector:@selector(viewController)];
+        if ([vc isKindOfClass:[CDVViewController class]]) {
+            ((CDVViewController*)vc).imageView.hidden = !show;
+            ((CDVViewController*)vc).activityView.hidden = !show;
+        }
+    }
+}
+
+- (void) show:(NSArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	[self __show:YES];
+}
+
+- (void) hide:(NSArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	[self __show:NO];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVURLProtocol.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVURLProtocol.h b/CordovaLib/Classes/CDVURLProtocol.h
new file mode 100644
index 0000000..527cd43
--- /dev/null
+++ b/CordovaLib/Classes/CDVURLProtocol.h
@@ -0,0 +1,34 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface CDVURLProtocol : NSURLProtocol {
+}
+
++ (void) registerPGHttpURLProtocol;
+
+@end
+
+@interface CDVHTTPURLResponse : NSHTTPURLResponse {
+} 
+
+- (CDVHTTPURLResponse*) initWithUnauthorizedURL:(NSURL*)url;
+
+@end
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVURLProtocol.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVURLProtocol.m b/CordovaLib/Classes/CDVURLProtocol.m
new file mode 100644
index 0000000..a15b54f
--- /dev/null
+++ b/CordovaLib/Classes/CDVURLProtocol.m
@@ -0,0 +1,124 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "CDVURLProtocol.h"
+#import "CDVWhitelist.h"
+#import "CDVViewController.h"
+
+static CDVWhitelist* gWhitelist = nil;
+
+@implementation CDVURLProtocol
+
+// Called before any use of the protocol, ensure it is only called once
++ (void) registerPGHttpURLProtocol {
+    static BOOL registered = NO;
+    if (!registered) {
+        [NSURLProtocol registerClass:[CDVURLProtocol class]];
+        registered = YES;
+    }
+}
+
++ (BOOL) canInitWithRequest:(NSURLRequest *)theRequest
+{
+    NSURL* theUrl = [theRequest URL];
+    NSString* theScheme = [theUrl scheme];
+    
+    if (gWhitelist == nil) {
+        id<UIApplicationDelegate> delegate = [[UIApplication sharedApplication] delegate];
+        
+        if ([delegate respondsToSelector:@selector(viewController)]) {
+            id vc = [delegate performSelector:@selector(viewController)];
+            if ([vc isKindOfClass:[CDVViewController class]]) {
+                gWhitelist = [((CDVViewController*)vc).whitelist retain];
+            }
+        }
+    }
+    
+    // we only care about http and https connections
+	if ([gWhitelist schemeIsAllowed:theScheme])
+    {
+        // if it FAILS the whitelist, we return TRUE, so we can fail the connection later
+        return ![gWhitelist URLIsAllowed:theUrl];
+    }
+    
+    return NO;
+}
+
++ (NSURLRequest*) canonicalRequestForRequest:(NSURLRequest*) request 
+{
+    //NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
+    return request;
+}
+
+- (void) startLoading
+{    
+    //NSLog(@"%@ received %@ - start", self, NSStringFromSelector(_cmd));
+    NSURL* url = [[self request] URL];
+    NSString* body = [gWhitelist errorStringForURL:url];
+
+    CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithUnauthorizedURL:url];
+    
+    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+    
+    [[self client] URLProtocol:self didLoadData:[body dataUsingEncoding:NSASCIIStringEncoding]];
+
+    [[self client] URLProtocolDidFinishLoading:self];                
+    
+    [response release];    
+}
+
+- (void) stopLoading
+{
+    // do any cleanup here
+}
+
++ (BOOL) requestIsCacheEquivalent: (NSURLRequest*)requestA toRequest: (NSURLRequest*)requestB 
+{
+    return NO;
+}
+
+@end
+
+
+
+@implementation CDVHTTPURLResponse
+
+- (id) initWithUnauthorizedURL:(NSURL*)url
+{
+    NSInteger statusCode = 401;
+    NSDictionary* headerFields = [NSDictionary dictionaryWithObject:@"Digest realm = \"Cordova.plist/ExternalHosts\"" forKey:@"WWW-Authenticate"];
+    double requestTime = 1;
+    
+    SEL selector = NSSelectorFromString(@"initWithURL:statusCode:headerFields:requestTime:");
+    NSMethodSignature* signature = [self methodSignatureForSelector:selector];
+    
+    NSInvocation* inv = [NSInvocation invocationWithMethodSignature:signature];
+    [inv setTarget:self];
+    [inv setSelector:selector];
+    [inv setArgument:&url atIndex:2];
+    [inv setArgument:&statusCode atIndex:3];
+    [inv setArgument:&headerFields atIndex:4];
+    [inv setArgument:&requestTime atIndex:5];
+    
+    [inv invoke];
+    
+    return self;
+}
+
+@end
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVViewController.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVViewController.h b/CordovaLib/Classes/CDVViewController.h
new file mode 100644
index 0000000..41f4d1b
--- /dev/null
+++ b/CordovaLib/Classes/CDVViewController.h
@@ -0,0 +1,65 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "CDVCordovaView.h"
+
+#import "JSONKit.h"
+#import "CDVInvokedUrlCommand.h"
+#import "CDVCommandDelegate.h"
+#import "CDVWhitelist.h"
+
+
+@interface CDVViewController : UIViewController<UIWebViewDelegate, CDVCommandDelegate> {
+	
+}
+
+@property (nonatomic, retain) IBOutlet CDVCordovaView* webView;
+
+@property (nonatomic, readonly, retain) NSMutableDictionary* pluginObjects;
+@property (nonatomic, readonly, retain) NSDictionary* pluginsMap;
+@property (nonatomic, readonly, retain) NSDictionary* settings;
+@property (nonatomic, readonly, retain) CDVWhitelist* whitelist; // readonly for public
+@property (nonatomic, readonly, retain) NSArray* supportedOrientations;
+@property (nonatomic, readonly, copy)   NSString* sessionKey;
+@property (nonatomic, readonly, assign) BOOL loadFromString;
+
+@property (nonatomic, readwrite, assign) BOOL useSplashScreen;
+@property (nonatomic, readonly, retain) IBOutlet UIActivityIndicatorView* activityView;
+@property (nonatomic, readonly, retain) UIImageView *imageView;
+@property (nonatomic, readwrite, retain) id<CDVCommandDelegate> commandDelegate;
+
+@property (nonatomic, readwrite, copy) NSString* wwwFolderName;
+@property (nonatomic, readwrite, copy) NSString* startPage;
+
++ (NSDictionary*) getBundlePlist:(NSString*)plistName;
++ (NSString*) cordovaVersion;
++ (NSString*) applicationDocumentsDirectory;
+
+- (void) createGapView;
+
+- (int) executeQueuedCommands;
+- (void) flushCommandQueue;
+
+- (void) javascriptAlert:(NSString*)text;
+- (NSString*) appURLScheme;
+- (NSDictionary*) deviceProperties;
+
+- (NSArray*) parseInterfaceOrientations:(NSArray*)orientations;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVViewController.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVViewController.m b/CordovaLib/Classes/CDVViewController.m
new file mode 100644
index 0000000..420b732
--- /dev/null
+++ b/CordovaLib/Classes/CDVViewController.m
@@ -0,0 +1,957 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "CDVViewController.h"
+#import "CDVPlugin.h"
+#import "CDVLocation.h"
+#import "CDVConnection.h"
+#import "NSDictionary+Extensions.h"
+
+#define SYMBOL_TO_NSSTRING_HELPER(x) @#x
+#define SYMBOL_TO_NSSTRING(x) SYMBOL_TO_NSSTRING_HELPER(x)
+#define degreesToRadian(x) (M_PI * (x) / 180.0)
+
+@interface CDVViewController ()
+
+@property (nonatomic, readwrite, retain) NSDictionary* settings;
+@property (nonatomic, readwrite, retain) CDVWhitelist* whitelist; 
+@property (nonatomic, readwrite, retain) NSMutableDictionary* pluginObjects;
+@property (nonatomic, readwrite, retain) NSDictionary* pluginsMap;
+@property (nonatomic, readwrite, retain) NSArray* supportedOrientations;
+@property (nonatomic, readwrite, copy)   NSString* sessionKey;
+@property (nonatomic, readwrite, assign) BOOL loadFromString;
+
+@property (nonatomic, readwrite, retain) IBOutlet UIActivityIndicatorView* activityView;
+@property (nonatomic, readwrite, retain) UIImageView* imageView;
+
+@end
+
+
+@implementation CDVViewController
+
+@synthesize webView, supportedOrientations;
+@synthesize pluginObjects, pluginsMap, whitelist;
+@synthesize settings, sessionKey, loadFromString;
+@synthesize imageView, activityView, useSplashScreen, commandDelegate;
+@synthesize wwwFolderName, startPage;
+
+- (id) init
+{
+    self = [super init];
+    if (self != nil) {
+        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedOrientationChange) 
+                                                     name:UIDeviceOrientationDidChangeNotification object:nil];
+        
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillTerminate:) 
+                                                     name:UIApplicationWillTerminateNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:) 
+                                                     name:UIApplicationWillResignActiveNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:) 
+                                                     name:UIApplicationWillEnterForegroundNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:) 
+                                                     name:UIApplicationDidBecomeActiveNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:) 
+                                                     name:UIApplicationDidEnterBackgroundNotification object:nil];
+
+        self.wwwFolderName = @"www";
+        self.startPage = @"index.html";
+        [self setWantsFullScreenLayout:YES];
+    }
+    return self; 
+}
+
+// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
+- (void) viewDidLoad 
+{
+    if (self.sessionKey == nil) {
+        self.sessionKey = [NSString stringWithFormat:@"%d", arc4random()];
+    }
+    
+    [super viewDidLoad];
+	
+    self.pluginObjects = [[[NSMutableDictionary alloc] initWithCapacity:4] autorelease];
+    
+	// read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist
+    self.supportedOrientations = [self parseInterfaceOrientations:
+									  [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]];
+    
+    // read from Cordova.plist in the app bundle
+    NSString* appPlistName = @"Cordova";
+    NSDictionary* cordovaPlist = [[self class] getBundlePlist:appPlistName];
+    if (cordovaPlist == nil) {
+        NSLog(@"WARNING: %@.plist is missing.", appPlistName);
+		return;
+    }
+    self.settings = [[[NSDictionary alloc] initWithDictionary:cordovaPlist] autorelease];
+	
+    // read from Plugins dict in Cordova.plist in the app bundle
+    NSString* pluginsKey = @"Plugins";
+    NSDictionary* pluginsDict = [self.settings objectForKey:@"Plugins"];
+    if (pluginsDict == nil) {
+        NSLog(@"WARNING: %@ key in %@.plist is missing! Cordova will not work, you need to have this key.", pluginsKey, appPlistName);
+        return;
+    }
+    
+    // set the whitelist
+    self.whitelist = [[[CDVWhitelist alloc] initWithArray:[self.settings objectForKey:@"ExternalHosts"]] autorelease];
+	
+    self.pluginsMap = [pluginsDict dictionaryWithLowercaseKeys];
+    
+    ///////////////////
+    
+	NSString* startFilePath = [self pathForResource:self.startPage];
+	NSURL* appURL  = nil;
+    NSString* loadErr = nil;
+    
+    if (startFilePath == nil) {
+        loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage];
+        NSLog(@"%@", loadErr);
+        self.loadFromString = YES;
+        appURL = nil;
+    } else {
+        appURL = [NSURL fileURLWithPath:startFilePath];
+    }
+
+    [ self createGapView];
+    
+    ///////////////////
+    
+    NSNumber* enableLocation       = [self.settings objectForKey:@"EnableLocation"];
+    NSString* enableViewportScale  = [self.settings objectForKey:@"EnableViewportScale"];
+    NSNumber* allowInlineMediaPlayback = [self.settings objectForKey:@"AllowInlineMediaPlayback"];
+    BOOL mediaPlaybackRequiresUserAction = YES;  // default value
+    if ([self.settings objectForKey:@"MediaPlaybackRequiresUserAction"]) {
+        mediaPlaybackRequiresUserAction = [(NSNumber*)[settings objectForKey:@"MediaPlaybackRequiresUserAction"] boolValue];
+    }
+    
+    self.webView.scalesPageToFit = [enableViewportScale boolValue];
+    
+    /*
+     * Fire up the GPS Service right away as it takes a moment for data to come back.
+     */
+    
+    if ([enableLocation boolValue]) {
+        [[self.commandDelegate getCommandInstance:@"org.apache.cordova.geolocation"] startLocation:nil withDict:nil];
+    }
+    
+    /*
+     * This is for iOS 4.x, where you can allow inline <video> and <audio>, and also autoplay them
+     */
+    if ([allowInlineMediaPlayback boolValue] && [self.webView respondsToSelector:@selector(allowsInlineMediaPlayback)]) {
+        self.webView.allowsInlineMediaPlayback = YES;
+    }
+    if (mediaPlaybackRequiresUserAction == NO && [self.webView respondsToSelector:@selector(mediaPlaybackRequiresUserAction)]) {
+        self.webView.mediaPlaybackRequiresUserAction = NO;
+    }
+    
+    // UIWebViewBounce property - defaults to true
+    NSNumber* bouncePreference = [self.settings objectForKey:@"UIWebViewBounce"];
+    BOOL bounceAllowed = (bouncePreference==nil || [bouncePreference boolValue]); 
+    
+    // prevent webView from bouncing
+    // based on UIWebViewBounce key in Cordova.plist
+    if (!bounceAllowed) {
+        if ([ self.webView respondsToSelector:@selector(scrollView) ]) {
+            ((UIScrollView *) [self.webView scrollView]).bounces = NO;
+        } else {
+            for (id subview in self.webView.subviews)
+                if ([[subview class] isSubclassOfClass: [UIScrollView class]])
+                    ((UIScrollView *)subview).bounces = NO;
+        }
+    }
+    
+    ///////////////////
+    
+    if (!loadErr) {
+        NSURLRequest *appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
+        [self.webView loadRequest:appReq];
+    } else {
+        NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
+        [self.webView loadHTMLString:html baseURL:nil];
+    }
+    
+	self.commandDelegate = self;
+}
+
+- (NSArray*) parseInterfaceOrientations:(NSArray*)orientations
+{
+    NSMutableArray* result = [[[NSMutableArray alloc] init] autorelease];
+	
+    if (orientations != nil) 
+    {
+        NSEnumerator* enumerator = [orientations objectEnumerator];
+        NSString* orientationString;
+        
+        while (orientationString = [enumerator nextObject]) 
+        {
+            if ([orientationString isEqualToString:@"UIInterfaceOrientationPortrait"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]];
+            } else if ([orientationString isEqualToString:@"UIInterfaceOrientationPortraitUpsideDown"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortraitUpsideDown]];
+            } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeLeft"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft]];
+            } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeRight"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight]];
+            }
+        }
+    }
+    
+    // default
+    if ([result count] == 0) {
+        [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]];
+    }
+    
+    return result;
+}
+
+- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 
+{
+	// First ask the webview via JS if it wants to support the new orientation -jm
+	int i = 0;
+	
+	switch (interfaceOrientation){
+            
+		case UIInterfaceOrientationPortraitUpsideDown:
+			i = 180;
+			break;
+		case UIInterfaceOrientationLandscapeLeft:
+			i = -90;
+			break;
+		case UIInterfaceOrientationLandscapeRight:
+			i = 90;
+			break;
+		default:
+		case UIInterfaceOrientationPortrait:
+			// noop
+			break;
+	}
+	
+	NSString* jsCall = [ NSString stringWithFormat:@"shouldRotateToOrientation(%d);",i];
+	NSString* res = [webView stringByEvaluatingJavaScriptFromString:jsCall];
+	
+	if([res length] > 0)
+	{
+		return [res boolValue];
+	}
+	
+	// if js did not handle the new orientation ( no return value ) we will look it up in the plist -jm
+	
+	BOOL autoRotate = [self.supportedOrientations count] > 0; // autorotate if only more than 1 orientation supported
+	if (autoRotate)
+	{
+		if ([self.supportedOrientations containsObject:
+			 [NSNumber numberWithInt:interfaceOrientation]]) {
+			return YES;
+		}
+    }
+	
+	// default return value is NO! -jm
+	
+	return NO;
+}
+
+
+/**
+ Called by UIKit when the device starts to rotate to a new orientation.  This fires the \c setOrientation
+ method on the Orientation object in JavaScript.  Look at the JavaScript documentation for more information.
+ */
+- (void)didRotateFromInterfaceOrientation: (UIInterfaceOrientation)fromInterfaceOrientation
+{
+	int i = 0;
+	
+	switch (self.interfaceOrientation){
+		case UIInterfaceOrientationPortrait:
+			i = 0;
+			break;
+		case UIInterfaceOrientationPortraitUpsideDown:
+			i = 180;
+			break;
+		case UIInterfaceOrientationLandscapeLeft:
+			i = -90;
+			break;
+		case UIInterfaceOrientationLandscapeRight:
+			i = 90;
+			break;
+	}
+	
+	NSString* jsCallback = [NSString stringWithFormat:@"window.__defineGetter__('orientation',function(){ return %d; }); Cordova.fireEvent('orientationchange', window);",i];
+	[self.webView stringByEvaluatingJavaScriptFromString:jsCallback];    
+}
+
+- (void) createGapView
+{
+    CGRect webViewBounds = self.view.bounds;
+    webViewBounds.origin = self.view.bounds.origin;
+	
+    if (!self.webView) 
+	{
+        self.webView = [[ [ CDVCordovaView alloc ] initWithFrame:webViewBounds] autorelease];
+		self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
+		
+		[self.view addSubview:self.webView];
+		[self.view sendSubviewToBack:self.webView];
+		
+		self.webView.delegate = self;
+    }
+}
+
+- (void) didReceiveMemoryWarning {
+	// Releases the view if it doesn't have a superview.
+    [super didReceiveMemoryWarning];
+	
+	// Release any cached data, images, etc. that aren't in use.
+}
+
+
+- (void) viewDidUnload {
+	// Release any retained subviews of the main view.
+	// e.g. self.myOutlet = nil;
+}
+
+
+#pragma mark UIWebViewDelegate
+
+/**
+ When web application loads Add stuff to the DOM, mainly the user-defined settings from the Settings.plist file, and
+ the device's data such as device ID, platform version, etc.
+ */
+- (void) webViewDidStartLoad:(UIWebView*)theWebView 
+{
+    
+}
+
+/**
+ Called when the webview finishes loading.  This stops the activity view and closes the imageview
+ */
+- (void) webViewDidFinishLoad:(UIWebView*)theWebView 
+{
+    // Share session key with the WebView by setting Cordova.sessionKey
+    NSString *sessionKeyScript = [NSString stringWithFormat:@"Cordova.sessionKey = \"%@\";", self.sessionKey];
+    [theWebView stringByEvaluatingJavaScriptFromString:sessionKeyScript];
+	
+    
+    NSDictionary *deviceProperties = [ self deviceProperties];
+    NSMutableString *result = [[NSMutableString alloc] initWithFormat:@"DeviceInfo = %@;", [deviceProperties JSONString]];
+    
+    /* Settings.plist
+     * Read the optional Settings.plist file and push these user-defined settings down into the web application.
+     * This can be useful for supplying build-time configuration variables down to the app to change its behaviour,
+     * such as specifying Full / Lite version, or localization (English vs German, for instance).
+     */
+    
+    NSDictionary *temp = [[self class] getBundlePlist:@"Settings"];
+    if ([temp respondsToSelector:@selector(JSONString)]) {
+        [result appendFormat:@"\nwindow.Settings = %@;", [temp JSONString]];
+    }
+    
+    NSLog(@"Device initialization: %@", result);
+    [theWebView stringByEvaluatingJavaScriptFromString:result];
+    [result release];
+    
+    /*
+     * Hide the Top Activity THROBBER in the Battery Bar
+     */
+    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+	
+    id autoHideSplashScreenValue = [self.settings objectForKey:@"AutoHideSplashScreen"];
+    // if value is missing, default to yes
+    if (autoHideSplashScreenValue == nil || [autoHideSplashScreenValue boolValue]) {
+        self.imageView.hidden = YES;
+        self.activityView.hidden = YES;    
+        [self.view.superview bringSubviewToFront:self.webView];
+    }
+    
+    [self didRotateFromInterfaceOrientation:(UIInterfaceOrientation)[[UIDevice currentDevice] orientation]];
+}
+
+- (void) webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error 
+{
+    NSLog(@"Failed to load webpage with error: %@", [error localizedDescription]);
+    /*
+	 if ([error code] != NSURLErrorCancelled)
+	 alert([error localizedDescription]);
+     */
+}
+
+- (BOOL) webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+	NSURL* url = [request URL];
+    
+    /*
+     * Execute any commands queued with Cordova.exec() on the JS side.
+     * The part of the URL after gap:// is irrelevant.
+     */
+	if ([[url scheme] isEqualToString:@"gap"]) {
+        [self flushCommandQueue];
+        return NO;
+	}
+    /*
+     * If a URL is being loaded that's a file/http/https URL, just load it internally
+     */
+    else if ([url isFileURL])
+    {
+        return YES;
+    }
+    else if ([self.whitelist schemeIsAllowed:[url scheme]])
+    {            
+        if ([self.whitelist URLIsAllowed:url] == YES)
+        {
+            NSNumber *openAllInWhitelistSetting = [self.settings objectForKey:@"OpenAllWhitelistURLsInWebView"];
+            if ((nil != openAllInWhitelistSetting) && [openAllInWhitelistSetting boolValue]) {
+                NSLog(@"OpenAllWhitelistURLsInWebView set: opening in webview");
+                return YES;
+            }
+			
+            // mainDocument will be nil for an iFrame
+            NSString* mainDocument = [theWebView.request.mainDocumentURL absoluteString];
+			
+            // anchor target="_blank" - load in Mobile Safari
+            if (navigationType == UIWebViewNavigationTypeOther && mainDocument != nil)
+            {
+                [[UIApplication sharedApplication] openURL:url];
+                return NO;
+            }
+            // other anchor target - load in Cordova webView
+            else
+            {
+                return YES;
+            }
+        }
+        
+        return NO;
+    }
+    /*
+     *    If we loaded the HTML from a string, we let the app handle it
+     */
+    else if (self.loadFromString == YES)
+    {
+        self.loadFromString = NO;
+        return YES;
+    }
+    /*
+     * all tel: scheme urls we let the UIWebview handle it using the default behaviour
+     */
+    else if ([[url scheme] isEqualToString:@"tel"])
+    {
+        return YES;
+    }
+    /*
+     * all about: scheme urls are not handled
+     */
+    else if ([[url scheme] isEqualToString:@"about"])
+    {
+        return NO;
+    }
+    /*
+     * We don't have a Cordova or web/local request, load it in the main Safari browser.
+     * pass this to the application to handle.  Could be a mailto:dude@duderanch.com or a tel:55555555 or sms:55555555 facetime:55555555
+     */
+    else
+    {
+        NSLog(@"PGAppDelegate::shouldStartLoadWithRequest: Received Unhandled URL %@", url);
+		
+        if ([[UIApplication sharedApplication] canOpenURL:url]) {
+            [[UIApplication sharedApplication] openURL:url];
+        } else { // handle any custom schemes to plugins
+            [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
+        }
+		
+        return NO;
+    }
+    
+    return YES;
+}
+
+#pragma mark GapHelpers
+
+- (void) javascriptAlert:(NSString*)text
+{
+    NSString* jsString = [NSString stringWithFormat:@"alert('%@');", text];
+    [webView stringByEvaluatingJavaScriptFromString:jsString];
+}
+
++ (BOOL) isIPad 
+{
+#ifdef UI_USER_INTERFACE_IDIOM
+    return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
+#else
+    return NO;
+#endif
+}
+
++ (NSString*) resolveImageResource:(NSString*)resource
+{
+    NSString* systemVersion = [[UIDevice currentDevice] systemVersion];
+    BOOL isLessThaniOS4 = ([systemVersion compare:@"4.0" options:NSNumericSearch] == NSOrderedAscending);
+    
+    // the iPad image (nor retina) differentiation code was not in 3.x, and we have to explicitly set the path
+    if (isLessThaniOS4)
+    {
+        if ([[self class] isIPad]) {
+            return [NSString stringWithFormat:@"%@~ipad.png", resource];
+        } else {
+            return [NSString stringWithFormat:@"%@.png", resource];
+        }
+    }
+    
+    return resource;
+}
+
+- (NSString*) pathForResource:(NSString*)resourcepath
+{
+    NSBundle * mainBundle = [NSBundle mainBundle];
+    NSMutableArray *directoryParts = [NSMutableArray arrayWithArray:[resourcepath componentsSeparatedByString:@"/"]];
+    NSString       *filename       = [directoryParts lastObject];
+    [directoryParts removeLastObject];
+    
+    NSString* directoryPartsJoined =[directoryParts componentsJoinedByString:@"/"];
+    NSString* directoryStr = self.wwwFolderName;
+    
+    if ([directoryPartsJoined length] > 0) {
+        directoryStr = [NSString stringWithFormat:@"%@/%@", self.wwwFolderName, [directoryParts componentsJoinedByString:@"/"]];
+    }
+    
+    return [mainBundle pathForResource:filename ofType:@"" inDirectory:directoryStr];
+}
+
++ (NSString*) applicationDocumentsDirectory 
+{
+    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
+    return basePath;
+}
+
+- (void) showSplashScreen
+{
+    NSString* launchImageFile = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchImageFile"];
+    if (launchImageFile == nil) { // fallback if no launch image was specified
+        launchImageFile = @"Default"; 
+    }
+    
+    NSString* orientedLaunchImageFile = nil;    
+    CGAffineTransform startupImageTransform = CGAffineTransformIdentity;
+    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
+    CGRect screenBounds = [[UIScreen mainScreen] bounds];
+    UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
+    BOOL isIPad = [[self class] isIPad];
+    UIImage* launchImage = nil;
+    
+    if (isIPad)
+    {
+        if (!UIDeviceOrientationIsValidInterfaceOrientation(deviceOrientation)) {
+            deviceOrientation = (UIDeviceOrientation)statusBarOrientation;
+        }
+        
+        switch (deviceOrientation) 
+        {
+            case UIDeviceOrientationLandscapeLeft: // this is where the home button is on the right (yeah, I know, confusing)
+            {
+                orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Landscape", launchImageFile];
+                startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(90));
+            }
+                break;
+            case UIDeviceOrientationLandscapeRight: // this is where the home button is on the left (yeah, I know, confusing)
+            {
+                orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Landscape", launchImageFile];
+                startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(-90));
+            } 
+                break;
+            case UIDeviceOrientationPortraitUpsideDown:
+            {
+                orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Portrait", launchImageFile];
+                startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(180));
+            } 
+                break;
+            case UIDeviceOrientationPortrait:
+            default:
+            {
+                orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Portrait", launchImageFile];
+                startupImageTransform = CGAffineTransformIdentity;
+            }
+                break;
+        }
+        
+        launchImage = [UIImage imageNamed:[[self class] resolveImageResource:orientedLaunchImageFile]];
+    }
+    else // not iPad
+    {
+        orientedLaunchImageFile = @"Default";
+        launchImage = [UIImage imageNamed:[[self class] resolveImageResource:orientedLaunchImageFile]];
+    }
+    
+    if (launchImage == nil) {
+        NSLog(@"WARNING: Splash-screen image '%@' was not found. Orientation: %d, iPad: %d", orientedLaunchImageFile, deviceOrientation, isIPad);
+    }
+    
+    self.imageView = [[[UIImageView alloc] initWithImage:launchImage] autorelease];    
+    self.imageView.tag = 1;
+    self.imageView.center = CGPointMake((screenBounds.size.width / 2), (screenBounds.size.height / 2));
+    
+    self.imageView.autoresizingMask = (UIViewAutoresizingFlexibleWidth & UIViewAutoresizingFlexibleHeight & UIViewAutoresizingFlexibleLeftMargin & UIViewAutoresizingFlexibleRightMargin);    
+    [self.imageView setTransform:startupImageTransform];
+    [self.view.superview addSubview:self.imageView];
+    
+    
+    /*
+     * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style.
+     *
+     *     whiteLarge = UIActivityIndicatorViewStyleWhiteLarge
+     *     white      = UIActivityIndicatorViewStyleWhite
+     *     gray       = UIActivityIndicatorViewStyleGray
+     *
+     */
+    NSString* topActivityIndicator = [self.settings objectForKey:@"TopActivityIndicator"];
+    UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray;
+    
+    if ([topActivityIndicator isEqualToString:@"whiteLarge"]) {
+        topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge;
+    } else if ([topActivityIndicator isEqualToString:@"white"]) {
+        topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite;
+    } else if ([topActivityIndicator isEqualToString:@"gray"]) {
+        topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray;
+    }
+    
+    self.activityView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle] autorelease];
+    self.activityView.tag = 2;
+    
+    id showSplashScreenSpinnerValue = [self.settings objectForKey:@"ShowSplashScreenSpinner"];
+    // backwards compatibility - if key is missing, default to true
+    if (showSplashScreenSpinnerValue == nil || [showSplashScreenSpinnerValue boolValue]) {
+        [self.view.superview addSubview:self.activityView];
+    }
+    
+    self.activityView.center = self.view.center;
+    [self.activityView startAnimating];
+    
+    [self.view.superview layoutSubviews];
+}    
+
+BOOL gSplashScreenShown = NO;
+- (void) receivedOrientationChange
+{
+    if (self.imageView == nil) {
+        gSplashScreenShown = YES;
+        if (self.useSplashScreen) {
+            [self showSplashScreen];
+        }
+    }
+}
+
+#pragma mark CordovaCommands
+
+/**
+ * Fetches the command queue and executes each command. It is possible that the
+ * queue will not be empty after this function has completed since the executed
+ * commands may have run callbacks which queued more commands.
+ *
+ * Returns the number of executed commands.
+ */
+- (int) executeQueuedCommands
+{
+    // Grab all the queued commands from the JS side.
+    NSString* queuedCommandsJSON = [self.webView stringByEvaluatingJavaScriptFromString:
+									@"Cordova.getAndClearQueuedCommands()"];
+	
+	
+    // Parse the returned JSON array.
+    //PG_SBJsonParser* jsonParser = [[[PG_SBJsonParser alloc] init] autorelease];
+    NSArray* queuedCommands =
+	[queuedCommandsJSON objectFromJSONString];
+	
+    // Iterate over and execute all of the commands.
+    for (NSString* commandJson in queuedCommands) {
+		
+        if(![self.commandDelegate execute:
+		 [CDVInvokedUrlCommand commandFromObject:
+		  [commandJson mutableObjectFromJSONString]]])
+		{
+			NSLog(@"FAILED pluginJSON = %@",commandJson);
+		}
+    }
+	
+    return [queuedCommands count];
+}
+
+/**
+ * Repeatedly fetches and executes the command queue until it is empty.
+ */
+- (void) flushCommandQueue
+{
+    [self.webView stringByEvaluatingJavaScriptFromString:
+	 @"Cordova.commandQueueFlushing = true"];
+	
+    // Keep executing the command queue until no commands get executed.
+    // This ensures that commands that are queued while executing other
+    // commands are executed as well.
+    int numExecutedCommands = 0;
+    do {
+        numExecutedCommands = [self executeQueuedCommands];
+    } while (numExecutedCommands != 0);
+	
+    [self.webView stringByEvaluatingJavaScriptFromString:
+	 @"Cordova.commandQueueFlushing = false"];
+}
+
+- (BOOL) execute:(CDVInvokedUrlCommand*)command
+{
+    if (command.className == nil || command.methodName == nil) {
+        return NO;
+    }
+    
+    // Fetch an instance of this class
+    CDVPlugin* obj = [self.commandDelegate getCommandInstance:command.className];
+    
+    if (!([obj isKindOfClass:[CDVPlugin class]])) { // still allow deprecated class, until 1.0 release
+        NSLog(@"ERROR: Plugin '%@' not found, or is not a CDVPlugin. Check your plugin mapping in Cordova.plist.", command.className);
+        return NO;
+    }
+    BOOL retVal = YES;
+    
+    // construct the fill method name to ammend the second argument.
+    NSString* fullMethodName = [[NSString alloc] initWithFormat:@"%@:withDict:", command.methodName];
+    if ([obj respondsToSelector:NSSelectorFromString(fullMethodName)]) {
+        [obj performSelector:NSSelectorFromString(fullMethodName) withObject:command.arguments withObject:command.options];
+    } else {
+        // There's no method to call, so throw an error.
+        NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", fullMethodName, command.className);
+        retVal = NO;
+    }
+    [fullMethodName release];
+    
+    return retVal;
+}
+
+/**
+ Returns an instance of a CordovaCommand object, based on its name.  If one exists already, it is returned.
+ */
+- (id) getCommandInstance:(NSString*)pluginName
+{
+    // first, we try to find the pluginName in the pluginsMap 
+    // (acts as a whitelist as well) if it does not exist, we return nil
+    // NOTE: plugin names are matched as lowercase to avoid problems - however, a 
+    // possible issue is there can be duplicates possible if you had:
+    // "org.apache.cordova.Foo" and "org.apache.cordova.foo" - only the lower-cased entry will match
+    NSString* className = [self.pluginsMap objectForKey:[pluginName lowercaseString]];
+    if (className == nil) {
+        return nil;
+    }
+    
+    id obj = [self.pluginObjects objectForKey:className];
+    if (!obj) 
+    {
+        // attempt to load the settings for this command class
+        NSDictionary* classSettings = [self.settings objectForKey:className];
+		
+        if (classSettings) {
+            obj = [[NSClassFromString(className) alloc] initWithWebView:webView settings:classSettings];
+        } else {
+            obj = [[NSClassFromString(className) alloc] initWithWebView:webView];
+        }
+        
+        if ([obj isKindOfClass:[CDVPlugin class]] && [obj respondsToSelector:@selector(setViewController:)]) { 
+            [obj setViewController:self];
+        }
+        
+        if ([obj isKindOfClass:[CDVPlugin class]] && [obj respondsToSelector:@selector(setCommandDelegate:)]) { 
+            [obj setCommandDelegate:self.commandDelegate];
+        }
+
+        if (obj != nil) {
+            [self.pluginObjects setObject:obj forKey:className];
+            [obj release];
+        } else {
+            NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName);
+        }
+    }
+    return obj;
+}
+
+
+#pragma mark -
+
+- (NSDictionary*) deviceProperties
+{
+    UIDevice *device = [UIDevice currentDevice];
+    NSMutableDictionary *devProps = [NSMutableDictionary dictionaryWithCapacity:4];
+    [devProps setObject:[device model] forKey:@"platform"];
+    [devProps setObject:[device systemVersion] forKey:@"version"];
+    [devProps setObject:[device uniqueIdentifier] forKey:@"uuid"];
+    [devProps setObject:[device name] forKey:@"name"];
+    [devProps setObject:[[self class] cordovaVersion ] forKey:@"gap"];
+    
+    id cmd = [self.commandDelegate getCommandInstance:@"org.apache.cordova.connection"];
+    if (cmd && [cmd isKindOfClass:[CDVConnection class]]) 
+    {
+        NSMutableDictionary *connProps = [NSMutableDictionary dictionaryWithCapacity:3];
+        if ([cmd respondsToSelector:@selector(connectionType)]) {
+            [connProps setObject:[cmd connectionType] forKey:@"type"];
+        }
+        [devProps setObject:connProps forKey:@"connection"];
+    }
+    
+    NSDictionary *devReturn = [NSDictionary dictionaryWithDictionary:devProps];
+    return devReturn;
+}
+
+- (NSString*) appURLScheme
+{
+    NSString* URLScheme = nil;
+    
+    NSArray *URLTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleURLTypes"];
+    if(URLTypes != nil ) {
+        NSDictionary* dict = [URLTypes objectAtIndex:0];
+        if(dict != nil ) {
+            NSArray* URLSchemes = [dict objectForKey:@"CFBundleURLSchemes"];
+            if( URLSchemes != nil ) {    
+                URLScheme = [URLSchemes objectAtIndex:0];
+            }
+        }
+    }
+    
+    return URLScheme;
+}
+
+/**
+ Returns the contents of the named plist bundle, loaded as a dictionary object
+ */
++ (NSDictionary*) getBundlePlist:(NSString*)plistName
+{
+    NSString *errorDesc = nil;
+    NSPropertyListFormat format;
+    NSString *plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:@"plist"];
+    NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
+    NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization
+                                          propertyListFromData:plistXML
+                                          mutabilityOption:NSPropertyListMutableContainersAndLeaves              
+                                          format:&format errorDescription:&errorDesc];
+    return temp;
+}
+
+/**
+ Returns the current version of Cordova as read from the VERSION file
+ This only touches the filesystem once and stores the result in the class variable gapVersion
+ */
+static NSString* cdvVersion;
++ (NSString*) cordovaVersion
+{
+#ifdef CDV_VERSION
+    cdvVersion = SYMBOL_TO_NSSTRING(CDV_VERSION);
+#else
+	
+    if (cdvVersion == nil) {
+        NSBundle *mainBundle = [NSBundle mainBundle];
+        NSString *filename = [mainBundle pathForResource:@"VERSION" ofType:nil];
+        // read from the filesystem and save in the variable
+        // first, separate by new line
+        NSString* fileContents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:NULL];
+        NSArray* all_lines = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
+        NSString* first_line = [all_lines objectAtIndex:0];        
+        
+        cdvVersion = [first_line retain];
+    }
+#endif
+    
+    return cdvVersion;
+}
+
+
+#pragma mark -
+#pragma mark UIApplicationDelegate impl
+
+/*
+ This method lets your application know that it is about to be terminated and purged from memory entirely
+ */
+- (void) onAppWillTerminate:(NSNotification*)notification
+{
+    NSLog(@"applicationWillTerminate");
+    
+    // empty the tmp directory
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
+    NSError* err = nil;    
+    
+    // clear contents of NSTemporaryDirectory 
+    NSString* tempDirectoryPath = NSTemporaryDirectory();
+    NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];    
+    NSString* fileName = nil;
+    BOOL result;
+    
+    while ((fileName = [directoryEnumerator nextObject])) {
+        NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
+        result = [fileMgr removeItemAtPath:filePath error:&err];
+        if (!result && err) {
+            NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
+        }
+    }    
+    [fileMgr release];
+}
+
+/*
+ This method is called to let your application know that it is about to move from the active to inactive state.
+ You should use this method to pause ongoing tasks, disable timer, ...
+ */
+- (void) onAppWillResignActive:(NSNotification*)notification
+{
+    //NSLog(@"%@",@"applicationWillResignActive");
+    [self.webView stringByEvaluatingJavaScriptFromString:@"Cordova.fireDocumentEvent('resign');"];
+}
+
+/*
+ In iOS 4.0 and later, this method is called as part of the transition from the background to the inactive state. 
+ You can use this method to undo many of the changes you made to your application upon entering the background.
+ invariably followed by applicationDidBecomeActive
+ */
+- (void) onAppWillEnterForeground:(NSNotification*)notification
+{
+    //NSLog(@"%@",@"applicationWillEnterForeground");
+    [self.webView stringByEvaluatingJavaScriptFromString:@"Cordova.fireDocumentEvent('resume');"];
+}
+
+// This method is called to let your application know that it moved from the inactive to active state. 
+- (void) onAppDidBecomeActive:(NSNotification*)notification
+{
+    //NSLog(@"%@",@"applicationDidBecomeActive");
+    [self.webView stringByEvaluatingJavaScriptFromString:@"Cordova.fireDocumentEvent('active');"];
+}
+
+/*
+ In iOS 4.0 and later, this method is called instead of the applicationWillTerminate: method 
+ when the user quits an application that supports background execution.
+ */
+- (void) onAppDidEnterBackground:(NSNotification*)notification
+{
+    //NSLog(@"%@",@"applicationDidEnterBackground");
+    [self.webView stringByEvaluatingJavaScriptFromString:@"Cordova.fireDocumentEvent('pause');"];
+}
+
+// ///////////////////////
+
+
+- (void)dealloc 
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
+    
+    [super dealloc];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVWhitelist.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVWhitelist.h b/CordovaLib/Classes/CDVWhitelist.h
new file mode 100644
index 0000000..37f7969
--- /dev/null
+++ b/CordovaLib/Classes/CDVWhitelist.h
@@ -0,0 +1,33 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface CDVWhitelist : NSObject
+
+@property (nonatomic, readonly, retain) NSArray* whitelist;
+@property (nonatomic, readonly, retain) NSArray* expandedWhitelist;
+@property (nonatomic, readonly, assign) BOOL allowAll;
+
+- (id) initWithArray:(NSArray*)array;
+- (BOOL) URLIsAllowed:(NSURL*)url;
+- (BOOL) schemeIsAllowed:(NSString*)scheme;
+- (NSString*) errorStringForURL:(NSURL*)url;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/bcff9559/CordovaLib/Classes/CDVWhitelist.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/CDVWhitelist.m b/CordovaLib/Classes/CDVWhitelist.m
new file mode 100644
index 0000000..f740b5a
--- /dev/null
+++ b/CordovaLib/Classes/CDVWhitelist.m
@@ -0,0 +1,183 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "CDVWhitelist.h"
+
+@interface CDVWhitelist ()
+
+@property (nonatomic, readwrite, retain) NSArray* whitelist;
+@property (nonatomic, readwrite, retain) NSArray* expandedWhitelist;
+@property (nonatomic, readwrite, assign) BOOL allowAll;
+
+- (void) processWhitelist;
+
+@end
+
+@implementation CDVWhitelist
+
+@synthesize whitelist, expandedWhitelist, allowAll;
+
+- (id) initWithArray:(NSArray*)array
+{
+    self = [super init];
+    if (self) {
+        self.whitelist = array;
+        self.expandedWhitelist = nil;
+        self.allowAll = NO;
+        [self processWhitelist];
+    }
+    
+    return self;
+}
+
+- (BOOL) isIPv4Address:(NSString*)externalHost
+{
+    // an IPv4 address has 4 octets b.b.b.b where b is a number between 0 and 255. 
+    // for our purposes, b can also be the wildcard character '*'
+
+    // we could use a regex to solve this problem but then I would have two problems
+    // anyways, this is much clearer and maintainable
+    NSArray* octets = [externalHost componentsSeparatedByString: @"."];
+    NSUInteger num_octets = [octets count];
+    
+    // quick check
+    if (num_octets != 4) {
+        return NO;
+    }
+    
+    // restrict number parsing to 0-255
+    NSNumberFormatter* numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
+    [numberFormatter setMinimum:[NSNumber numberWithUnsignedInteger:0]];
+    [numberFormatter setMaximum:[NSNumber numberWithUnsignedInteger:255]];
+    
+    // iterate through each octet, and test for a number between 0-255 or if it equals '*'
+    for (NSUInteger i=0; i < num_octets; ++i)
+    {
+        NSString* octet = [octets objectAtIndex:i];
+        
+        if ([octet isEqualToString:@"*"]) { // passes - check next octet
+            continue;
+        } else if ([numberFormatter numberFromString:octet] == nil) { // fails - not a number and not within our range, return
+            return NO;
+        }
+    }
+    
+    return YES;
+}
+
+- (void) processWhitelist
+{
+    if (self.whitelist == nil) {
+        NSLog(@"ERROR: PGWhitelist was not initialized properly, all urls will be disallowed.");
+        return;
+    }
+    
+    NSMutableArray* expanded = [NSMutableArray arrayWithCapacity:[self.whitelist count]];
+    
+    // iterate through settings ExternalHosts, check for equality
+    NSEnumerator* enumerator = [self.whitelist  objectEnumerator];
+    id externalHost = nil;
+    
+    // only allow known TLDs (since Aug 23rd 2011), and two character country codes
+    // does not match internationalized domain names with non-ASCII characters
+    NSString* tld_match = @"(aero|asia|arpa|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|[a-z][a-z])";
+    
+    while (externalHost = [enumerator nextObject])
+    {
+        NSString* regex = [[externalHost copy] autorelease];
+        BOOL is_ip = [self isIPv4Address:regex];
+        
+        // check for single wildcard '*', if found set allowAll to YES
+        if ([regex isEqualToString:@"*"]) {
+            self.allowAll = YES;
+            self.expandedWhitelist = [NSArray arrayWithObject:regex];
+            break;
+        }
+        
+        // starts with wildcard match - we make the first '.' optional (so '*.org.apache.cordova' will match 'org.apache.cordova')
+        NSString* prefix = @"*.";
+        if ([regex hasPrefix:prefix]) { 
+            // replace the first two characters '*.' with our regex
+            regex = [regex stringByReplacingCharactersInRange:NSMakeRange(0, [prefix length]) withString:@"(\\s{0}|*.)"]; // the '*' and '.' will be substituted later
+        }
+        
+        // ends with wildcard match for TLD
+        if (!is_ip && [regex hasSuffix:@".*"]) { 
+            // replace * with tld_match
+            regex = [regex stringByReplacingCharactersInRange:NSMakeRange([regex length]-1, 1) withString:tld_match];
+        }
+        // escape periods - since '.' means any character in regex
+        regex = [regex stringByReplacingOccurrencesOfString:@"." withString:@"\\."];
+        // wildcard is match 1 or more characters (to make it simple, since we are not doing verification whether the hostname is valid)
+        regex = [regex stringByReplacingOccurrencesOfString:@"*" withString:@".*"];  
+        
+        [expanded addObject:regex];
+    }
+    
+    self.expandedWhitelist = expanded;
+}
+
+- (BOOL) schemeIsAllowed:(NSString*)scheme
+{
+    return ([scheme isEqualToString:@"http"] || 
+            [scheme isEqualToString:@"https"] || 
+            [scheme isEqualToString:@"ftp"] || 
+            [scheme isEqualToString:@"ftps"] );
+}
+
+- (BOOL) URLIsAllowed:(NSURL*)url
+{
+    if (self.expandedWhitelist == nil) {
+        NSLog(@"ERROR: PGWhitelist was not initialized properly, all urls will be disallowed.");
+        return NO;
+    }
+    
+    if (self.allowAll) {
+        return YES;
+    }
+
+    // iterate through settings ExternalHosts, check for equality
+    NSEnumerator* enumerator = [self.expandedWhitelist  objectEnumerator];
+    id regex = nil;
+    NSString* urlHost = [url host];
+    
+    // if the url host IS found in the whitelist, load it in the app (however UIWebViewNavigationTypeOther kicks it out to Safari)
+    // if the url host IS NOT found in the whitelist, we do nothing
+    while (regex = [enumerator nextObject])
+    {
+        NSPredicate* regex_test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
+        
+        if ([regex_test evaluateWithObject:urlHost] == YES)
+        {
+            // if it matches at least one rule, return 
+            return YES;
+        }
+    }
+    
+    NSLog([self errorStringForURL:url], @"");
+    // if we got here, the url host is not in the white-list, do nothing
+    return NO;
+}
+
+- (NSString*) errorStringForURL:(NSURL*)url
+{
+    return [NSString stringWithFormat:@"ERROR whitelist rejection: url='%@'", [url absoluteString]];
+}
+
+@end