From c95f190140ba00048983d07289082e497d702947 Mon Sep 17 00:00:00 2001 From: Frederic Jacobs Date: Mon, 24 Aug 2015 01:47:25 +0200 Subject: [PATCH] Require AddressBook permission. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signal requires the AddressBook permission to use the app at the moment. This avoids the edgecases where a user doesn’t allow access to his address book and then tries to use the app. We’re also doing a significantly better job at explaining why we need this permission to the user. --- Signal/Signal-Info.plist | 2 +- Signal/src/AppDelegate.m | 7 +- Signal/src/contact/ContactsManager.h | 2 + Signal/src/contact/ContactsManager.m | 93 +++++++++++------- .../CodeVerificationViewController.m | 15 ++- .../translations/en.lproj/Localizable.strings | Bin 27742 -> 28652 bytes 6 files changed, 82 insertions(+), 37 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index e960492cc..5403a2944 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -50,7 +50,7 @@ LSRequiresIPhoneOS NSContactsUsageDescription - Signal uses your AddressBook as contacts list. We do not store your contacts on the server. + Signal uses your contacts to find users you know. We do not store your contacts on the server. NSMicrophoneUsageDescription Signal needs access to your microphone to make and receive phone calls. UIAppFonts diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 356afcc4e..a0a8b4af1 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -46,7 +46,11 @@ static NSString * const kURLHostVerifyPrefix = @"verify"; [logger addLoggingCallback:^(NSString *category, id details, NSUInteger index) {}]; [Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]]; [Environment.getCurrent.phoneDirectoryManager startUntilCancelled:nil]; - [Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup]; + + if ([TSAccountManager isRegistered]) { + [Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup]; + } + [Environment.getCurrent initCallListener]; [[TSStorageManager sharedManager] setupDatabase]; @@ -151,6 +155,7 @@ static NSString * const kURLHostVerifyPrefix = @"verify"; if ([TSAccountManager isRegistered]) { // We're double checking that the app is active, to be sure since we can't verify in production env due to code signing. [TSSocketManager becomeActiveFromForeground]; + [[Environment getCurrent].contactsManager verifyABPermission]; } [self removeScreenProtection]; diff --git a/Signal/src/contact/ContactsManager.h b/Signal/src/contact/ContactsManager.h index 3ad5e57e6..40f4ef63a 100644 --- a/Signal/src/contact/ContactsManager.h +++ b/Signal/src/contact/ContactsManager.h @@ -41,6 +41,8 @@ typedef void(^ABReloadRequestCompletionBlock)(NSArray *contacts); +(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString; +(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString; +- (void)verifyABPermission; + - (NSArray*)allContacts; - (NSArray*)signalContacts; - (NSArray*)textSecureContacts; diff --git a/Signal/src/contact/ContactsManager.m b/Signal/src/contact/ContactsManager.m index 88f020fc4..3e8cfb985 100644 --- a/Signal/src/contact/ContactsManager.m +++ b/Signal/src/contact/ContactsManager.m @@ -27,6 +27,7 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*); } return self; } + -(void) doAfterEnvironmentInitSetup { [self setupAddressBook]; [observableContactsController watchLatestValueOnArbitraryThread:^(NSArray *latestContacts) { @@ -46,6 +47,12 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*); [life cancel]; } +- (void)verifyABPermission { + if (!addressBookReference) { + [self setupAddressBook]; + } +} + #pragma mark - Notification Handlers -(void) registerNotificationHandlers{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatedDirectoryHandler:) name:NOTIFICATION_DIRECTORY_UPDATE object:nil]; @@ -55,7 +62,7 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*); NSArray *currentUsers = [self getSignalUsersFromContactsArray:latestContactsById.allValues]; [observableRedPhoneUsersController updateValue:currentUsers]; - } +} #pragma mark - Address Book callbacks @@ -88,7 +95,9 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError); checkOperationDescribe(nil == creationError, [((__bridge NSError *)creationError) localizedDescription]) ; ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) { - // TO DO: DISPLAY ALERT + if (!granted) { + [ContactsManager blockingContactDialog]; + } }); [observableContactsController updateValue:[self getContactsFromAddressBook:addressBookRef]]; } @@ -103,6 +112,20 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in } } ++ (void)blockingContactDialog{ + UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_TITLE", nil) + message:NSLocalizedString(@"AB_PERMISSION_MISSING_BODY", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_ACTION", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + }]]; + + [[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:controller animated:YES completion:nil]; +} + - (void)setupLatestRedPhoneUsers:(NSArray *)users { if (users) { latestWhisperUsersById = [ContactsManager keyContactsById:users]; @@ -126,11 +149,12 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError); assert((addressBookRef == nil) == (creationError != nil)); if (creationError != nil) { + [self blockingContactDialog]; return [TOCFuture futureWithFailure:(__bridge_transfer id)creationError]; } - + TOCFutureSource *futureAddressBookSource = [TOCFutureSource new]; - + id addressBook = (__bridge_transfer id)addressBookRef; ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef requestAccessError) { if (granted) { @@ -138,10 +162,11 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in [futureAddressBookSource trySetResult:addressBook]; }); } else { + [self blockingContactDialog]; [futureAddressBookSource trySetFailure:(__bridge id)requestAccessError]; } }); - + return futureAddressBookSource.future; } @@ -155,7 +180,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in (void*)(unsigned long)ABPersonGetSortOrdering()); NSArray *sortedPeople = (__bridge_transfer NSArray *)allPeopleMutable; - + // This predicate returns all contacts from the addressbook having at least one phone number NSPredicate* predicate = [NSPredicate predicateWithBlock: ^BOOL(id record, NSDictionary *bindings) { @@ -190,11 +215,11 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in - (Contact *)contactForRecord:(ABRecordRef)record { ABRecordID recordID = ABRecordGetRecordID(record); - + NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty); NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonLastNameProperty); NSArray *phoneNumbers = [self phoneNumbersForRecord:record]; - + if (!firstName && !lastName) { NSString *companyName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonOrganizationProperty); if (companyName) { @@ -203,12 +228,12 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in firstName = phoneNumbers.firstObject; } } - + NSString *notes = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonNoteProperty); NSArray *emails = [ContactsManager emailsForRecord:record]; NSData *image = (__bridge_transfer NSData*)ABPersonCopyImageDataWithFormat(record, kABPersonImageFormatThumbnail); UIImage *img = [UIImage imageWithData:image]; - + return [Contact contactWithFirstName:firstName andLastName:lastName andUserTextPhoneNumbers:phoneNumbers @@ -220,7 +245,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in -(Contact*)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber { NSArray *allContacts = [self allContacts]; - + ContactSearchBlock searchBlock = ^BOOL(Contact *contact, NSUInteger idx, BOOL *stop) { for (PhoneNumber *number in contact.parsedPhoneNumbers) { @@ -231,9 +256,9 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in } return NO; }; - + NSUInteger contactIndex = [allContacts indexOfObjectPassingTest:searchBlock]; - + if (contactIndex != NSNotFound) { return allContacts[contactIndex]; } else { @@ -247,7 +272,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in - (NSArray *)phoneNumbersForRecord:(ABRecordRef)record { ABMultiValueRef numberRefs = ABRecordCopyValue(record, kABPersonPhoneProperty); - + @try { NSArray *phoneNumbers = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(numberRefs); @@ -271,7 +296,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in +(NSArray *)emailsForRecord:(ABRecordRef)record { ABMultiValueRef emailRefs = ABRecordCopyValue(record, kABPersonEmailProperty); - + @try { NSArray *emails = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(emailRefs); @@ -288,14 +313,14 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in +(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString { require(contacts != nil); - + NSArray *matchingContacts = [contacts filter:^int(Contact *contact) { return optionalSearchString.length == 0 || [self name:contact.fullName matchesQuery:optionalSearchString]; }]; - + return [matchingContacts groupBy:^id(Contact *contact) { NSString *nameToUse = @""; - + BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst?YES:NO; if (firstNameOrdering && contact.firstName != nil && contact.firstName.length > 0) { @@ -355,7 +380,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet; NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet]; NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet]; - + return [queryStrings all:^int(NSString* query) { if (query.length == 0) return YES; return [nameStrings any:^int(NSString* nameWord) { @@ -368,7 +393,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in +(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString { NSString *phoneNumberString = phoneNumber.localizedDescriptionForUser; NSString *searchString = phoneNumberString.digitsOnly; - + if (queryString.length == 0) return YES; NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch; return [searchString rangeOfString:queryString options:searchOpts].location != NSNotFound; @@ -389,7 +414,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in #pragma mark - Whisper User Management -(NSArray*) getSignalUsersFromContactsArray:(NSArray*)contacts { - return [[contacts filter:^int(Contact* contact) { + return [[contacts filter:^int(Contact* contact) { return [self isContactRegisteredWithRedPhone:contact] || contact.isTextSecureContact; }]sortedArrayUsingComparator:[[self class] contactComparator]]; } @@ -420,25 +445,25 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in } -(NSArray*) getNewItemsFrom:(NSArray*) newArray comparedTo:(NSArray*) oldArray { - NSMutableSet *newSet = [NSMutableSet setWithArray:newArray]; - NSSet *oldSet = [NSSet setWithArray:oldArray]; - - [newSet minusSet:oldSet]; - return newSet.allObjects; + NSMutableSet *newSet = [NSMutableSet setWithArray:newArray]; + NSSet *oldSet = [NSSet setWithArray:oldArray]; + + [newSet minusSet:oldSet]; + return newSet.allObjects; } - (BOOL)isContactRegisteredWithRedPhone:(Contact*)contact { - for(PhoneNumber *phoneNumber in contact.parsedPhoneNumbers){ - if ( [self isPhoneNumberRegisteredWithRedPhone:phoneNumber]) { - return YES; - } - } - return NO; + for(PhoneNumber *phoneNumber in contact.parsedPhoneNumbers){ + if ( [self isPhoneNumberRegisteredWithRedPhone:phoneNumber]) { + return YES; + } + } + return NO; } - (BOOL)isPhoneNumberRegisteredWithRedPhone:(PhoneNumber*)phoneNumber { - PhoneNumberDirectoryFilter* directory = Environment.getCurrent.phoneDirectoryManager.getCurrentFilter; - return phoneNumber != nil && [directory containsPhoneNumber:phoneNumber]; + PhoneNumberDirectoryFilter* directory = Environment.getCurrent.phoneDirectoryManager.getCurrentFilter; + return phoneNumber != nil && [directory containsPhoneNumber:phoneNumber]; } - (NSString*)nameStringForPhoneIdentifier:(NSString*)identifier{ diff --git a/Signal/src/view controllers/CodeVerificationViewController.m b/Signal/src/view controllers/CodeVerificationViewController.m index 9a6668ebe..9c3cf5108 100644 --- a/Signal/src/view controllers/CodeVerificationViewController.m +++ b/Signal/src/view controllers/CodeVerificationViewController.m @@ -9,6 +9,7 @@ #import "CodeVerificationViewController.h" #import "Environment.h" +#import "ContactsManager.h" #import "PhoneNumberDirectoryFilterManager.h" #import "RPServerRequestsManager.h" #import "LocalizableText.h" @@ -62,7 +63,19 @@ [self registerWithSuccess:^{ [_submitCodeSpinner stopAnimating]; [Environment.getCurrent.phoneDirectoryManager forceUpdate]; - [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + [self.navigationController dismissViewControllerAnimated:YES completion:^{ + UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_WELCOME", nil) + message:NSLocalizedString(@"REGISTER_CONTACTS_BODY", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_CONTINUE", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup]; + }]]; + + [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:controller animated:YES completion:nil]; + }]; } failure:^(NSError *error) { [self showAlertForError:error]; [self enableServerActions:YES]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index c62ec8de234f97216f4be55c938069faff0ab727..616dc802c60dbf5363e9e764f1ad461358567a2c 100644 GIT binary patch delta 519 zcmb7A(MkeQ5Isu~6eN;FglP9}L~lY+6okPhAyQdn;-fCMm|-jJYGr>B^9B7u&@c26 zJ=G8N8ohRA!|1VCcJ7%obLY-EuP@H~oAX$;66Vt~wUSz9e=%GyU_TY&cd5t<(f>jrW>W1yxR{FxKUBGPmYp z8#7emVHZstQ{4$QRyeoDsNKz9c@^b*yRcc~3K72%EU*4~fIKW^X^W99C$Fn5e+yC* z4bBo-A>)9VnxJn4Vq4jUnx{r+941z&_z2fYjQ_JkBE3ve9!hexlbJ8mK%HX}liNfw K;wR7M_P+o$h-F*= delta 14 WcmaEJpYh%e#tnXooA0Oycmn`Bh6b?!