openEventsPlatformApp/plugins/cordova-plugin-camera/src/ios/CDVCamera.m
plugins/cordova-plugin-camera/src/ios/CDVCamera.mRaw
1/*
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18 */
19
20#import "CDVCamera.h"
21#import "CDVJpegHeaderWriter.h"
22#import "UIImage+CropScaleOrientation.h"
23#import <ImageIO/CGImageProperties.h>
24#import <AssetsLibrary/ALAssetRepresentation.h>
25#import <AssetsLibrary/AssetsLibrary.h>
26#import <AVFoundation/AVFoundation.h>
27#import <ImageIO/CGImageSource.h>
28#import <ImageIO/CGImageProperties.h>
29#import <ImageIO/CGImageDestination.h>
30#import <MobileCoreServices/UTCoreTypes.h>
31#import <objc/message.h>
32
33#ifndef __CORDOVA_4_0_0
34 #import <Cordova/NSData+Base64.h>
35#endif
36
37#define CDV_PHOTO_PREFIX @"cdv_photo_"
38
39static NSSet* org_apache_cordova_validArrowDirections;
40
41static NSString* toBase64(NSData* data) {
42 SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
43 SEL s2 = NSSelectorFromString(@"base64EncodedString");
44 SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
45
46 if ([data respondsToSelector:s1]) {
47 NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
48 return func(data, s1);
49 } else if ([data respondsToSelector:s2]) {
50 NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s2];
51 return func(data, s2);
52 } else if ([data respondsToSelector:s3]) {
53 NSString* (*func)(id, SEL, NSUInteger) = (void *)[data methodForSelector:s3];
54 return func(data, s3, 0);
55 } else {
56 return nil;
57 }
58}
59
60@implementation CDVPictureOptions
61
62+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
63{
64 CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
65
66 pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
67 pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
68 pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(UIImagePickerControllerSourceTypeCamera)] unsignedIntegerValue];
69
70 NSNumber* targetWidth = [command argumentAtIndex:3 withDefault:nil];
71 NSNumber* targetHeight = [command argumentAtIndex:4 withDefault:nil];
72 pictureOptions.targetSize = CGSizeMake(0, 0);
73 if ((targetWidth != nil) && (targetHeight != nil)) {
74 pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
75 }
76
77 pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
78 pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
79 pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
80 pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
81 pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
82 pictureOptions.popoverOptions = [command argumentAtIndex:10 withDefault:nil];
83 pictureOptions.cameraDirection = [[command argumentAtIndex:11 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
84
85 pictureOptions.popoverSupported = NO;
86 pictureOptions.usesGeolocation = NO;
87
88 return pictureOptions;
89}
90
91@end
92
93
94@interface CDVCamera ()
95
96@property (readwrite, assign) BOOL hasPendingOperation;
97
98@end
99
100@implementation CDVCamera
101
102+ (void)initialize
103{
104 org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
105}
106
107@synthesize hasPendingOperation, pickerController, locationManager;
108
109- (NSURL*) urlTransformer:(NSURL*)url
110{
111 NSURL* urlToTransform = url;
112
113 // for backwards compatibility - we check if this property is there
114 SEL sel = NSSelectorFromString(@"urlTransformer");
115 if ([self.commandDelegate respondsToSelector:sel]) {
116 // grab the block from the commandDelegate
117 NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(self.commandDelegate, sel);
118 // if block is not null, we call it
119 if (urlTransformer) {
120 urlToTransform = urlTransformer(url);
121 }
122 }
123
124 return urlToTransform;
125}
126
127- (BOOL)usesGeolocation
128{
129 id useGeo = [self.commandDelegate.settings objectForKey:[@"CameraUsesGeolocation" lowercaseString]];
130 return [(NSNumber*)useGeo boolValue];
131}
132
133- (BOOL)popoverSupported
134{
135 return (NSClassFromString(@"UIPopoverController") != nil) &&
136 (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
137}
138
139- (void)takePicture:(CDVInvokedUrlCommand*)command
140{
141 self.hasPendingOperation = YES;
142
143 __weak CDVCamera* weakSelf = self;
144
145 [self.commandDelegate runInBackground:^{
146
147 CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
148 pictureOptions.popoverSupported = [weakSelf popoverSupported];
149 pictureOptions.usesGeolocation = [weakSelf usesGeolocation];
150 pictureOptions.cropToSize = NO;
151
152 BOOL hasCamera = [UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType];
153 if (!hasCamera) {
154 NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
155 CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No camera available"];
156 [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
157 return;
158 }
159
160 // Validate the app has permission to access the camera
161 if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
162 AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
163 if (authStatus == AVAuthorizationStatusDenied ||
164 authStatus == AVAuthorizationStatusRestricted) {
165 // If iOS 8+, offer a link to the Settings app
166#pragma clang diagnostic push
167#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
168 NSString* settingsButton = (&UIApplicationOpenSettingsURLString != NULL)
169 ? NSLocalizedString(@"Settings", nil)
170 : nil;
171#pragma clang diagnostic pop
172
173 // Denied; show an alert
174 dispatch_async(dispatch_get_main_queue(), ^{
175 [[[UIAlertView alloc] initWithTitle:[[NSBundle mainBundle]
176 objectForInfoDictionaryKey:@"CFBundleDisplayName"]
177 message:NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.", nil)
178 delegate:weakSelf
179 cancelButtonTitle:NSLocalizedString(@"OK", nil)
180 otherButtonTitles:settingsButton, nil] show];
181 });
182 }
183 }
184
185 CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
186 weakSelf.pickerController = cameraPicker;
187
188 cameraPicker.delegate = weakSelf;
189 cameraPicker.callbackId = command.callbackId;
190 // we need to capture this state for memory warnings that dealloc this object
191 cameraPicker.webView = weakSelf.webView;
192
193 // Perform UI operations on the main thread
194 dispatch_async(dispatch_get_main_queue(), ^{
195 // If a popover is already open, close it; we only want one at a time.
196 if (([[weakSelf pickerController] pickerPopoverController] != nil) && [[[weakSelf pickerController] pickerPopoverController] isPopoverVisible]) {
197 [[[weakSelf pickerController] pickerPopoverController] dismissPopoverAnimated:YES];
198 [[[weakSelf pickerController] pickerPopoverController] setDelegate:nil];
199 [[weakSelf pickerController] setPickerPopoverController:nil];
200 }
201
202 if ([weakSelf popoverSupported] && (pictureOptions.sourceType != UIImagePickerControllerSourceTypeCamera)) {
203 if (cameraPicker.pickerPopoverController == nil) {
204 cameraPicker.pickerPopoverController = [[NSClassFromString(@"UIPopoverController") alloc] initWithContentViewController:cameraPicker];
205 }
206 [weakSelf displayPopover:pictureOptions.popoverOptions];
207 weakSelf.hasPendingOperation = NO;
208 } else {
209 [weakSelf.viewController presentViewController:cameraPicker animated:YES completion:^{
210 weakSelf.hasPendingOperation = NO;
211 }];
212 }
213 });
214 }];
215}
216
217// Delegate for camera permission UIAlertView
218- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
219{
220 // If Settings button (on iOS 8), open the settings app
221 if (buttonIndex == 1) {
222#pragma clang diagnostic push
223#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
224 if (&UIApplicationOpenSettingsURLString != NULL) {
225 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
226 }
227#pragma clang diagnostic pop
228 }
229
230 // Dismiss the view
231 [[self.pickerController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
232
233 CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"]; // error callback expects string ATM
234
235 [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
236
237 self.hasPendingOperation = NO;
238 self.pickerController = nil;
239}
240
241- (void)repositionPopover:(CDVInvokedUrlCommand*)command
242{
243 if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
244
245 [[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
246
247 NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
248 [self displayPopover:options];
249 }
250}
251
252- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
253{
254 NSInteger value = defaultValue;
255
256 NSNumber* val = [dict valueForKey:key]; // value is an NSNumber
257
258 if (val != nil) {
259 value = [val integerValue];
260 }
261 return value;
262}
263
264- (void)displayPopover:(NSDictionary*)options
265{
266 NSInteger x = 0;
267 NSInteger y = 32;
268 NSInteger width = 320;
269 NSInteger height = 480;
270 UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
271
272 if (options) {
273 x = [self integerValueForKey:options key:@"x" defaultValue:0];
274 y = [self integerValueForKey:options key:@"y" defaultValue:32];
275 width = [self integerValueForKey:options key:@"width" defaultValue:320];
276 height = [self integerValueForKey:options key:@"height" defaultValue:480];
277 arrowDirection = [self integerValueForKey:options key:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
278 if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithUnsignedInteger:arrowDirection]]) {
279 arrowDirection = UIPopoverArrowDirectionAny;
280 }
281 }
282
283 [[[self pickerController] pickerPopoverController] setDelegate:self];
284 [[[self pickerController] pickerPopoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
285 inView:[self.webView superview]
286 permittedArrowDirections:arrowDirection
287 animated:YES];
288}
289
290- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
291{
292 if([navigationController isKindOfClass:[UIImagePickerController class]]){
293 UIImagePickerController* cameraPicker = (UIImagePickerController*)navigationController;
294
295 if(![cameraPicker.mediaTypes containsObject:(NSString*)kUTTypeImage]){
296 [viewController.navigationItem setTitle:NSLocalizedString(@"Videos", nil)];
297 }
298 }
299}
300
301- (void)cleanup:(CDVInvokedUrlCommand*)command
302{
303 // empty the tmp directory
304 NSFileManager* fileMgr = [[NSFileManager alloc] init];
305 NSError* err = nil;
306 BOOL hasErrors = NO;
307
308 // clear contents of NSTemporaryDirectory
309 NSString* tempDirectoryPath = NSTemporaryDirectory();
310 NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
311 NSString* fileName = nil;
312 BOOL result;
313
314 while ((fileName = [directoryEnumerator nextObject])) {
315 // only delete the files we created
316 if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) {
317 continue;
318 }
319 NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
320 result = [fileMgr removeItemAtPath:filePath error:&err];
321 if (!result && err) {
322 NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
323 hasErrors = YES;
324 }
325 }
326
327 CDVPluginResult* pluginResult;
328 if (hasErrors) {
329 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."];
330 } else {
331 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
332 }
333 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
334}
335
336- (void)popoverControllerDidDismissPopover:(id)popoverController
337{
338 UIPopoverController* pc = (UIPopoverController*)popoverController;
339
340 [pc dismissPopoverAnimated:YES];
341 pc.delegate = nil;
342 if (self.pickerController && self.pickerController.callbackId && self.pickerController.pickerPopoverController) {
343 self.pickerController.pickerPopoverController = nil;
344 NSString* callbackId = self.pickerController.callbackId;
345 CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
346 [self.commandDelegate sendPluginResult:result callbackId:callbackId];
347 }
348 self.hasPendingOperation = NO;
349}
350
351- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
352{
353 NSData* data = nil;
354
355 switch (options.encodingType) {
356 case EncodingTypePNG:
357 data = UIImagePNGRepresentation(image);
358 break;
359 case EncodingTypeJPEG:
360 {
361 if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO) && (([options.quality integerValue] == 100) || (options.sourceType != UIImagePickerControllerSourceTypeCamera))){
362 // use image unedited as requested , don't resize
363 data = UIImageJPEGRepresentation(image, 1.0);
364 } else {
365 data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
366 }
367
368 if (options.usesGeolocation) {
369 NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
370 if (controllerMetadata) {
371 self.data = data;
372 self.metadata = [[NSMutableDictionary alloc] init];
373
374 NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
375 if (EXIFDictionary) {
376 [self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
377 }
378
379 if (IsAtLeastiOSVersion(@"8.0")) {
380 [[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
381 }
382 [[self locationManager] startUpdatingLocation];
383 }
384 }
385 }
386 break;
387 default:
388 break;
389 };
390
391 return data;
392}
393
394- (NSString*)tempFilePath:(NSString*)extension
395{
396 NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
397 NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by Apple (vs [NSFileManager defaultManager]) to be threadsafe
398 NSString* filePath;
399
400 // generate unique file name
401 int i = 1;
402 do {
403 filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, extension];
404 } while ([fileMgr fileExistsAtPath:filePath]);
405
406 return filePath;
407}
408
409- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options
410{
411 // get the image
412 UIImage* image = nil;
413 if (options.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
414 image = [info objectForKey:UIImagePickerControllerEditedImage];
415 } else {
416 image = [info objectForKey:UIImagePickerControllerOriginalImage];
417 }
418
419 if (options.correctOrientation) {
420 image = [image imageCorrectedForCaptureOrientation];
421 }
422
423 UIImage* scaledImage = nil;
424
425 if ((options.targetSize.width > 0) && (options.targetSize.height > 0)) {
426 // if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
427 if (options.cropToSize) {
428 scaledImage = [image imageByScalingAndCroppingForSize:options.targetSize];
429 } else {
430 scaledImage = [image imageByScalingNotCroppingForSize:options.targetSize];
431 }
432 }
433
434 return (scaledImage == nil ? image : scaledImage);
435}
436
437- (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info completion:(void (^)(CDVPluginResult* res))completion
438{
439 CDVPluginResult* result = nil;
440 BOOL saveToPhotoAlbum = options.saveToPhotoAlbum;
441 UIImage* image = nil;
442
443 switch (options.destinationType) {
444 case DestinationTypeNativeUri:
445 {
446 NSURL* url = [info objectForKey:UIImagePickerControllerReferenceURL];
447 saveToPhotoAlbum = NO;
448 // If, for example, we use sourceType = Camera, URL might be nil because image is stored in memory.
449 // In this case we must save image to device before obtaining an URI.
450 if (url == nil) {
451 image = [self retrieveImage:info options:options];
452 ALAssetsLibrary* library = [ALAssetsLibrary new];
453 [library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:^(NSURL *assetURL, NSError *error) {
454 CDVPluginResult* resultToReturn = nil;
455 if (error) {
456 resultToReturn = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
457 } else {
458 NSString* nativeUri = [[self urlTransformer:assetURL] absoluteString];
459 resultToReturn = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
460 }
461 completion(resultToReturn);
462 }];
463 return;
464 } else {
465 NSString* nativeUri = [[self urlTransformer:url] absoluteString];
466 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
467 }
468 }
469 break;
470 case DestinationTypeFileUri:
471 {
472 image = [self retrieveImage:info options:options];
473 NSData* data = [self processImage:image info:info options:options];
474 if (data) {
475
476 NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
477 NSString* filePath = [self tempFilePath:extension];
478 NSError* err = nil;
479
480 // save file
481 if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
482 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
483 } else {
484 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
485 }
486 }
487 }
488 break;
489 case DestinationTypeDataUrl:
490 {
491 image = [self retrieveImage:info options:options];
492 NSData* data = [self processImage:image info:info options:options];
493 if (data) {
494 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
495 }
496 }
497 break;
498 default:
499 break;
500 };
501
502 if (saveToPhotoAlbum && image) {
503 ALAssetsLibrary* library = [ALAssetsLibrary new];
504 [library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
505 }
506
507 completion(result);
508}
509
510- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
511{
512 NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
513 return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
514}
515
516- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
517{
518 __weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
519 __weak CDVCamera* weakSelf = self;
520
521 dispatch_block_t invoke = ^(void) {
522 __block CDVPluginResult* result = nil;
523
524 NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
525 if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
526 [weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) {
527 if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
528 [weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
529 weakSelf.hasPendingOperation = NO;
530 weakSelf.pickerController = nil;
531 }
532 }];
533 }
534 else {
535 result = [weakSelf resultForVideo:info];
536 [weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
537 weakSelf.hasPendingOperation = NO;
538 weakSelf.pickerController = nil;
539 }
540 };
541
542 if (cameraPicker.pictureOptions.popoverSupported && (cameraPicker.pickerPopoverController != nil)) {
543 [cameraPicker.pickerPopoverController dismissPopoverAnimated:YES];
544 cameraPicker.pickerPopoverController.delegate = nil;
545 cameraPicker.pickerPopoverController = nil;
546 invoke();
547 } else {
548 [[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
549 }
550}
551
552// older api calls newer didFinishPickingMediaWithInfo
553- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
554{
555 NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
556
557 [self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo];
558}
559
560- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
561{
562 __weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
563 __weak CDVCamera* weakSelf = self;
564
565 dispatch_block_t invoke = ^ (void) {
566 CDVPluginResult* result;
567 if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != ALAuthorizationStatusAuthorized) {
568 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"];
569 } else if (picker.sourceType != UIImagePickerControllerSourceTypeCamera && [ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized) {
570 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
571 } else {
572 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
573 }
574
575
576 [weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
577
578 weakSelf.hasPendingOperation = NO;
579 weakSelf.pickerController = nil;
580 };
581
582 [[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
583}
584
585- (CLLocationManager*)locationManager
586{
587 if (locationManager != nil) {
588 return locationManager;
589 }
590
591 locationManager = [[CLLocationManager alloc] init];
592 [locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
593 [locationManager setDelegate:self];
594
595 return locationManager;
596}
597
598- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
599{
600 if (locationManager == nil) {
601 return;
602 }
603
604 [self.locationManager stopUpdatingLocation];
605 self.locationManager = nil;
606
607 NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
608
609 CLLocationDegrees latitude = newLocation.coordinate.latitude;
610 CLLocationDegrees longitude = newLocation.coordinate.longitude;
611
612 // latitude
613 if (latitude < 0.0) {
614 latitude = latitude * -1.0f;
615 [GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
616 } else {
617 [GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
618 }
619 [GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
620
621 // longitude
622 if (longitude < 0.0) {
623 longitude = longitude * -1.0f;
624 [GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
625 }
626 else {
627 [GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
628 }
629 [GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
630
631 // altitude
632 CGFloat altitude = newLocation.altitude;
633 if (!isnan(altitude)){
634 if (altitude < 0) {
635 altitude = -altitude;
636 [GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
637 } else {
638 [GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
639 }
640 [GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
641 }
642
643 // Time and date
644 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
645 [formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
646 [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
647 [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
648 [formatter setDateFormat:@"yyyy:MM:dd"];
649 [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
650
651 [self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
652 [self imagePickerControllerReturnImageResult];
653}
654
655- (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error
656{
657 if (locationManager == nil) {
658 return;
659 }
660
661 [self.locationManager stopUpdatingLocation];
662 self.locationManager = nil;
663
664 [self imagePickerControllerReturnImageResult];
665}
666
667- (void)imagePickerControllerReturnImageResult
668{
669 CDVPictureOptions* options = self.pickerController.pictureOptions;
670 CDVPluginResult* result = nil;
671
672 if (self.metadata) {
673 CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
674 CFStringRef sourceType = CGImageSourceGetType(sourceImage);
675
676 CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
677 CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
678 CGImageDestinationFinalize(destinationImage);
679
680 CFRelease(sourceImage);
681 CFRelease(destinationImage);
682 }
683
684 switch (options.destinationType) {
685 case DestinationTypeFileUri:
686 {
687 NSError* err = nil;
688 NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
689 NSString* filePath = [self tempFilePath:extension];
690
691 // save file
692 if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
693 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
694 }
695 else {
696 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
697 }
698 }
699 break;
700 case DestinationTypeDataUrl:
701 {
702 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
703 }
704 break;
705 case DestinationTypeNativeUri:
706 default:
707 break;
708 };
709
710 if (result) {
711 [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
712 }
713
714 self.hasPendingOperation = NO;
715 self.pickerController = nil;
716 self.data = nil;
717 self.metadata = nil;
718
719 if (options.saveToPhotoAlbum) {
720 ALAssetsLibrary *library = [ALAssetsLibrary new];
721 [library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
722 }
723}
724
725@end
726
727@implementation CDVCameraPicker
728
729- (BOOL)prefersStatusBarHidden
730{
731 return YES;
732}
733
734- (UIViewController*)childViewControllerForStatusBarHidden
735{
736 return nil;
737}
738
739- (void)viewWillAppear:(BOOL)animated
740{
741 SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
742 if ([self respondsToSelector:sel]) {
743 [self performSelector:sel withObject:nil afterDelay:0];
744 }
745
746 [super viewWillAppear:animated];
747}
748
749+ (instancetype) createFromPictureOptions:(CDVPictureOptions*)pictureOptions;
750{
751 CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
752 cameraPicker.pictureOptions = pictureOptions;
753 cameraPicker.sourceType = pictureOptions.sourceType;
754 cameraPicker.allowsEditing = pictureOptions.allowsEditing;
755
756 if (cameraPicker.sourceType == UIImagePickerControllerSourceTypeCamera) {
757 // We only allow taking pictures (no video) in this API.
758 cameraPicker.mediaTypes = @[(NSString*)kUTTypeImage];
759 // We can only set the camera device if we're actually using the camera.
760 cameraPicker.cameraDevice = pictureOptions.cameraDirection;
761 } else if (pictureOptions.mediaType == MediaTypeAll) {
762 cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:cameraPicker.sourceType];
763 } else {
764 NSArray* mediaArray = @[(NSString*)(pictureOptions.mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage)];
765 cameraPicker.mediaTypes = mediaArray;
766 }
767
768 return cameraPicker;
769}
770
771@end