Updated Branches: refs/heads/master db3846983 -> 4a3b22064
Fixed CB-902 Contacts Permissions in iOS6 Made necessary updates to support changes for contacts privacy settings in iOS 6. Fixed bug in peoplePickerNavigationControllerDidCancel where setting allowsEditing to true and canceling w/o selecting a contact would crash. Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/commit/4a3b2206 Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/tree/4a3b2206 Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/diff/4a3b2206 Branch: refs/heads/master Commit: 4a3b22064e67c90a4237397ee8205b52cd2e14f1 Parents: db38469 Author: Becky Gibson <becka...@apache.org> Authored: Mon Oct 8 08:04:31 2012 -0400 Committer: Becky Gibson <becka...@apache.org> Committed: Mon Oct 8 08:12:20 2012 -0400 ---------------------------------------------------------------------- CordovaLib/Classes/CDVContacts.h | 15 + CordovaLib/Classes/CDVContacts.m | 526 ++++++++++++++++++++------------- 2 files changed, 328 insertions(+), 213 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/4a3b2206/CordovaLib/Classes/CDVContacts.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVContacts.h b/CordovaLib/Classes/CDVContacts.h index 4cf7734..17470c0 100644 --- a/CordovaLib/Classes/CDVContacts.h +++ b/CordovaLib/Classes/CDVContacts.h @@ -134,3 +134,18 @@ @property (nonatomic, strong) CDVPlugin* contactsPlugin; @end +@interface CDVAddressBookAccessError : NSObject +{} +@property (assign) CDVContactError errorCode; +- (CDVAddressBookAccessError*)initWithCode:(CDVContactError)code; +@end + +typedef void (^CDVAddressBookWorkerBlock)( + ABAddressBookRef addressBook, + CDVAddressBookAccessError * error + ); +@interface CDVAddressBookHelper : NSObject +{} + +- (void)createAddressBook:(CDVAddressBookWorkerBlock)workerBlock; +@end http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/4a3b2206/CordovaLib/Classes/CDVContacts.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVContacts.m b/CordovaLib/Classes/CDVContacts.m index 62f4080..8d7ce30 100644 --- a/CordovaLib/Classes/CDVContacts.m +++ b/CordovaLib/Classes/CDVContacts.m @@ -90,23 +90,29 @@ dispatch_queue_t workQueue = nil; { NSString* callbackId = command.callbackId; - CDVNewContactsController* npController = [[CDVNewContactsController alloc] init]; + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __unsafe_unretained weakSelf = self; // play it safe to avoid retain cycles - ABAddressBookRef ab = ABAddressBookCreate(); - - npController.addressBook = ab; // a CF retaining assign - CFRelease(ab); + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { + if (addrBook == NULL) { + // permission was denied or other error just return (no error callback) + return; + } + CDVNewContactsController* npController = [[CDVNewContactsController alloc] init]; + npController.addressBook = addrBook; // a CF retaining assign + CFRelease (addrBook); - npController.newPersonViewDelegate = self; - npController.callbackId = callbackId; + npController.newPersonViewDelegate = self; + npController.callbackId = callbackId; - UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:npController]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:npController]; - if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { - [self.viewController presentViewController:navController animated:YES completion:nil]; - } else { - [self.viewController presentModalViewController:navController animated:YES]; - } + if ([weakSelf.viewController respondsToSelector:@selector(presentViewController:::)]) { + [weakSelf.viewController presentViewController:navController animated:YES completion:nil]; + } else { + [weakSelf.viewController presentModalViewController:navController animated:YES]; + } + }]; } - (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person @@ -135,43 +141,53 @@ dispatch_queue_t workQueue = nil; NSString* callbackId = command.callbackId; ABRecordID recordID = [[command.arguments objectAtIndex:0] intValue]; NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:[NSNull null]]; - bool bEdit = [options isKindOfClass:[NSNull class]] ? false : [options existsValue:@"true" forKey:@"allowsEditing"]; - ABAddressBookRef addrBook = ABAddressBookCreate(); - ABRecordRef rec = ABAddressBookGetPersonWithRecordID(addrBook, recordID); - if (rec) { - CDVDisplayContactViewController* personController = [[CDVDisplayContactViewController alloc] init]; - personController.displayedPerson = rec; - personController.personViewDelegate = self; - personController.allowsEditing = NO; + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __unsafe_unretained weakSelf = self; // play it safe to avoid retain cycles - // create this so DisplayContactViewController will have a "back" button. - UIViewController* parentController = [[UIViewController alloc] init]; - UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { + if (addrBook == NULL) { + // permission was denied or other error - return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + ABRecordRef rec = ABAddressBookGetPersonWithRecordID (addrBook, recordID); - [navController pushViewController:personController animated:YES]; + if (rec) { + CDVDisplayContactViewController* personController = [[CDVDisplayContactViewController alloc] init]; + personController.displayedPerson = rec; + personController.personViewDelegate = self; + personController.allowsEditing = NO; - if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { - [self.viewController presentViewController:navController animated:YES completion:nil]; - } else { - [self.viewController presentModalViewController:navController animated:YES]; - } + // create this so DisplayContactViewController will have a "back" button. + UIViewController* parentController = [[UIViewController alloc] init]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; - if (bEdit) { - // create the editing controller and push it onto the stack - ABPersonViewController* editPersonController = [[ABPersonViewController alloc] init]; - editPersonController.displayedPerson = rec; - editPersonController.personViewDelegate = self; - editPersonController.allowsEditing = YES; - [navController pushViewController:editPersonController animated:YES]; - } - } else { - // no record, return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:UNKNOWN_ERROR]; - [self.commandDelegate sendPluginResult:result callbackId:callbackId]; - } - CFRelease(addrBook); + [navController pushViewController:personController animated:YES]; + + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:navController animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:navController animated:YES]; + } + + if (bEdit) { + // create the editing controller and push it onto the stack + ABPersonViewController* editPersonController = [[ABPersonViewController alloc] init]; + editPersonController.displayedPerson = rec; + editPersonController.personViewDelegate = self; + editPersonController.allowsEditing = YES; + [navController pushViewController:editPersonController animated:YES]; + } + } else { + // no record, return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + CFRelease (addrBook); + }]; } - (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person @@ -247,12 +263,21 @@ dispatch_queue_t workQueue = nil; if (picker.allowsEditing) { // get the info after possible edit - ABAddressBookRef addrBook = ABAddressBookCreate(); + // if we got this far, user has already approved/ disapproved addressBook access + ABAddressBookRef addrBook = nil; + if (&ABAddressBookCreateWithOptions != NULL) { + addrBook = ABAddressBookCreateWithOptions(NULL, NULL); + } else { + // iOS 4 & 5 + addrBook = ABAddressBookCreate(); + } ABRecordRef person = ABAddressBookGetPersonWithRecordID(addrBook, [[picker.pickedContactDictionary objectForKey:kW3ContactId] integerValue]); - CDVContact* pickedContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)person]; - NSArray* fields = [picker.options objectForKey:@"fields"]; - NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; - picker.pickedContactDictionary = [pickedContact toDictionary:returnFields]; + if (person) { + CDVContact* pickedContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)person]; + NSArray* fields = [picker.options objectForKey:@"fields"]; + NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + picker.pickedContactDictionary = [pickedContact toDictionary:returnFields]; + } CFRelease(addrBook); } CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:picker.pickedContactDictionary]; @@ -273,83 +298,93 @@ dispatch_queue_t workQueue = nil; dispatch_async([CDVContacts getWorkQueue], ^{ // from Apple: Important You must ensure that an instance of ABAddressBookRef is used by only one thread. - // which is why ABAddressBookCreate() is done within the dispatch queue. + // which is why address book is created within the dispatch queue. // more details here: http: //blog.byadrian.net/2012/05/05/ios-addressbook-framework-and-gcd/ - ABAddressBookRef addrBook = ABAddressBookCreate (); - - NSArray* foundRecords = nil; - // get the findOptions values - BOOL multiple = NO; // default is false - NSString* filter = nil; - if (![findOptions isKindOfClass:[NSNull class]]) { - id value = nil; - filter = (NSString*)[findOptions objectForKey:@"filter"]; - value = [findOptions objectForKey:@"multiple"]; - if ([value isKindOfClass:[NSNumber class]]) { - // multiple is a boolean that will come through as an NSNumber - multiple = [(NSNumber*) value boolValue]; - // NSLog(@"multiple is: %d", multiple); - } - } - - NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __unsafe_unretained weakSelf = self; // play it safe to avoid retain cycles + // it gets uglier, block within block..... + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { + if (addrBook == NULL) { + // permission was denied or other error - return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } - NSMutableArray* matches = nil; - if (!filter || [filter isEqualToString:@""]) { - // get all records - foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); - if (foundRecords && [foundRecords count] > 0) { - // create Contacts and put into matches array - // doesn't make sense to ask for all records when multiple == NO but better check - int xferCount = multiple == YES ? [foundRecords count] : 1; - matches = [NSMutableArray arrayWithCapacity:xferCount]; - - for (int k = 0; k < xferCount; k++) { - CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)[foundRecords objectAtIndex:k]]; - [matches addObject:xferContact]; - xferContact = nil; + NSArray* foundRecords = nil; + // get the findOptions values + BOOL multiple = NO; // default is false + NSString* filter = nil; + if (![findOptions isKindOfClass:[NSNull class]]) { + id value = nil; + filter = (NSString*)[findOptions objectForKey:@"filter"]; + value = [findOptions objectForKey:@"multiple"]; + if ([value isKindOfClass:[NSNumber class]]) { + // multiple is a boolean that will come through as an NSNumber + multiple = [(NSNumber*) value boolValue]; + // NSLog(@"multiple is: %d", multiple); + } } - } - } else { - foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); - matches = [NSMutableArray arrayWithCapacity:1]; - BOOL bFound = NO; - int testCount = [foundRecords count]; - - for (int j = 0; j < testCount; j++) { - CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)[foundRecords objectAtIndex:j]]; - if (testContact) { - bFound = [testContact foundValue:filter inFields:returnFields]; - if (bFound) { - [matches addObject:testContact]; + + NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + + NSMutableArray* matches = nil; + if (!filter || [filter isEqualToString:@""]) { + // get all records + foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); + if (foundRecords && [foundRecords count] > 0) { + // create Contacts and put into matches array + // doesn't make sense to ask for all records when multiple == NO but better check + int xferCount = multiple == YES ? [foundRecords count]:1; + matches = [NSMutableArray arrayWithCapacity:xferCount]; + + for (int k = 0; k < xferCount; k++) { + CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)[foundRecords objectAtIndex:k]]; + [matches addObject:xferContact]; + xferContact = nil; + } + } + } else { + foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); + matches = [NSMutableArray arrayWithCapacity:1]; + BOOL bFound = NO; + int testCount = [foundRecords count]; + + for (int j = 0; j < testCount; j++) { + CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)[foundRecords objectAtIndex:j]]; + if (testContact) { + bFound = [testContact foundValue:filter inFields:returnFields]; + if (bFound) { + [matches addObject:testContact]; + } + testContact = nil; + } } - testContact = nil; } - } - } - NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1]; - - if (matches != nil && [matches count] > 0) { - // convert to JS Contacts format and return in callback - // - returnFields determines what properties to return - @autoreleasepool { - int count = multiple == YES ? [matches count] : 1; - - for (int i = 0; i < count; i++) { - CDVContact* newContact = [matches objectAtIndex:i]; - NSDictionary* aContact = [newContact toDictionary:returnFields]; - [returnContacts addObject:aContact]; + NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1]; + + if (matches != nil && [matches count] > 0) { + // convert to JS Contacts format and return in callback + // - returnFields determines what properties to return + @autoreleasepool { + int count = multiple == YES ? [matches count]:1; + + for (int i = 0; i < count; i++) { + CDVContact* newContact = [matches objectAtIndex:i]; + NSDictionary* aContact = [newContact toDictionary:returnFields]; + [returnContacts addObject:aContact]; + } + } } - } - } - // return found contacts (array is empty if no contacts found) - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts]; - [self.commandDelegate sendPluginResult:result callbackId:callbackId]; - // NSLog(@"findCallback string: %@", jsString); + // return found contacts (array is empty if no contacts found) + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + // NSLog(@"findCallback string: %@", jsString); - if (addrBook) { - CFRelease (addrBook); - } + if (addrBook) { + CFRelease (addrBook); + } + }]; }); // end of workQueue block return; @@ -361,117 +396,134 @@ dispatch_queue_t workQueue = nil; NSDictionary* contactDict = [command.arguments objectAtIndex:0]; dispatch_async([CDVContacts getWorkQueue], ^{ + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __unsafe_unretained weakSelf = self; // play it safe to avoid retain cycles + + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errorCode) { + CDVPluginResult* result = nil; + if (addrBook == NULL) { + // permission was denied or other error - return error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode ? errorCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + + bool bIsError = FALSE, bSuccess = FALSE; + BOOL bUpdate = NO; + CDVContactError errCode = UNKNOWN_ERROR; + CFErrorRef error; + NSNumber* cId = [contactDict valueForKey:kW3ContactId]; + CDVContact* aContact = nil; + ABRecordRef rec = nil; + if (cId && ![cId isKindOfClass:[NSNull class]]) { + rec = ABAddressBookGetPersonWithRecordID (addrBook, [cId intValue]); + if (rec) { + aContact = [[CDVContact alloc] initFromABRecord:rec]; + bUpdate = YES; + } + } + if (!aContact) { + aContact = [[CDVContact alloc] init]; + } + + bSuccess = [aContact setFromContactDict:contactDict asUpdate:bUpdate]; + if (bSuccess) { + if (!bUpdate) { + bSuccess = ABAddressBookAddRecord (addrBook, [aContact record], &error); + } + if (bSuccess) { + bSuccess = ABAddressBookSave (addrBook, &error); + } + if (!bSuccess) { // need to provide error codes + bIsError = TRUE; + errCode = IO_ERROR; + } else { + // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified + // so would give back all fields (which W3C spec. indicates is not desired) + // for now (while testing) give back saved, full contact + NSDictionary* newContact = [aContact toDictionary:[CDVContact defaultFields]]; + // NSString* contactStr = [newContact JSONRepresentation]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newContact]; + } + } else { + bIsError = TRUE; + errCode = IO_ERROR; + } + CFRelease (addrBook); + + if (bIsError) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; + } + + if (result) { + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + }]; + }); // end of queue +} + +- (void)remove:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSNumber* cId = [command.arguments objectAtIndex:0]; + + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __unsafe_unretained weakSelf = self; // play it safe to avoid retain cycles + + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errorCode) { + CDVPluginResult* result = nil; + if (addrBook == NULL) { + // permission was denied or other error - return error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode ? errorCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + bool bIsError = FALSE, bSuccess = FALSE; - BOOL bUpdate = NO; CDVContactError errCode = UNKNOWN_ERROR; CFErrorRef error; - CDVPluginResult* result = nil; - - ABAddressBookRef addrBook = ABAddressBookCreate (); - NSNumber* cId = [contactDict valueForKey:kW3ContactId]; - CDVContact* aContact = nil; ABRecordRef rec = nil; - if (cId && ![cId isKindOfClass:[NSNull class]]) { + if (cId && ![cId isKindOfClass:[NSNull class]] && ([cId intValue] != kABRecordInvalidID)) { rec = ABAddressBookGetPersonWithRecordID (addrBook, [cId intValue]); if (rec) { - aContact = [[CDVContact alloc] initFromABRecord:rec]; - bUpdate = YES; - } - } - if (!aContact) { - aContact = [[CDVContact alloc] init]; - } - - bSuccess = [aContact setFromContactDict:contactDict asUpdate:bUpdate]; - if (bSuccess) { - if (!bUpdate) { - bSuccess = ABAddressBookAddRecord (addrBook, [aContact record], &error); - } - if (bSuccess) { - bSuccess = ABAddressBookSave (addrBook, &error); - } - if (!bSuccess) { // need to provide error codes - bIsError = TRUE; - errCode = IO_ERROR; + bSuccess = ABAddressBookRemoveRecord (addrBook, rec, &error); + if (!bSuccess) { + bIsError = TRUE; + errCode = IO_ERROR; + } else { + bSuccess = ABAddressBookSave (addrBook, &error); + if (!bSuccess) { + bIsError = TRUE; + errCode = IO_ERROR; + } else { + // set id to null + // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; + // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: contactDict]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + // NSString* contactStr = [contactDict JSONRepresentation]; + } + } } else { - // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified - // so would give back all fields (which W3C spec. indicates is not desired) - // for now (while testing) give back saved, full contact - NSDictionary* newContact = [aContact toDictionary:[CDVContact defaultFields]]; - // NSString* contactStr = [newContact JSONRepresentation]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newContact]; + // no record found return error + bIsError = TRUE; + errCode = UNKNOWN_ERROR; } } else { + // invalid contact id provided bIsError = TRUE; - errCode = IO_ERROR; + errCode = INVALID_ARGUMENT_ERROR; } - CFRelease (addrBook); + if (addrBook) { + CFRelease (addrBook); + } if (bIsError) { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; } - if (result) { - [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; } - }); // end of queue -} - -- (void)remove:(CDVInvokedUrlCommand*)command -{ - NSString* callbackId = command.callbackId; - NSNumber* cId = [command.arguments objectAtIndex:0]; - bool bIsError = FALSE, bSuccess = FALSE; - CDVContactError errCode = UNKNOWN_ERROR; - CFErrorRef error; - ABAddressBookRef addrBook = nil; - ABRecordRef rec = nil; - CDVPluginResult* result = nil; - - // NSMutableDictionary* contactDict = options; - addrBook = ABAddressBookCreate(); - // NSNumber* cId = [contactDict valueForKey:kW3ContactId]; - if (cId && ![cId isKindOfClass:[NSNull class]] && ([cId intValue] != kABRecordInvalidID)) { - rec = ABAddressBookGetPersonWithRecordID(addrBook, [cId intValue]); - if (rec) { - bSuccess = ABAddressBookRemoveRecord(addrBook, rec, &error); - if (!bSuccess) { - bIsError = TRUE; - errCode = IO_ERROR; - } else { - bSuccess = ABAddressBookSave(addrBook, &error); - if (!bSuccess) { - bIsError = TRUE; - errCode = IO_ERROR; - } else { - // set id to null - // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; - // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: contactDict]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - // NSString* contactStr = [contactDict JSONRepresentation]; - } - } - } else { - // no record found return error - bIsError = TRUE; - errCode = UNKNOWN_ERROR; - } - } else { - // invalid contact id provided - bIsError = TRUE; - errCode = INVALID_ARGUMENT_ERROR; - } - - if (addrBook) { - CFRelease(addrBook); - } - if (bIsError) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; - } - if (result) { - [self.commandDelegate sendPluginResult:result callbackId:callbackId]; - } - + }]; return; } @@ -496,3 +548,51 @@ dispatch_queue_t workQueue = nil; } @end +@implementation CDVAddressBookAccessError + +@synthesize errorCode; + +- (CDVAddressBookAccessError*)initWithCode:(CDVContactError)code +{ + self = [super init]; + if (self) { + self.errorCode = code; + } + return self; +} + +@end + +@implementation CDVAddressBookHelper + +- (void)createAddressBook:(CDVAddressBookWorkerBlock)workerBlock +{ + // !! caller is responsible for releasing AddressBook!! + ABAddressBookRef addressBook; + + if (&ABAddressBookCreateWithOptions != NULL) { + CFErrorRef error = nil; + // CFIndex status = ABAddressBookGetAuthorizationStatus(); + addressBook = ABAddressBookCreateWithOptions(NULL, &error); + // NSLog(@"addressBook access: %lu", status); + ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { + // callback can occur in background, address book must be accessed on thread it was created on + dispatch_sync (dispatch_get_main_queue (), ^{ + if (error) { + workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + } else if (!granted) { + workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:PERMISSION_DENIED_ERROR]); + } else { + // access granted + workerBlock (addressBook, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + } + }); + }); + } else { + // iOS 4 or 5 no checks needed + addressBook = ABAddressBookCreate (); + workerBlock (addressBook, NULL); + } +} + +@end