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