You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2013/01/22 02:57:59 UTC
[16/52] [partial] support for 2.4.0rc1. "vendored" the platform libs
in. added Gord and Braden as contributors. removed dependency on unzip and
axed the old download-cordova code.
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDV.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDV.h b/lib/cordova-ios/CordovaLib/Classes/CDV.h
new file mode 100644
index 0000000..5a0ae6a
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDV.h
@@ -0,0 +1,57 @@
+/*
+ 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 "CDVAvailability.h"
+
+#import "CDVPlugin.h"
+#import "CDVViewController.h"
+#import "CDVCommandDelegate.h"
+#import "CDVURLProtocol.h"
+#import "CDVInvokedUrlCommand.h"
+
+#import "CDVAccelerometer.h"
+#import "CDVBattery.h"
+#import "CDVCamera.h"
+#import "CDVCapture.h"
+#import "CDVConnection.h"
+#import "CDVContact.h"
+#import "CDVContacts.h"
+#import "CDVDebug.h"
+#import "CDVDebugConsole.h"
+#import "CDVDevice.h"
+#import "CDVFile.h"
+#import "CDVFileTransfer.h"
+#import "CDVLocation.h"
+#import "CDVNotification.h"
+#import "CDVPluginResult.h"
+#import "CDVReachability.h"
+#import "CDVSound.h"
+#import "CDVSplashScreen.h"
+#import "CDVWhitelist.h"
+#import "CDVLocalStorage.h"
+#import "CDVInAppBrowser.h"
+#import "CDVScreenOrientationDelegate.h"
+
+#import "NSArray+Comparisons.h"
+#import "NSData+Base64.h"
+#import "NSDictionary+Extensions.h"
+#import "NSMutableArray+QueueAdditions.h"
+#import "UIDevice+Extensions.h"
+
+#import "CDVJSON.h"
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.h b/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.h
new file mode 100644
index 0000000..044ca53
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.h
@@ -0,0 +1,39 @@
+/*
+ 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 <UIKit/UIKit.h>
+#import "CDVPlugin.h"
+
+@interface CDVAccelerometer : CDVPlugin <UIAccelerometerDelegate>
+{
+ double x;
+ double y;
+ double z;
+ NSTimeInterval timestamp;
+}
+
+@property (readonly, assign) BOOL isRunning;
+@property (nonatomic, strong) NSString* callbackId;
+
+- (CDVAccelerometer*)init;
+
+- (void)start:(CDVInvokedUrlCommand*)command;
+- (void)stop:(CDVInvokedUrlCommand*)command;
+
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.m b/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.m
new file mode 100644
index 0000000..33093d0
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVAccelerometer.m
@@ -0,0 +1,128 @@
+/*
+ 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 "CDVAccelerometer.h"
+
+@interface CDVAccelerometer () {}
+@property (readwrite, assign) BOOL isRunning;
+@end
+
+@implementation CDVAccelerometer
+
+@synthesize callbackId, isRunning;
+
+// defaults to 10 msec
+#define kAccelerometerInterval 40
+// g constant: -9.81 m/s^2
+#define kGravitationalConstant -9.81
+
+- (CDVAccelerometer*)init
+{
+ self = [super init];
+ if (self) {
+ x = 0;
+ y = 0;
+ z = 0;
+ timestamp = 0;
+ self.callbackId = nil;
+ self.isRunning = NO;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [self stop:nil];
+}
+
+- (void)start:(CDVInvokedUrlCommand*)command
+{
+ NSString* cbId = command.callbackId;
+ NSTimeInterval desiredFrequency_num = kAccelerometerInterval;
+ UIAccelerometer* pAccel = [UIAccelerometer sharedAccelerometer];
+
+ // accelerometer expects fractional seconds, but we have msecs
+ pAccel.updateInterval = desiredFrequency_num / 1000;
+ self.callbackId = cbId;
+ if (!self.isRunning) {
+ pAccel.delegate = self;
+ self.isRunning = YES;
+ }
+}
+
+- (void)onReset
+{
+ [self stop:nil];
+}
+
+- (void)stop:(CDVInvokedUrlCommand*)command
+{
+ UIAccelerometer* theAccelerometer = [UIAccelerometer sharedAccelerometer];
+
+ theAccelerometer.delegate = nil;
+ self.isRunning = NO;
+}
+
+/**
+ * Picks up accel updates from device and stores them in this class
+ */
+- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration
+{
+ if (self.isRunning) {
+ x = acceleration.x;
+ y = acceleration.y;
+ z = acceleration.z;
+ timestamp = ([[NSDate date] timeIntervalSince1970] * 1000);
+ [self returnAccelInfo];
+ }
+}
+
+- (void)returnAccelInfo
+{
+ // Create an acceleration object
+ NSMutableDictionary* accelProps = [NSMutableDictionary dictionaryWithCapacity:4];
+
+ [accelProps setValue:[NSNumber numberWithDouble:x * kGravitationalConstant] forKey:@"x"];
+ [accelProps setValue:[NSNumber numberWithDouble:y * kGravitationalConstant] forKey:@"y"];
+ [accelProps setValue:[NSNumber numberWithDouble:z * kGravitationalConstant] forKey:@"z"];
+ [accelProps setValue:[NSNumber numberWithDouble:timestamp] forKey:@"timestamp"];
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:accelProps];
+ [result setKeepCallback:[NSNumber numberWithBool:YES]];
+ [self.commandDelegate sendPluginResult:result callbackId:self.callbackId];
+}
+
+// TODO: Consider using filtering to isolate instantaneous data vs. gravity data -jm
+
+/*
+ #define kFilteringFactor 0.1
+
+ // Use a basic low-pass filter to keep only the gravity component of each axis.
+ grav_accelX = (acceleration.x * kFilteringFactor) + ( grav_accelX * (1.0 - kFilteringFactor));
+ grav_accelY = (acceleration.y * kFilteringFactor) + ( grav_accelY * (1.0 - kFilteringFactor));
+ grav_accelZ = (acceleration.z * kFilteringFactor) + ( grav_accelZ * (1.0 - kFilteringFactor));
+
+ // Subtract the low-pass value from the current value to get a simplified high-pass filter
+ instant_accelX = acceleration.x - ( (acceleration.x * kFilteringFactor) + (instant_accelX * (1.0 - kFilteringFactor)) );
+ instant_accelY = acceleration.y - ( (acceleration.y * kFilteringFactor) + (instant_accelY * (1.0 - kFilteringFactor)) );
+ instant_accelZ = acceleration.z - ( (acceleration.z * kFilteringFactor) + (instant_accelZ * (1.0 - kFilteringFactor)) );
+
+
+ */
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVAvailability.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVAvailability.h b/lib/cordova-ios/CordovaLib/Classes/CDVAvailability.h
new file mode 100644
index 0000000..67583be
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVAvailability.h
@@ -0,0 +1,75 @@
+/*
+ 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.
+ */
+
+#define __CORDOVA_0_9_6 906
+#define __CORDOVA_1_0_0 10000
+#define __CORDOVA_1_1_0 10100
+#define __CORDOVA_1_2_0 10200
+#define __CORDOVA_1_3_0 10300
+#define __CORDOVA_1_4_0 10400
+#define __CORDOVA_1_4_1 10401
+#define __CORDOVA_1_5_0 10500
+#define __CORDOVA_1_6_0 10600
+#define __CORDOVA_1_6_1 10601
+#define __CORDOVA_1_7_0 10700
+#define __CORDOVA_1_8_0 10800
+#define __CORDOVA_1_8_1 10801
+#define __CORDOVA_1_9_0 10900
+#define __CORDOVA_2_0_0 20000
+#define __CORDOVA_2_1_0 20100
+#define __CORDOVA_2_2_0 20200
+#define __CORDOVA_2_3_0 20300
+#define __CORDOVA_2_4_0 20400
+#define __CORDOVA_NA 99999 /* not available */
+
+/*
+ #if CORDOVA_VERSION_MIN_REQUIRED >= __CORDOVA_1_7_0
+ // do something when its at least 1.7.0
+ #else
+ // do something else (non 1.7.0)
+ #endif
+ */
+#ifndef CORDOVA_VERSION_MIN_REQUIRED
+ #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_4_0
+#endif
+
+/*
+ Returns YES if it is at least version specified as NSString(X)
+ Usage:
+ if (IsAtLeastiOSVersion(@"5.1")) {
+ // do something for iOS 5.1 or greater
+ }
+ */
+#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending)
+
+#define CDV_IsIPad() ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] && ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad))
+
+#define CDV_IsIPhone5() ([[UIScreen mainScreen] bounds].size.height == 568 && [[UIScreen mainScreen] bounds].size.width == 320)
+
+/* Return the string version of the decimal version */
+#define CDV_VERSION [NSString stringWithFormat:@"%d.%d.%d", \
+ (CORDOVA_VERSION_MIN_REQUIRED / 10000), \
+ (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100, \
+ (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100]
+
+#ifdef __clang__
+ #define CDV_DEPRECATED(version, msg) __attribute__((deprecated("Deprecated in Cordova " #version ". " msg)))
+#else
+ #define CDV_DEPRECATED(version, msg) __attribute__((deprecated()))
+#endif
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVBattery.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVBattery.h b/lib/cordova-ios/CordovaLib/Classes/CDVBattery.h
new file mode 100644
index 0000000..ba26c3a
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVBattery.h
@@ -0,0 +1,40 @@
+/*
+ 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 CDVBattery : CDVPlugin {
+ UIDeviceBatteryState state;
+ float level;
+ bool isPlugged;
+ NSString* callbackId;
+}
+
+@property (nonatomic) UIDeviceBatteryState state;
+@property (nonatomic) float level;
+@property (nonatomic) bool isPlugged;
+@property (strong) NSString* callbackId;
+
+- (void)updateBatteryStatus:(NSNotification*)notification;
+- (NSDictionary*)getBatteryStatus;
+- (void)start:(CDVInvokedUrlCommand*)command;
+- (void)stop:(CDVInvokedUrlCommand*)command;
+- (void)dealloc;
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVBattery.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVBattery.m b/lib/cordova-ios/CordovaLib/Classes/CDVBattery.m
new file mode 100644
index 0000000..681511c
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVBattery.m
@@ -0,0 +1,152 @@
+/*
+ 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 "CDVBattery.h"
+
+@interface CDVBattery (PrivateMethods)
+- (void)updateOnlineStatus;
+@end
+
+@implementation CDVBattery
+
+@synthesize state, level, callbackId, isPlugged;
+
+/* determining type of event occurs on JavaScript side
+- (void) updateBatteryLevel:(NSNotification*)notification
+{
+ // send batterylow event for less than 25% battery
+ // send batterycritical event for less than 10% battery
+ // W3c says to send batteryStatus event when batterylevel changes by more than 1% (iOS seems to notify each 5%)
+ // always update the navigator.device.battery info
+ float currentLevel = [[UIDevice currentDevice] batteryLevel];
+ NSString* type = @"";
+ // no check for level == -1 since this api is only called when monitoring is enabled so level should be valid
+ if (currentLevel < 0.10){
+ type = @"batterycritical";
+ } else if (currentLevel < 0.25) {
+ type = @"batterylow";
+ } else {
+ float onePercent = 0.1;
+ if (self.level >= 0 ){
+ onePercent = self.level * 0.01;
+ }
+ if (fabsf(currentLevel - self.level) > onePercent){
+ // issue batteryStatus event
+ type = @"batterystatus";
+ }
+ }
+ // update the battery info and fire event
+ NSString* jsString = [NSString stringWithFormat:@"navigator.device.battery._status(\"%@\", %@)", type,[[self getBatteryStatus] JSONRepresentation]];
+ [super writeJavascript:jsString];
+}
+ */
+
+- (void)updateBatteryStatus:(NSNotification*)notification
+{
+ NSDictionary* batteryData = [self getBatteryStatus];
+
+ if (self.callbackId) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:batteryData];
+ [result setKeepCallbackAsBool:YES];
+ [self.commandDelegate sendPluginResult:result callbackId:self.callbackId];
+ }
+}
+
+/* Get the current battery status and level. Status will be unknown and level will be -1.0 if
+ * monitoring is turned off.
+ */
+- (NSDictionary*)getBatteryStatus
+{
+ UIDevice* currentDevice = [UIDevice currentDevice];
+ UIDeviceBatteryState currentState = [currentDevice batteryState];
+
+ isPlugged = FALSE; // UIDeviceBatteryStateUnknown or UIDeviceBatteryStateUnplugged
+ if ((currentState == UIDeviceBatteryStateCharging) || (currentState == UIDeviceBatteryStateFull)) {
+ isPlugged = TRUE;
+ }
+ float currentLevel = [currentDevice batteryLevel];
+
+ if ((currentLevel != self.level) || (currentState != self.state)) {
+ self.level = currentLevel;
+ self.state = currentState;
+ }
+
+ // W3C spec says level must be null if it is unknown
+ NSObject* w3cLevel = nil;
+ if ((currentState == UIDeviceBatteryStateUnknown) || (currentLevel == -1.0)) {
+ w3cLevel = [NSNull null];
+ } else {
+ w3cLevel = [NSNumber numberWithFloat:(currentLevel * 100)];
+ }
+ NSMutableDictionary* batteryData = [NSMutableDictionary dictionaryWithCapacity:2];
+ [batteryData setObject:[NSNumber numberWithBool:isPlugged] forKey:@"isPlugged"];
+ [batteryData setObject:w3cLevel forKey:@"level"];
+ return batteryData;
+}
+
+/* turn on battery monitoring*/
+- (void)start:(CDVInvokedUrlCommand*)command
+{
+ self.callbackId = command.callbackId;
+
+ if ([UIDevice currentDevice].batteryMonitoringEnabled == NO) {
+ [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateBatteryStatus:)
+ name:UIDeviceBatteryStateDidChangeNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateBatteryStatus:)
+ name:UIDeviceBatteryLevelDidChangeNotification object:nil];
+ }
+}
+
+/* turn off battery monitoring */
+- (void)stop:(CDVInvokedUrlCommand*)command
+{
+ // callback one last time to clear the callback function on JS side
+ if (self.callbackId) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getBatteryStatus]];
+ [result setKeepCallbackAsBool:NO];
+ [self.commandDelegate sendPluginResult:result callbackId:self.callbackId];
+ }
+ self.callbackId = nil;
+ [[UIDevice currentDevice] setBatteryMonitoringEnabled:NO];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryStateDidChangeNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryLevelDidChangeNotification object:nil];
+}
+
+- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView
+{
+ self = (CDVBattery*)[super initWithWebView:theWebView];
+ if (self) {
+ self.state = UIDeviceBatteryStateUnknown;
+ self.level = -1.0;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [self stop:nil];
+}
+
+- (void)onReset
+{
+ [self stop:nil];
+}
+
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVCamera.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVCamera.h b/lib/cordova-ios/CordovaLib/Classes/CDVCamera.h
new file mode 100644
index 0000000..e32da7d
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVCamera.h
@@ -0,0 +1,91 @@
+/*
+ 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"
+
+enum CDVDestinationType {
+ DestinationTypeDataUrl = 0,
+ DestinationTypeFileUri,
+ DestinationTypeNativeUri
+};
+typedef NSUInteger CDVDestinationType;
+
+enum CDVEncodingType {
+ EncodingTypeJPEG = 0,
+ EncodingTypePNG
+};
+typedef NSUInteger CDVEncodingType;
+
+enum CDVMediaType {
+ MediaTypePicture = 0,
+ MediaTypeVideo,
+ MediaTypeAll
+};
+typedef NSUInteger CDVMediaType;
+
+@interface CDVCameraPicker : UIImagePickerController
+{}
+
+@property (assign) NSInteger quality;
+@property (copy) NSString* callbackId;
+@property (copy) NSString* postUrl;
+@property (nonatomic) enum CDVDestinationType returnType;
+@property (nonatomic) enum CDVEncodingType encodingType;
+@property (strong) UIPopoverController* popoverController;
+@property (assign) CGSize targetSize;
+@property (assign) bool correctOrientation;
+@property (assign) bool saveToPhotoAlbum;
+@property (assign) bool cropToSize;
+@property (strong) UIWebView* webView;
+@property (assign) BOOL popoverSupported;
+
+@end
+
+// ======================================================================= //
+
+@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate,
+ UINavigationControllerDelegate,
+ UIPopoverControllerDelegate>
+{}
+
+@property (strong) CDVCameraPicker* pickerController;
+
+/*
+ * getPicture
+ *
+ * arguments:
+ * 1: this is the javascript function that will be called with the results, the first parameter passed to the
+ * javascript function is the picture as a Base64 encoded string
+ * 2: this is the javascript function to be called if there was an error
+ * options:
+ * quality: integer between 1 and 100
+ */
+- (void)takePicture:(CDVInvokedUrlCommand*)command;
+- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url;
+- (void)cleanup:(CDVInvokedUrlCommand*)command;
+
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
+- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
+- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize;
+- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize;
+- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage;
+
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVCamera.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVCamera.m b/lib/cordova-ios/CordovaLib/Classes/CDVCamera.m
new file mode 100644
index 0000000..4cf5c82
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVCamera.m
@@ -0,0 +1,536 @@
+/*
+ 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 "CDVCamera.h"
+#import "NSArray+Comparisons.h"
+#import "NSData+Base64.h"
+#import "NSDictionary+Extensions.h"
+#import <MobileCoreServices/UTCoreTypes.h>
+
+#define CDV_PHOTO_PREFIX @"cdv_photo_"
+
+static NSSet* org_apache_cordova_validArrowDirections;
+
+@interface CDVCamera ()
+
+@property (readwrite, assign) BOOL hasPendingOperation;
+
+@end
+
+@implementation CDVCamera
+
++ (void)initialize
+{
+ org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
+}
+
+@synthesize hasPendingOperation, pickerController;
+
+- (BOOL)popoverSupported
+{
+ return (NSClassFromString(@"UIPopoverController") != nil) &&
+ (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
+}
+
+/* takePicture arguments:
+ * INDEX ARGUMENT
+ * 0 quality
+ * 1 destination type
+ * 2 source type
+ * 3 targetWidth
+ * 4 targetHeight
+ * 5 encodingType
+ * 6 mediaType
+ * 7 allowsEdit
+ * 8 correctOrientation
+ * 9 saveToPhotoAlbum
+ */
+- (void)takePicture:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSArray* arguments = command.arguments;
+
+ self.hasPendingOperation = NO;
+
+ NSString* sourceTypeString = [arguments objectAtIndex:2];
+ UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; // default
+ if (sourceTypeString != nil) {
+ sourceType = (UIImagePickerControllerSourceType)[sourceTypeString intValue];
+ }
+
+ bool hasCamera = [UIImagePickerController isSourceTypeAvailable:sourceType];
+ if (!hasCamera) {
+ NSLog(@"Camera.getPicture: source type %d not available.", sourceType);
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no camera available"];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ return;
+ }
+
+ bool allowEdit = [[arguments objectAtIndex:7] boolValue];
+ NSNumber* targetWidth = [arguments objectAtIndex:3];
+ NSNumber* targetHeight = [arguments objectAtIndex:4];
+ NSNumber* mediaValue = [arguments objectAtIndex:6];
+ CDVMediaType mediaType = (mediaValue) ? [mediaValue intValue] : MediaTypePicture;
+
+ CGSize targetSize = CGSizeMake(0, 0);
+ if ((targetWidth != nil) && (targetHeight != nil)) {
+ targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
+ }
+
+ CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
+ self.pickerController = cameraPicker;
+
+ cameraPicker.delegate = self;
+ cameraPicker.sourceType = sourceType;
+ cameraPicker.allowsEditing = allowEdit; // THIS IS ALL IT TAKES FOR CROPPING - jm
+ cameraPicker.callbackId = callbackId;
+ cameraPicker.targetSize = targetSize;
+ cameraPicker.cropToSize = NO;
+ // we need to capture this state for memory warnings that dealloc this object
+ cameraPicker.webView = self.webView;
+ cameraPicker.popoverSupported = [self popoverSupported];
+
+ cameraPicker.correctOrientation = [[arguments objectAtIndex:8] boolValue];
+ cameraPicker.saveToPhotoAlbum = [[arguments objectAtIndex:9] boolValue];
+
+ cameraPicker.encodingType = ([arguments objectAtIndex:5]) ? [[arguments objectAtIndex:5] intValue] : EncodingTypeJPEG;
+
+ cameraPicker.quality = ([arguments objectAtIndex:0]) ? [[arguments objectAtIndex:0] intValue] : 50;
+ cameraPicker.returnType = ([arguments objectAtIndex:1]) ? [[arguments objectAtIndex:1] intValue] : DestinationTypeFileUri;
+
+ if (sourceType == UIImagePickerControllerSourceTypeCamera) {
+ // we only allow taking pictures (no video) in this api
+ cameraPicker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil];
+ } else if (mediaType == MediaTypeAll) {
+ cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType];
+ } else {
+ NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie:kUTTypeImage), nil];
+ cameraPicker.mediaTypes = mediaArray;
+ }
+
+ if ([self popoverSupported] && (sourceType != UIImagePickerControllerSourceTypeCamera)) {
+ if (cameraPicker.popoverController == nil) {
+ cameraPicker.popoverController = [[NSClassFromString (@"UIPopoverController")alloc] initWithContentViewController:cameraPicker];
+ }
+ int x = 0;
+ int y = 32;
+ int width = 320;
+ int height = 480;
+ UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
+ NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil];
+ if (options) {
+ x = [options integerValueForKey:@"x" defaultValue:0];
+ y = [options integerValueForKey:@"y" defaultValue:32];
+ width = [options integerValueForKey:@"width" defaultValue:320];
+ height = [options integerValueForKey:@"height" defaultValue:480];
+ arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
+ if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) {
+ arrowDirection = UIPopoverArrowDirectionAny;
+ }
+ }
+
+ cameraPicker.popoverController.delegate = self;
+ [cameraPicker.popoverController presentPopoverFromRect:CGRectMake(x, y, width, height)
+ inView:[self.webView superview]
+ permittedArrowDirections:arrowDirection
+ animated:YES];
+ } else {
+ if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) {
+ [self.viewController presentViewController:cameraPicker animated:YES completion:nil];
+ } else {
+ [self.viewController presentModalViewController:cameraPicker animated:YES];
+ }
+ }
+ self.hasPendingOperation = YES;
+}
+
+- (void)cleanup:(CDVInvokedUrlCommand*)command
+{
+ // empty the tmp directory
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* err = nil;
+ BOOL hasErrors = NO;
+
+ // clear contents of NSTemporaryDirectory
+ NSString* tempDirectoryPath = NSTemporaryDirectory();
+ NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
+ NSString* fileName = nil;
+ BOOL result;
+
+ while ((fileName = [directoryEnumerator nextObject])) {
+ // only delete the files we created
+ if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) {
+ continue;
+ }
+ NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
+ result = [fileMgr removeItemAtPath:filePath error:&err];
+ if (!result && err) {
+ NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
+ hasErrors = YES;
+ }
+ }
+
+ CDVPluginResult* pluginResult;
+ if (hasErrors) {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."];
+ } else {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ }
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+}
+
+- (void)popoverControllerDidDismissPopover:(id)popoverController
+{
+ // [ self imagePickerControllerDidCancel:self.pickerController ]; '
+ UIPopoverController* pc = (UIPopoverController*)popoverController;
+
+ [pc dismissPopoverAnimated:YES];
+ pc.delegate = nil;
+ if (self.pickerController && self.pickerController.callbackId && self.pickerController.popoverController) {
+ self.pickerController.popoverController = nil;
+ NSString* callbackId = self.pickerController.callbackId;
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+ self.hasPendingOperation = NO;
+}
+
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
+{
+ CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
+
+ if (cameraPicker.popoverSupported && (cameraPicker.popoverController != nil)) {
+ [cameraPicker.popoverController dismissPopoverAnimated:YES];
+ cameraPicker.popoverController.delegate = nil;
+ cameraPicker.popoverController = nil;
+ } else {
+ if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) {
+ [[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES];
+ } else {
+ [[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES];
+ }
+ }
+
+ CDVPluginResult* result = nil;
+
+ NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
+ // IMAGE TYPE
+ if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
+ // get the image
+ UIImage* image = nil;
+ if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
+ image = [info objectForKey:UIImagePickerControllerEditedImage];
+ } else {
+ image = [info objectForKey:UIImagePickerControllerOriginalImage];
+ }
+
+ if (cameraPicker.saveToPhotoAlbum) {
+ UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
+ }
+
+ if (cameraPicker.correctOrientation) {
+ image = [self imageCorrectedForCaptureOrientation:image];
+ }
+
+ UIImage* scaledImage = nil;
+
+ if ((cameraPicker.targetSize.width > 0) && (cameraPicker.targetSize.height > 0)) {
+ // if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
+ if (cameraPicker.cropToSize) {
+ scaledImage = [self imageByScalingAndCroppingForSize:image toSize:cameraPicker.targetSize];
+ } else {
+ scaledImage = [self imageByScalingNotCroppingForSize:image toSize:cameraPicker.targetSize];
+ }
+ }
+
+ NSData* data = nil;
+
+ if (cameraPicker.encodingType == EncodingTypePNG) {
+ data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage);
+ } else {
+ data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f);
+ }
+
+ if (cameraPicker.returnType == DestinationTypeFileUri) {
+ // write to temp directory and return URI
+ // get the temp directory path
+ NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath];
+ NSError* err = nil;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe
+ // generate unique file name
+ NSString* filePath;
+
+ int i = 1;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, cameraPicker.encodingType == EncodingTypePNG ? @"png":@"jpg"];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ // save file
+ if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
+ }
+ } else if (cameraPicker.returnType == DestinationTypeNativeUri) {
+ NSString* nativeUri = [(NSURL*)[info objectForKey:UIImagePickerControllerReferenceURL] absoluteString];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[data base64EncodedString]];
+ }
+ }
+ // NOT IMAGE TYPE (MOVIE)
+ else {
+ NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
+ }
+
+ if (result) {
+ [self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
+ }
+
+ self.hasPendingOperation = NO;
+ self.pickerController = nil;
+}
+
+// older api calls newer didFinishPickingMediaWithInfo
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
+{
+ NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
+
+ [self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo];
+}
+
+- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
+{
+ CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
+
+ if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) {
+ [[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES];
+ } else {
+ [[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES];
+ }
+ // popoverControllerDidDismissPopover:(id)popoverController is called if popover is cancelled
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
+ [self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
+
+ self.hasPendingOperation = NO;
+ self.pickerController = nil;
+}
+
+- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize
+{
+ UIImage* sourceImage = anImage;
+ UIImage* newImage = nil;
+ CGSize imageSize = sourceImage.size;
+ CGFloat width = imageSize.width;
+ CGFloat height = imageSize.height;
+ CGFloat targetWidth = targetSize.width;
+ CGFloat targetHeight = targetSize.height;
+ CGFloat scaleFactor = 0.0;
+ CGFloat scaledWidth = targetWidth;
+ CGFloat scaledHeight = targetHeight;
+ CGPoint thumbnailPoint = CGPointMake(0.0, 0.0);
+
+ if (CGSizeEqualToSize(imageSize, targetSize) == NO) {
+ CGFloat widthFactor = targetWidth / width;
+ CGFloat heightFactor = targetHeight / height;
+
+ if (widthFactor > heightFactor) {
+ scaleFactor = widthFactor; // scale to fit height
+ } else {
+ scaleFactor = heightFactor; // scale to fit width
+ }
+ scaledWidth = width * scaleFactor;
+ scaledHeight = height * scaleFactor;
+
+ // center the image
+ if (widthFactor > heightFactor) {
+ thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
+ } else if (widthFactor < heightFactor) {
+ thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
+ }
+ }
+
+ UIGraphicsBeginImageContext(targetSize); // this will crop
+
+ CGRect thumbnailRect = CGRectZero;
+ thumbnailRect.origin = thumbnailPoint;
+ thumbnailRect.size.width = scaledWidth;
+ thumbnailRect.size.height = scaledHeight;
+
+ [sourceImage drawInRect:thumbnailRect];
+
+ newImage = UIGraphicsGetImageFromCurrentImageContext();
+ if (newImage == nil) {
+ NSLog(@"could not scale image");
+ }
+
+ // pop the context to get back to the default
+ UIGraphicsEndImageContext();
+ return newImage;
+}
+
+- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage
+{
+ float rotation_radians = 0;
+ bool perpendicular = false;
+
+ switch ([anImage imageOrientation]) {
+ case UIImageOrientationUp :
+ rotation_radians = 0.0;
+ break;
+
+ case UIImageOrientationDown :
+ rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math
+ break;
+
+ case UIImageOrientationRight:
+ rotation_radians = M_PI_2;
+ perpendicular = true;
+ break;
+
+ case UIImageOrientationLeft:
+ rotation_radians = -M_PI_2;
+ perpendicular = true;
+ break;
+
+ default:
+ break;
+ }
+
+ UIGraphicsBeginImageContext(CGSizeMake(anImage.size.width, anImage.size.height));
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ // Rotate around the center point
+ CGContextTranslateCTM(context, anImage.size.width / 2, anImage.size.height / 2);
+ CGContextRotateCTM(context, rotation_radians);
+
+ CGContextScaleCTM(context, 1.0, -1.0);
+ float width = perpendicular ? anImage.size.height : anImage.size.width;
+ float height = perpendicular ? anImage.size.width : anImage.size.height;
+ CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [anImage CGImage]);
+
+ // Move the origin back since the rotation might've change it (if its 90 degrees)
+ if (perpendicular) {
+ CGContextTranslateCTM(context, -anImage.size.height / 2, -anImage.size.width / 2);
+ }
+
+ UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ return newImage;
+}
+
+- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize
+{
+ UIImage* sourceImage = anImage;
+ UIImage* newImage = nil;
+ CGSize imageSize = sourceImage.size;
+ CGFloat width = imageSize.width;
+ CGFloat height = imageSize.height;
+ CGFloat targetWidth = frameSize.width;
+ CGFloat targetHeight = frameSize.height;
+ CGFloat scaleFactor = 0.0;
+ CGSize scaledSize = frameSize;
+
+ if (CGSizeEqualToSize(imageSize, frameSize) == NO) {
+ CGFloat widthFactor = targetWidth / width;
+ CGFloat heightFactor = targetHeight / height;
+
+ // opposite comparison to imageByScalingAndCroppingForSize in order to contain the image within the given bounds
+ if (widthFactor > heightFactor) {
+ scaleFactor = heightFactor; // scale to fit height
+ } else {
+ scaleFactor = widthFactor; // scale to fit width
+ }
+ scaledSize = CGSizeMake(MIN(width * scaleFactor, targetWidth), MIN(height * scaleFactor, targetHeight));
+ }
+
+ UIGraphicsBeginImageContext(scaledSize); // this will resize
+
+ [sourceImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
+
+ newImage = UIGraphicsGetImageFromCurrentImageContext();
+ if (newImage == nil) {
+ NSLog(@"could not scale image");
+ }
+
+ // pop the context to get back to the default
+ UIGraphicsEndImageContext();
+ return newImage;
+}
+
+- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url
+{
+ self.hasPendingOperation = YES;
+
+ NSString* boundary = @"----BOUNDARY_IS_I";
+
+ NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
+ [req setHTTPMethod:@"POST"];
+
+ NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
+ [req setValue:contentType forHTTPHeaderField:@"Content-type"];
+
+ NSData* imageData = UIImagePNGRepresentation(anImage);
+
+ // adding the body
+ NSMutableData* postBody = [NSMutableData data];
+
+ // first parameter an image
+ [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
+ [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"upload\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]];
+ [postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
+ [postBody appendData:imageData];
+
+ // // second parameter information
+ // [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
+ // [postBody appendData:[@"Content-Disposition: form-data; name=\"some_other_name\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
+ // [postBody appendData:[@"some_other_value" dataUsingEncoding:NSUTF8StringEncoding]];
+ // [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r \n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
+
+ [req setHTTPBody:postBody];
+
+ NSURLResponse* response;
+ NSError* error;
+ [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
+
+ // NSData* result = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
+ // NSString * resultStr = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease];
+
+ self.hasPendingOperation = NO;
+}
+
+@end
+
+@implementation CDVCameraPicker
+
+@synthesize quality, postUrl;
+@synthesize returnType;
+@synthesize callbackId;
+@synthesize popoverController;
+@synthesize targetSize;
+@synthesize correctOrientation;
+@synthesize saveToPhotoAlbum;
+@synthesize encodingType;
+@synthesize cropToSize;
+@synthesize webView;
+@synthesize popoverSupported;
+
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVCapture.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVCapture.h b/lib/cordova-ios/CordovaLib/Classes/CDVCapture.h
new file mode 100644
index 0000000..afb82b4
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVCapture.h
@@ -0,0 +1,118 @@
+/*
+ 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 <MobileCoreServices/MobileCoreServices.h>
+#import <AVFoundation/AVFoundation.h>
+#import "CDVPlugin.h"
+#import "CDVFile.h"
+
+enum CDVCaptureError {
+ CAPTURE_INTERNAL_ERR = 0,
+ CAPTURE_APPLICATION_BUSY = 1,
+ CAPTURE_INVALID_ARGUMENT = 2,
+ CAPTURE_NO_MEDIA_FILES = 3,
+ CAPTURE_NOT_SUPPORTED = 20
+};
+typedef NSUInteger CDVCaptureError;
+
+@interface CDVImagePicker : UIImagePickerController
+{
+ NSString* callbackid;
+ NSInteger quality;
+ NSString* mimeType;
+}
+@property (assign) NSInteger quality;
+@property (copy) NSString* callbackId;
+@property (copy) NSString* mimeType;
+
+@end
+
+@interface CDVCapture : CDVPlugin <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
+{
+ CDVImagePicker* pickerController;
+ BOOL inUse;
+}
+@property BOOL inUse;
+- (void)captureAudio:(CDVInvokedUrlCommand*)command;
+- (void)captureImage:(CDVInvokedUrlCommand*)command;
+- (CDVPluginResult*)processImage:(UIImage*)image type:(NSString*)mimeType forCallbackId:(NSString*)callbackId;
+- (void)captureVideo:(CDVInvokedUrlCommand*)command;
+- (CDVPluginResult*)processVideo:(NSString*)moviePath forCallbackId:(NSString*)callbackId;
+- (void)getMediaModes:(CDVInvokedUrlCommand*)command;
+- (void)getFormatData:(CDVInvokedUrlCommand*)command;
+- (NSDictionary*)getMediaDictionaryFromPath:(NSString*)fullPath ofType:(NSString*)type;
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
+- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
+
+@end
+
+@interface CDVAudioNavigationController : UINavigationController
+
+@end
+
+/* AudioRecorderViewController is used to create a simple view for audio recording.
+ * It is created from [Capture captureAudio]. It creates a very simple interface for
+ * recording by presenting just a record/stop button and a Done button to close the view.
+ * The recording time is displayed and recording for a specified duration is supported. When duration
+ * is specified there is no UI to the user - recording just stops when the specified
+ * duration is reached. The UI has been minimized to avoid localization.
+ */
+@interface CDVAudioRecorderViewController : UIViewController <AVAudioRecorderDelegate>
+{
+ CDVCaptureError errorCode;
+ NSString* callbackId;
+ NSNumber* duration;
+ CDVCapture* captureCommand;
+ UIBarButtonItem* doneButton;
+ UIView* recordingView;
+ UIButton* recordButton;
+ UIImage* recordImage;
+ UIImage* stopRecordImage;
+ UILabel* timerLabel;
+ AVAudioRecorder* avRecorder;
+ AVAudioSession* avSession;
+ CDVPluginResult* pluginResult;
+ NSTimer* timer;
+ BOOL isTimed;
+}
+@property (nonatomic) CDVCaptureError errorCode;
+@property (nonatomic, copy) NSString* callbackId;
+@property (nonatomic, copy) NSNumber* duration;
+@property (nonatomic, strong) CDVCapture* captureCommand;
+@property (nonatomic, strong) UIBarButtonItem* doneButton;
+@property (nonatomic, strong) UIView* recordingView;
+@property (nonatomic, strong) UIButton* recordButton;
+@property (nonatomic, strong) UIImage* recordImage;
+@property (nonatomic, strong) UIImage* stopRecordImage;
+@property (nonatomic, strong) UILabel* timerLabel;
+@property (nonatomic, strong) AVAudioRecorder* avRecorder;
+@property (nonatomic, strong) AVAudioSession* avSession;
+@property (nonatomic, strong) CDVPluginResult* pluginResult;
+@property (nonatomic, strong) NSTimer* timer;
+@property (nonatomic) BOOL isTimed;
+
+- (id)initWithCommand:(CDVPlugin*)theCommand duration:(NSNumber*)theDuration callbackId:(NSString*)theCallbackId;
+- (void)processButton:(id)sender;
+- (void)stopRecordingCleanup;
+- (void)dismissAudioView:(id)sender;
+- (NSString*)formatTime:(int)interval;
+- (void)updateTime;
+@end
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-ios/CordovaLib/Classes/CDVCapture.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVCapture.m b/lib/cordova-ios/CordovaLib/Classes/CDVCapture.m
new file mode 100644
index 0000000..95c3f17
--- /dev/null
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVCapture.m
@@ -0,0 +1,850 @@
+/*
+ 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 "CDVCapture.h"
+#import "CDVJSON.h"
+#import "CDVAvailability.h"
+#import "CDVViewController.h"
+
+#define kW3CMediaFormatHeight @"height"
+#define kW3CMediaFormatWidth @"width"
+#define kW3CMediaFormatCodecs @"codecs"
+#define kW3CMediaFormatBitrate @"bitrate"
+#define kW3CMediaFormatDuration @"duration"
+#define kW3CMediaModeType @"type"
+
+@implementation CDVImagePicker
+
+@synthesize quality;
+@synthesize callbackId;
+@synthesize mimeType;
+
+- (uint64_t)accessibilityTraits
+{
+ NSString* systemVersion = [[UIDevice currentDevice] systemVersion];
+
+ if (([systemVersion compare:@"4.0" options:NSNumericSearch] != NSOrderedAscending)) { // this means system version is not less than 4.0
+ return UIAccessibilityTraitStartsMediaSession;
+ }
+
+ return UIAccessibilityTraitNone;
+}
+
+@end
+
+@implementation CDVCapture
+@synthesize inUse;
+
+- (id)initWithWebView:(UIWebView*)theWebView
+{
+ self = (CDVCapture*)[super initWithWebView:theWebView];
+ if (self) {
+ self.inUse = NO;
+ }
+ return self;
+}
+
+- (void)captureAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSDictionary* options = [command.arguments objectAtIndex:0];
+
+ if ([options isKindOfClass:[NSNull class]]) {
+ options = [NSDictionary dictionary];
+ }
+
+ NSNumber* duration = [options objectForKey:@"duration"];
+ // the default value of duration is 0 so use nil (no duration) if default value
+ if (duration) {
+ duration = [duration doubleValue] == 0 ? nil : duration;
+ }
+ CDVPluginResult* result = nil;
+
+ if (NSClassFromString(@"AVAudioRecorder") == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NOT_SUPPORTED];
+ } else if (self.inUse == YES) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_APPLICATION_BUSY];
+ } else {
+ // all the work occurs here
+ CDVAudioRecorderViewController* audioViewController = [[CDVAudioRecorderViewController alloc] initWithCommand:self duration:duration callbackId:callbackId];
+
+ // Now create a nav controller and display the view...
+ CDVAudioNavigationController* navController = [[CDVAudioNavigationController alloc] initWithRootViewController:audioViewController];
+
+ self.inUse = YES;
+
+ if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) {
+ [self.viewController presentViewController:navController animated:YES completion:nil];
+ } else {
+ [self.viewController presentModalViewController:navController animated:YES];
+ }
+ }
+
+ if (result) {
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+}
+
+- (void)captureImage:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSDictionary* options = [command.arguments objectAtIndex:0];
+
+ if ([options isKindOfClass:[NSNull class]]) {
+ options = [NSDictionary dictionary];
+ }
+ NSString* mode = [options objectForKey:@"mode"];
+
+ // options could contain limit and mode neither of which are supported at this time
+ // taking more than one picture (limit) is only supported if provide own controls via cameraOverlayView property
+ // can support mode in OS
+
+ if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
+ NSLog(@"Capture.imageCapture: camera not available.");
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NOT_SUPPORTED];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ } else {
+ if (pickerController == nil) {
+ pickerController = [[CDVImagePicker alloc] init];
+ }
+
+ pickerController.delegate = self;
+ pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
+ pickerController.allowsEditing = NO;
+ if ([pickerController respondsToSelector:@selector(mediaTypes)]) {
+ // iOS 3.0
+ pickerController.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil];
+ }
+
+ /*if ([pickerController respondsToSelector:@selector(cameraCaptureMode)]){
+ // iOS 4.0
+ pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
+ pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear;
+ pickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
+ }*/
+ // CDVImagePicker specific property
+ pickerController.callbackId = callbackId;
+ pickerController.mimeType = mode;
+
+ if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) {
+ [self.viewController presentViewController:pickerController animated:YES completion:nil];
+ } else {
+ [self.viewController presentModalViewController:pickerController animated:YES];
+ }
+ }
+}
+
+/* Process a still image from the camera.
+ * IN:
+ * UIImage* image - the UIImage data returned from the camera
+ * NSString* callbackId
+ */
+- (CDVPluginResult*)processImage:(UIImage*)image type:(NSString*)mimeType forCallbackId:(NSString*)callbackId
+{
+ CDVPluginResult* result = nil;
+
+ // save the image to photo album
+ UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
+
+ NSData* data = nil;
+ if (mimeType && [mimeType isEqualToString:@"image/png"]) {
+ data = UIImagePNGRepresentation(image);
+ } else {
+ data = UIImageJPEGRepresentation(image, 0.5);
+ }
+
+ // write to temp directory and return URI
+ NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory
+ NSError* err = nil;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ // generate unique file name
+ NSString* filePath;
+ int i = 1;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/photo_%03d.jpg", docsPath, i++];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageToErrorObject:CAPTURE_INTERNAL_ERR];
+ if (err) {
+ NSLog(@"Error saving image: %@", [err localizedDescription]);
+ }
+ } else {
+ // create MediaFile object
+
+ NSDictionary* fileDict = [self getMediaDictionaryFromPath:filePath ofType:mimeType];
+ NSArray* fileArray = [NSArray arrayWithObject:fileDict];
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray];
+ }
+
+ return result;
+}
+
+- (void)captureVideo:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSDictionary* options = [command.arguments objectAtIndex:0];
+
+ if ([options isKindOfClass:[NSNull class]]) {
+ options = [NSDictionary dictionary];
+ }
+
+ // options could contain limit, duration and mode, only duration is supported (but is not due to apple bug)
+ // taking more than one video (limit) is only supported if provide own controls via cameraOverlayView property
+ // NSNumber* duration = [options objectForKey:@"duration"];
+ NSString* mediaType = nil;
+
+ if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
+ // there is a camera, it is available, make sure it can do movies
+ pickerController = [[CDVImagePicker alloc] init];
+
+ NSArray* types = nil;
+ if ([UIImagePickerController respondsToSelector:@selector(availableMediaTypesForSourceType:)]) {
+ types = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
+ // NSLog(@"MediaTypes: %@", [types description]);
+
+ if ([types containsObject:(NSString*)kUTTypeMovie]) {
+ mediaType = (NSString*)kUTTypeMovie;
+ } else if ([types containsObject:(NSString*)kUTTypeVideo]) {
+ mediaType = (NSString*)kUTTypeVideo;
+ }
+ }
+ }
+ if (!mediaType) {
+ // don't have video camera return error
+ NSLog(@"Capture.captureVideo: video mode not available.");
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NOT_SUPPORTED];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ pickerController = nil;
+ } else {
+ pickerController.delegate = self;
+ pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
+ pickerController.allowsEditing = NO;
+ // iOS 3.0
+ pickerController.mediaTypes = [NSArray arrayWithObjects:mediaType, nil];
+
+ /*if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]){
+ if (duration) {
+ pickerController.videoMaximumDuration = [duration doubleValue];
+ }
+ //NSLog(@"pickerController.videoMaximumDuration = %f", pickerController.videoMaximumDuration);
+ }*/
+
+ // iOS 4.0
+ if ([pickerController respondsToSelector:@selector(cameraCaptureMode)]) {
+ pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
+ // pickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
+ // pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear;
+ // pickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
+ }
+ // CDVImagePicker specific property
+ pickerController.callbackId = callbackId;
+
+ if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) {
+ [self.viewController presentViewController:pickerController animated:YES completion:nil];
+ } else {
+ [self.viewController presentModalViewController:pickerController animated:YES];
+ }
+ }
+}
+
+- (CDVPluginResult*)processVideo:(NSString*)moviePath forCallbackId:(NSString*)callbackId
+{
+ // save the movie to photo album (only avail as of iOS 3.1)
+
+ /* don't need, it should automatically get saved
+ NSLog(@"can save %@: %d ?", moviePath, UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(moviePath));
+ if (&UIVideoAtPathIsCompatibleWithSavedPhotosAlbum != NULL && UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(moviePath) == YES) {
+ NSLog(@"try to save movie");
+ UISaveVideoAtPathToSavedPhotosAlbum(moviePath, nil, nil, nil);
+ NSLog(@"finished saving movie");
+ }*/
+ // create MediaFile object
+ NSDictionary* fileDict = [self getMediaDictionaryFromPath:moviePath ofType:nil];
+ NSArray* fileArray = [NSArray arrayWithObject:fileDict];
+
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray];
+}
+
+- (void)getMediaModes:(CDVInvokedUrlCommand*)command
+{
+ // NSString* callbackId = [arguments objectAtIndex:0];
+ // NSMutableDictionary* imageModes = nil;
+ NSArray* imageArray = nil;
+ NSArray* movieArray = nil;
+ NSArray* audioArray = nil;
+
+ if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
+ // there is a camera, find the modes
+ // can get image/jpeg or image/png from camera
+
+ /* can't find a way to get the default height and width and other info
+ * for images/movies taken with UIImagePickerController
+ */
+ NSDictionary* jpg = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:0], kW3CMediaFormatHeight,
+ [NSNumber numberWithInt:0], kW3CMediaFormatWidth,
+ @"image/jpeg", kW3CMediaModeType,
+ nil];
+ NSDictionary* png = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:0], kW3CMediaFormatHeight,
+ [NSNumber numberWithInt:0], kW3CMediaFormatWidth,
+ @"image/png", kW3CMediaModeType,
+ nil];
+ imageArray = [NSArray arrayWithObjects:jpg, png, nil];
+
+ if ([UIImagePickerController respondsToSelector:@selector(availableMediaTypesForSourceType:)]) {
+ NSArray* types = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
+
+ if ([types containsObject:(NSString*)kUTTypeMovie]) {
+ NSDictionary* mov = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:0], kW3CMediaFormatHeight,
+ [NSNumber numberWithInt:0], kW3CMediaFormatWidth,
+ @"video/quicktime", kW3CMediaModeType,
+ nil];
+ movieArray = [NSArray arrayWithObject:mov];
+ }
+ }
+ }
+ NSDictionary* modes = [NSDictionary dictionaryWithObjectsAndKeys:
+ imageArray ? (NSObject*) imageArray:[NSNull null], @"image",
+ movieArray ? (NSObject*) movieArray:[NSNull null], @"video",
+ audioArray ? (NSObject*) audioArray:[NSNull null], @"audio",
+ nil];
+ NSString* jsString = [NSString stringWithFormat:@"navigator.device.capture.setSupportedModes(%@);", [modes JSONString]];
+ [self.commandDelegate evalJs:jsString];
+}
+
+- (void)getFormatData:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ // existence of fullPath checked on JS side
+ NSString* fullPath = [command.arguments objectAtIndex:0];
+ // mimeType could be null
+ NSString* mimeType = nil;
+
+ if ([command.arguments count] > 1) {
+ mimeType = [command.arguments objectAtIndex:1];
+ }
+ BOOL bError = NO;
+ CDVCaptureError errorCode = CAPTURE_INTERNAL_ERR;
+ CDVPluginResult* result = nil;
+
+ if (!mimeType || [mimeType isKindOfClass:[NSNull class]]) {
+ // try to determine mime type if not provided
+ id command = [self.commandDelegate getCommandInstance:@"File"];
+ bError = !([command isKindOfClass:[CDVFile class]]);
+ if (!bError) {
+ CDVFile* cdvFile = (CDVFile*)command;
+ mimeType = [cdvFile getMimeTypeFromPath:fullPath];
+ if (!mimeType) {
+ // can't do much without mimeType, return error
+ bError = YES;
+ errorCode = CAPTURE_INVALID_ARGUMENT;
+ }
+ }
+ }
+ if (!bError) {
+ // create and initialize return dictionary
+ NSMutableDictionary* formatData = [NSMutableDictionary dictionaryWithCapacity:5];
+ [formatData setObject:[NSNull null] forKey:kW3CMediaFormatCodecs];
+ [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatBitrate];
+ [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatHeight];
+ [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatWidth];
+ [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatDuration];
+
+ if ([mimeType rangeOfString:@"image/"].location != NSNotFound) {
+ UIImage* image = [UIImage imageWithContentsOfFile:fullPath];
+ if (image) {
+ CGSize imgSize = [image size];
+ [formatData setObject:[NSNumber numberWithInteger:imgSize.width] forKey:kW3CMediaFormatWidth];
+ [formatData setObject:[NSNumber numberWithInteger:imgSize.height] forKey:kW3CMediaFormatHeight];
+ }
+ } else if (([mimeType rangeOfString:@"video/"].location != NSNotFound) && (NSClassFromString(@"AVURLAsset") != nil)) {
+ NSURL* movieURL = [NSURL fileURLWithPath:fullPath];
+ AVURLAsset* movieAsset = [[AVURLAsset alloc] initWithURL:movieURL options:nil];
+ CMTime duration = [movieAsset duration];
+ [formatData setObject:[NSNumber numberWithFloat:CMTimeGetSeconds(duration)] forKey:kW3CMediaFormatDuration];
+
+ NSArray* allVideoTracks = [movieAsset tracksWithMediaType:AVMediaTypeVideo];
+ if ([allVideoTracks count] > 0) {
+ AVAssetTrack* track = [[movieAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
+ CGSize size = [track naturalSize];
+
+ [formatData setObject:[NSNumber numberWithFloat:size.height] forKey:kW3CMediaFormatHeight];
+ [formatData setObject:[NSNumber numberWithFloat:size.width] forKey:kW3CMediaFormatWidth];
+ // not sure how to get codecs or bitrate???
+ // AVMetadataItem
+ // AudioFile
+ } else {
+ NSLog(@"No video tracks found for %@", fullPath);
+ }
+ } else if ([mimeType rangeOfString:@"audio/"].location != NSNotFound) {
+ if (NSClassFromString(@"AVAudioPlayer") != nil) {
+ NSURL* fileURL = [NSURL fileURLWithPath:fullPath];
+ NSError* err = nil;
+
+ AVAudioPlayer* avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&err];
+ if (!err) {
+ // get the data
+ [formatData setObject:[NSNumber numberWithDouble:[avPlayer duration]] forKey:kW3CMediaFormatDuration];
+ if ([avPlayer respondsToSelector:@selector(settings)]) {
+ NSDictionary* info = [avPlayer settings];
+ NSNumber* bitRate = [info objectForKey:AVEncoderBitRateKey];
+ if (bitRate) {
+ [formatData setObject:bitRate forKey:kW3CMediaFormatBitrate];
+ }
+ }
+ } // else leave data init'ed to 0
+ }
+ }
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:formatData];
+ // NSLog(@"getFormatData: %@", [formatData description]);
+ }
+ if (bError) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errorCode];
+ }
+ if (result) {
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+}
+
+- (NSDictionary*)getMediaDictionaryFromPath:(NSString*)fullPath ofType:(NSString*)type
+{
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSMutableDictionary* fileDict = [NSMutableDictionary dictionaryWithCapacity:5];
+
+ [fileDict setObject:[fullPath lastPathComponent] forKey:@"name"];
+ [fileDict setObject:fullPath forKey:@"fullPath"];
+ // determine type
+ if (!type) {
+ id command = [self.commandDelegate getCommandInstance:@"File"];
+ if ([command isKindOfClass:[CDVFile class]]) {
+ CDVFile* cdvFile = (CDVFile*)command;
+ NSString* mimeType = [cdvFile getMimeTypeFromPath:fullPath];
+ [fileDict setObject:(mimeType != nil ? (NSObject*)mimeType:[NSNull null]) forKey:@"type"];
+ }
+ }
+ NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:nil];
+ [fileDict setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"];
+ NSDate* modDate = [fileAttrs fileModificationDate];
+ NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000];
+ [fileDict setObject:msDate forKey:@"lastModifiedDate"];
+
+ return fileDict;
+}
+
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
+{
+ // older api calls new one
+ [self imagePickerController:picker didFinishPickingMediaWithInfo:editingInfo];
+}
+
+/* Called when image/movie is finished recording.
+ * Calls success or error code as appropriate
+ * if successful, result contains an array (with just one entry since can only get one image unless build own camera UI) of MediaFile object representing the image
+ * name
+ * fullPath
+ * type
+ * lastModifiedDate
+ * size
+ */
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
+{
+ CDVImagePicker* cameraPicker = (CDVImagePicker*)picker;
+ NSString* callbackId = cameraPicker.callbackId;
+
+ if ([picker respondsToSelector:@selector(presentingViewController)]) {
+ [[picker presentingViewController] dismissModalViewControllerAnimated:YES];
+ } else {
+ [[picker parentViewController] dismissModalViewControllerAnimated:YES];
+ }
+
+ CDVPluginResult* result = nil;
+
+ UIImage* image = nil;
+ NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
+ if (!mediaType || [mediaType isEqualToString:(NSString*)kUTTypeImage]) {
+ // mediaType is nil then only option is UIImagePickerControllerOriginalImage
+ if ([UIImagePickerController respondsToSelector:@selector(allowsEditing)] &&
+ (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage])) {
+ image = [info objectForKey:UIImagePickerControllerEditedImage];
+ } else {
+ image = [info objectForKey:UIImagePickerControllerOriginalImage];
+ }
+ }
+ if (image != nil) {
+ // mediaType was image
+ result = [self processImage:image type:cameraPicker.mimeType forCallbackId:callbackId];
+ } else if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]) {
+ // process video
+ NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] path];
+ if (moviePath) {
+ result = [self processVideo:moviePath forCallbackId:callbackId];
+ }
+ }
+ if (!result) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_INTERNAL_ERR];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ pickerController = nil;
+}
+
+- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
+{
+ CDVImagePicker* cameraPicker = (CDVImagePicker*)picker;
+ NSString* callbackId = cameraPicker.callbackId;
+
+ if ([picker respondsToSelector:@selector(presentingViewController)]) {
+ [[picker presentingViewController] dismissModalViewControllerAnimated:YES];
+ } else {
+ [[picker parentViewController] dismissModalViewControllerAnimated:YES];
+ }
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NO_MEDIA_FILES];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ pickerController = nil;
+}
+
+@end
+
+@implementation CDVAudioNavigationController
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000
+ - (NSUInteger)supportedInterfaceOrientations
+ {
+ // delegate to CVDAudioRecorderViewController
+ return [self.topViewController supportedInterfaceOrientations];
+ }
+
+#endif
+
+@end
+
+@implementation CDVAudioRecorderViewController
+@synthesize errorCode, callbackId, duration, captureCommand, doneButton, recordingView, recordButton, recordImage, stopRecordImage, timerLabel, avRecorder, avSession, pluginResult, timer, isTimed;
+
+- (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 user wants iPhone only app to run on iPad they must remove *~ipad.* images from capture.bundle
+ if (isLessThaniOS4) {
+ NSString* iPadResource = [NSString stringWithFormat:@"%@~ipad.png", resource];
+ if (CDV_IsIPad() && [UIImage imageNamed:iPadResource]) {
+ return iPadResource;
+ } else {
+ return [NSString stringWithFormat:@"%@.png", resource];
+ }
+ }
+
+ return resource;
+}
+
+- (id)initWithCommand:(CDVCapture*)theCommand duration:(NSNumber*)theDuration callbackId:(NSString*)theCallbackId
+{
+ if ((self = [super init])) {
+ self.captureCommand = theCommand;
+ self.duration = theDuration;
+ self.callbackId = theCallbackId;
+ self.errorCode = CAPTURE_NO_MEDIA_FILES;
+ self.isTimed = self.duration != nil;
+
+ return self;
+ }
+
+ return nil;
+}
+
+- (void)loadView
+{
+ // create view and display
+ CGRect viewRect = [[UIScreen mainScreen] applicationFrame];
+ UIView* tmp = [[UIView alloc] initWithFrame:viewRect];
+
+ // make backgrounds
+ NSString* microphoneResource = @"Capture.bundle/microphone";
+
+ if (CDV_IsIPhone5()) {
+ microphoneResource = @"Capture.bundle/microphone-568h";
+ }
+
+ UIImage* microphone = [UIImage imageNamed:[self resolveImageResource:microphoneResource]];
+ UIView* microphoneView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, microphone.size.height)];
+ [microphoneView setBackgroundColor:[UIColor colorWithPatternImage:microphone]];
+ [microphoneView setUserInteractionEnabled:NO];
+ [microphoneView setIsAccessibilityElement:NO];
+ [tmp addSubview:microphoneView];
+
+ // add bottom bar view
+ UIImage* grayBkg = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/controls_bg"]];
+ UIView* controls = [[UIView alloc] initWithFrame:CGRectMake(0, microphone.size.height, viewRect.size.width, grayBkg.size.height)];
+ [controls setBackgroundColor:[UIColor colorWithPatternImage:grayBkg]];
+ [controls setUserInteractionEnabled:NO];
+ [controls setIsAccessibilityElement:NO];
+ [tmp addSubview:controls];
+
+ // make red recording background view
+ UIImage* recordingBkg = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/recording_bg"]];
+ UIColor* background = [UIColor colorWithPatternImage:recordingBkg];
+ self.recordingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, recordingBkg.size.height)];
+ [self.recordingView setBackgroundColor:background];
+ [self.recordingView setHidden:YES];
+ [self.recordingView setUserInteractionEnabled:NO];
+ [self.recordingView setIsAccessibilityElement:NO];
+ [tmp addSubview:self.recordingView];
+
+ // add label
+ self.timerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, recordingBkg.size.height)];
+ // timerLabel.autoresizingMask = reSizeMask;
+ [self.timerLabel setBackgroundColor:[UIColor clearColor]];
+ [self.timerLabel setTextColor:[UIColor whiteColor]];
+ [self.timerLabel setTextAlignment:UITextAlignmentCenter];
+ [self.timerLabel setText:@"0:00"];
+ [self.timerLabel setAccessibilityHint:NSLocalizedString(@"recorded time in minutes and seconds", nil)];
+ self.timerLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
+ self.timerLabel.accessibilityTraits &= ~UIAccessibilityTraitStaticText;
+ [tmp addSubview:self.timerLabel];
+
+ // Add record button
+
+ self.recordImage = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/record_button"]];
+ self.stopRecordImage = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/stop_button"]];
+ self.recordButton.accessibilityTraits |= [self accessibilityTraits];
+ self.recordButton = [[UIButton alloc] initWithFrame:CGRectMake((viewRect.size.width - recordImage.size.width) / 2, (microphone.size.height + (grayBkg.size.height - recordImage.size.height) / 2), recordImage.size.width, recordImage.size.height)];
+ [self.recordButton setAccessibilityLabel:NSLocalizedString(@"toggle audio recording", nil)];
+ [self.recordButton setImage:recordImage forState:UIControlStateNormal];
+ [self.recordButton addTarget:self action:@selector(processButton:) forControlEvents:UIControlEventTouchUpInside];
+ [tmp addSubview:recordButton];
+
+ // make and add done button to navigation bar
+ self.doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissAudioView:)];
+ [self.doneButton setStyle:UIBarButtonItemStyleDone];
+ self.navigationItem.rightBarButtonItem = self.doneButton;
+
+ [self setView:tmp];
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
+ NSError* error = nil;
+
+ if (self.avSession == nil) {
+ // create audio session
+ self.avSession = [AVAudioSession sharedInstance];
+ if (error) {
+ // return error if can't create recording audio session
+ NSLog(@"error creating audio session: %@", [[error userInfo] description]);
+ self.errorCode = CAPTURE_INTERNAL_ERR;
+ [self dismissAudioView:nil];
+ }
+ }
+
+ // create file to record to in temporary dir
+
+ NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory
+ NSError* err = nil;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ // generate unique file name
+ NSString* filePath;
+ int i = 1;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/audio_%03d.wav", docsPath, i++];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ NSURL* fileURL = [NSURL fileURLWithPath:filePath isDirectory:NO];
+
+ // create AVAudioPlayer
+ self.avRecorder = [[AVAudioRecorder alloc] initWithURL:fileURL settings:nil error:&err];
+ if (err) {
+ NSLog(@"Failed to initialize AVAudioRecorder: %@\n", [err localizedDescription]);
+ self.avRecorder = nil;
+ // return error
+ self.errorCode = CAPTURE_INTERNAL_ERR;
+ [self dismissAudioView:nil];
+ } else {
+ self.avRecorder.delegate = self;
+ [self.avRecorder prepareToRecord];
+ self.recordButton.enabled = YES;
+ self.doneButton.enabled = YES;
+ }
+}
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000
+ - (NSUInteger)supportedInterfaceOrientations
+ {
+ NSUInteger orientation = UIInterfaceOrientationMaskPortrait; // must support portrait
+ NSUInteger supported = [captureCommand.viewController supportedInterfaceOrientations];
+
+ orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown);
+ return orientation;
+ }
+
+#endif
+
+- (void)viewDidUnload
+{
+ [self setView:nil];
+ [self.captureCommand setInUse:NO];
+}
+
+- (void)processButton:(id)sender
+{
+ if (self.avRecorder.recording) {
+ // stop recording
+ [self.avRecorder stop];
+ self.isTimed = NO; // recording was stopped via button so reset isTimed
+ // view cleanup will occur in audioRecordingDidFinishRecording
+ } else {
+ // begin recording
+ [self.recordButton setImage:stopRecordImage forState:UIControlStateNormal];
+ self.recordButton.accessibilityTraits &= ~[self accessibilityTraits];
+ [self.recordingView setHidden:NO];
+ NSError* error = nil;
+ [self.avSession setCategory:AVAudioSessionCategoryRecord error:&error];
+ [self.avSession setActive:YES error:&error];
+ if (error) {
+ // can't continue without active audio session
+ self.errorCode = CAPTURE_INTERNAL_ERR;
+ [self dismissAudioView:nil];
+ } else {
+ if (self.duration) {
+ self.isTimed = true;
+ [self.avRecorder recordForDuration:[duration doubleValue]];
+ } else {
+ [self.avRecorder record];
+ }
+ [self.timerLabel setText:@"0.00"];
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(updateTime) userInfo:nil repeats:YES];
+ self.doneButton.enabled = NO;
+ }
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+ }
+}
+
+/*
+ * helper method to clean up when stop recording
+ */
+- (void)stopRecordingCleanup
+{
+ if (self.avRecorder.recording) {
+ [self.avRecorder stop];
+ }
+ [self.recordButton setImage:recordImage forState:UIControlStateNormal];
+ self.recordButton.accessibilityTraits |= [self accessibilityTraits];
+ [self.recordingView setHidden:YES];
+ self.doneButton.enabled = YES;
+ if (self.avSession) {
+ // deactivate session so sounds can come through
+ [self.avSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
+ [self.avSession setActive:NO error:nil];
+ }
+ if (self.duration && self.isTimed) {
+ // VoiceOver announcement so user knows timed recording has finished
+ BOOL isUIAccessibilityAnnouncementNotification = (&UIAccessibilityAnnouncementNotification != NULL);
+ if (isUIAccessibilityAnnouncementNotification) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500ull * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
+ UIAccessibilityPostNotification (UIAccessibilityAnnouncementNotification, NSLocalizedString (@"timed recording complete", nil));
+ });
+ }
+ } else {
+ // issue a layout notification change so that VO will reannounce the button label when recording completes
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+ }
+}
+
+- (void)dismissAudioView:(id)sender
+{
+ // called when done button pressed or when error condition to do cleanup and remove view
+ if ([self.captureCommand.viewController.modalViewController respondsToSelector:@selector(presentingViewController)]) {
+ [[self.captureCommand.viewController.modalViewController presentingViewController] dismissModalViewControllerAnimated:YES];
+ } else {
+ [[self.captureCommand.viewController.modalViewController parentViewController] dismissModalViewControllerAnimated:YES];
+ }
+
+ if (!self.pluginResult) {
+ // return error
+ self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:self.errorCode];
+ }
+
+ self.avRecorder = nil;
+ [self.avSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
+ [self.avSession setActive:NO error:nil];
+ [self.captureCommand setInUse:NO];
+ UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
+ // return result
+ [self.captureCommand.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
+}
+
+- (void)updateTime
+{
+ // update the label with the elapsed time
+ [self.timerLabel setText:[self formatTime:self.avRecorder.currentTime]];
+}
+
+- (NSString*)formatTime:(int)interval
+{
+ // is this format universal?
+ int secs = interval % 60;
+ int min = interval / 60;
+
+ if (interval < 60) {
+ return [NSString stringWithFormat:@"0:%02d", interval];
+ } else {
+ return [NSString stringWithFormat:@"%d:%02d", min, secs];
+ }
+}
+
+- (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag
+{
+ // may be called when timed audio finishes - need to stop time and reset buttons
+ [self.timer invalidate];
+ [self stopRecordingCleanup];
+
+ // generate success result
+ if (flag) {
+ NSString* filePath = [avRecorder.url path];
+ // NSLog(@"filePath: %@", filePath);
+ NSDictionary* fileDict = [captureCommand getMediaDictionaryFromPath:filePath ofType:@"audio/wav"];
+ NSArray* fileArray = [NSArray arrayWithObject:fileDict];
+
+ self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray];
+ } else {
+ self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageToErrorObject:CAPTURE_INTERNAL_ERR];
+ }
+}
+
+- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder*)recorder error:(NSError*)error
+{
+ [self.timer invalidate];
+ [self stopRecordingCleanup];
+
+ NSLog(@"error recording audio");
+ self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageToErrorObject:CAPTURE_INTERNAL_ERR];
+ [self dismissAudioView:nil];
+}
+
+@end