Require AddressBook permission.

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.
pull/1/head
Frederic Jacobs 10 years ago
parent 0090030f3d
commit c95f190140

@ -50,7 +50,7 @@
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSContactsUsageDescription</key> <key>NSContactsUsageDescription</key>
<string>Signal uses your AddressBook as contacts list. We do not store your contacts on the server.</string> <string>Signal uses your contacts to find users you know. We do not store your contacts on the server.</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>Signal needs access to your microphone to make and receive phone calls.</string> <string>Signal needs access to your microphone to make and receive phone calls.</string>
<key>UIAppFonts</key> <key>UIAppFonts</key>

@ -46,7 +46,11 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
[logger addLoggingCallback:^(NSString *category, id details, NSUInteger index) {}]; [logger addLoggingCallback:^(NSString *category, id details, NSUInteger index) {}];
[Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]]; [Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]];
[Environment.getCurrent.phoneDirectoryManager startUntilCancelled:nil]; [Environment.getCurrent.phoneDirectoryManager startUntilCancelled:nil];
[Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup];
if ([TSAccountManager isRegistered]) {
[Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup];
}
[Environment.getCurrent initCallListener]; [Environment.getCurrent initCallListener];
[[TSStorageManager sharedManager] setupDatabase]; [[TSStorageManager sharedManager] setupDatabase];
@ -151,6 +155,7 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
if ([TSAccountManager isRegistered]) { 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. // 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]; [TSSocketManager becomeActiveFromForeground];
[[Environment getCurrent].contactsManager verifyABPermission];
} }
[self removeScreenProtection]; [self removeScreenProtection];

@ -41,6 +41,8 @@ typedef void(^ABReloadRequestCompletionBlock)(NSArray *contacts);
+(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString; +(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString;
+(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString; +(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString;
- (void)verifyABPermission;
- (NSArray*)allContacts; - (NSArray*)allContacts;
- (NSArray*)signalContacts; - (NSArray*)signalContacts;
- (NSArray*)textSecureContacts; - (NSArray*)textSecureContacts;

@ -27,6 +27,7 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*);
} }
return self; return self;
} }
-(void) doAfterEnvironmentInitSetup { -(void) doAfterEnvironmentInitSetup {
[self setupAddressBook]; [self setupAddressBook];
[observableContactsController watchLatestValueOnArbitraryThread:^(NSArray *latestContacts) { [observableContactsController watchLatestValueOnArbitraryThread:^(NSArray *latestContacts) {
@ -46,6 +47,12 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*);
[life cancel]; [life cancel];
} }
- (void)verifyABPermission {
if (!addressBookReference) {
[self setupAddressBook];
}
}
#pragma mark - Notification Handlers #pragma mark - Notification Handlers
-(void) registerNotificationHandlers{ -(void) registerNotificationHandlers{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatedDirectoryHandler:) name:NOTIFICATION_DIRECTORY_UPDATE object:nil]; [[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]; NSArray *currentUsers = [self getSignalUsersFromContactsArray:latestContactsById.allValues];
[observableRedPhoneUsersController updateValue:currentUsers]; [observableRedPhoneUsersController updateValue:currentUsers];
} }
#pragma mark - Address Book callbacks #pragma mark - Address Book callbacks
@ -88,7 +95,9 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError); ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError);
checkOperationDescribe(nil == creationError, [((__bridge NSError *)creationError) localizedDescription]) ; checkOperationDescribe(nil == creationError, [((__bridge NSError *)creationError) localizedDescription]) ;
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) { ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
// TO DO: DISPLAY ALERT if (!granted) {
[ContactsManager blockingContactDialog];
}
}); });
[observableContactsController updateValue:[self getContactsFromAddressBook:addressBookRef]]; [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 { - (void)setupLatestRedPhoneUsers:(NSArray *)users {
if (users) { if (users) {
latestWhisperUsersById = [ContactsManager keyContactsById:users]; latestWhisperUsersById = [ContactsManager keyContactsById:users];
@ -126,11 +149,12 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError); ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError);
assert((addressBookRef == nil) == (creationError != nil)); assert((addressBookRef == nil) == (creationError != nil));
if (creationError != nil) { if (creationError != nil) {
[self blockingContactDialog];
return [TOCFuture futureWithFailure:(__bridge_transfer id)creationError]; return [TOCFuture futureWithFailure:(__bridge_transfer id)creationError];
} }
TOCFutureSource *futureAddressBookSource = [TOCFutureSource new]; TOCFutureSource *futureAddressBookSource = [TOCFutureSource new];
id addressBook = (__bridge_transfer id)addressBookRef; id addressBook = (__bridge_transfer id)addressBookRef;
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef requestAccessError) { ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef requestAccessError) {
if (granted) { if (granted) {
@ -138,10 +162,11 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
[futureAddressBookSource trySetResult:addressBook]; [futureAddressBookSource trySetResult:addressBook];
}); });
} else { } else {
[self blockingContactDialog];
[futureAddressBookSource trySetFailure:(__bridge id)requestAccessError]; [futureAddressBookSource trySetFailure:(__bridge id)requestAccessError];
} }
}); });
return futureAddressBookSource.future; return futureAddressBookSource.future;
} }
@ -155,7 +180,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
(void*)(unsigned long)ABPersonGetSortOrdering()); (void*)(unsigned long)ABPersonGetSortOrdering());
NSArray *sortedPeople = (__bridge_transfer NSArray *)allPeopleMutable; NSArray *sortedPeople = (__bridge_transfer NSArray *)allPeopleMutable;
// This predicate returns all contacts from the addressbook having at least one phone number // This predicate returns all contacts from the addressbook having at least one phone number
NSPredicate* predicate = [NSPredicate predicateWithBlock: ^BOOL(id record, NSDictionary *bindings) { NSPredicate* predicate = [NSPredicate predicateWithBlock: ^BOOL(id record, NSDictionary *bindings) {
@ -190,11 +215,11 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
- (Contact *)contactForRecord:(ABRecordRef)record { - (Contact *)contactForRecord:(ABRecordRef)record {
ABRecordID recordID = ABRecordGetRecordID(record); ABRecordID recordID = ABRecordGetRecordID(record);
NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty); NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonLastNameProperty); NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonLastNameProperty);
NSArray *phoneNumbers = [self phoneNumbersForRecord:record]; NSArray *phoneNumbers = [self phoneNumbersForRecord:record];
if (!firstName && !lastName) { if (!firstName && !lastName) {
NSString *companyName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonOrganizationProperty); NSString *companyName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonOrganizationProperty);
if (companyName) { if (companyName) {
@ -203,12 +228,12 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
firstName = phoneNumbers.firstObject; firstName = phoneNumbers.firstObject;
} }
} }
NSString *notes = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonNoteProperty); NSString *notes = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonNoteProperty);
NSArray *emails = [ContactsManager emailsForRecord:record]; NSArray *emails = [ContactsManager emailsForRecord:record];
NSData *image = (__bridge_transfer NSData*)ABPersonCopyImageDataWithFormat(record, kABPersonImageFormatThumbnail); NSData *image = (__bridge_transfer NSData*)ABPersonCopyImageDataWithFormat(record, kABPersonImageFormatThumbnail);
UIImage *img = [UIImage imageWithData:image]; UIImage *img = [UIImage imageWithData:image];
return [Contact contactWithFirstName:firstName return [Contact contactWithFirstName:firstName
andLastName:lastName andLastName:lastName
andUserTextPhoneNumbers:phoneNumbers andUserTextPhoneNumbers:phoneNumbers
@ -220,7 +245,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
-(Contact*)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber { -(Contact*)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber {
NSArray *allContacts = [self allContacts]; NSArray *allContacts = [self allContacts];
ContactSearchBlock searchBlock = ^BOOL(Contact *contact, NSUInteger idx, BOOL *stop) { ContactSearchBlock searchBlock = ^BOOL(Contact *contact, NSUInteger idx, BOOL *stop) {
for (PhoneNumber *number in contact.parsedPhoneNumbers) { for (PhoneNumber *number in contact.parsedPhoneNumbers) {
@ -231,9 +256,9 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
} }
return NO; return NO;
}; };
NSUInteger contactIndex = [allContacts indexOfObjectPassingTest:searchBlock]; NSUInteger contactIndex = [allContacts indexOfObjectPassingTest:searchBlock];
if (contactIndex != NSNotFound) { if (contactIndex != NSNotFound) {
return allContacts[contactIndex]; return allContacts[contactIndex];
} else { } else {
@ -247,7 +272,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
- (NSArray *)phoneNumbersForRecord:(ABRecordRef)record { - (NSArray *)phoneNumbersForRecord:(ABRecordRef)record {
ABMultiValueRef numberRefs = ABRecordCopyValue(record, kABPersonPhoneProperty); ABMultiValueRef numberRefs = ABRecordCopyValue(record, kABPersonPhoneProperty);
@try { @try {
NSArray *phoneNumbers = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(numberRefs); NSArray *phoneNumbers = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(numberRefs);
@ -271,7 +296,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
+(NSArray *)emailsForRecord:(ABRecordRef)record { +(NSArray *)emailsForRecord:(ABRecordRef)record {
ABMultiValueRef emailRefs = ABRecordCopyValue(record, kABPersonEmailProperty); ABMultiValueRef emailRefs = ABRecordCopyValue(record, kABPersonEmailProperty);
@try { @try {
NSArray *emails = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(emailRefs); NSArray *emails = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(emailRefs);
@ -288,14 +313,14 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
+(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString { +(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString {
require(contacts != nil); require(contacts != nil);
NSArray *matchingContacts = [contacts filter:^int(Contact *contact) { NSArray *matchingContacts = [contacts filter:^int(Contact *contact) {
return optionalSearchString.length == 0 || [self name:contact.fullName matchesQuery:optionalSearchString]; return optionalSearchString.length == 0 || [self name:contact.fullName matchesQuery:optionalSearchString];
}]; }];
return [matchingContacts groupBy:^id(Contact *contact) { return [matchingContacts groupBy:^id(Contact *contact) {
NSString *nameToUse = @""; NSString *nameToUse = @"";
BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst?YES:NO; BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst?YES:NO;
if (firstNameOrdering && contact.firstName != nil && contact.firstName.length > 0) { if (firstNameOrdering && contact.firstName != nil && contact.firstName.length > 0) {
@ -355,7 +380,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet; NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet;
NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet]; NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet];
NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet]; NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet];
return [queryStrings all:^int(NSString* query) { return [queryStrings all:^int(NSString* query) {
if (query.length == 0) return YES; if (query.length == 0) return YES;
return [nameStrings any:^int(NSString* nameWord) { return [nameStrings any:^int(NSString* nameWord) {
@ -368,7 +393,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
+(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString { +(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString {
NSString *phoneNumberString = phoneNumber.localizedDescriptionForUser; NSString *phoneNumberString = phoneNumber.localizedDescriptionForUser;
NSString *searchString = phoneNumberString.digitsOnly; NSString *searchString = phoneNumberString.digitsOnly;
if (queryString.length == 0) return YES; if (queryString.length == 0) return YES;
NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch; NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch;
return [searchString rangeOfString:queryString options:searchOpts].location != NSNotFound; return [searchString rangeOfString:queryString options:searchOpts].location != NSNotFound;
@ -389,7 +414,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
#pragma mark - Whisper User Management #pragma mark - Whisper User Management
-(NSArray*) getSignalUsersFromContactsArray:(NSArray*)contacts { -(NSArray*) getSignalUsersFromContactsArray:(NSArray*)contacts {
return [[contacts filter:^int(Contact* contact) { return [[contacts filter:^int(Contact* contact) {
return [self isContactRegisteredWithRedPhone:contact] || contact.isTextSecureContact; return [self isContactRegisteredWithRedPhone:contact] || contact.isTextSecureContact;
}]sortedArrayUsingComparator:[[self class] contactComparator]]; }]sortedArrayUsingComparator:[[self class] contactComparator]];
} }
@ -420,25 +445,25 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
} }
-(NSArray*) getNewItemsFrom:(NSArray*) newArray comparedTo:(NSArray*) oldArray { -(NSArray*) getNewItemsFrom:(NSArray*) newArray comparedTo:(NSArray*) oldArray {
NSMutableSet *newSet = [NSMutableSet setWithArray:newArray]; NSMutableSet *newSet = [NSMutableSet setWithArray:newArray];
NSSet *oldSet = [NSSet setWithArray:oldArray]; NSSet *oldSet = [NSSet setWithArray:oldArray];
[newSet minusSet:oldSet]; [newSet minusSet:oldSet];
return newSet.allObjects; return newSet.allObjects;
} }
- (BOOL)isContactRegisteredWithRedPhone:(Contact*)contact { - (BOOL)isContactRegisteredWithRedPhone:(Contact*)contact {
for(PhoneNumber *phoneNumber in contact.parsedPhoneNumbers){ for(PhoneNumber *phoneNumber in contact.parsedPhoneNumbers){
if ( [self isPhoneNumberRegisteredWithRedPhone:phoneNumber]) { if ( [self isPhoneNumberRegisteredWithRedPhone:phoneNumber]) {
return YES; return YES;
} }
} }
return NO; return NO;
} }
- (BOOL)isPhoneNumberRegisteredWithRedPhone:(PhoneNumber*)phoneNumber { - (BOOL)isPhoneNumberRegisteredWithRedPhone:(PhoneNumber*)phoneNumber {
PhoneNumberDirectoryFilter* directory = Environment.getCurrent.phoneDirectoryManager.getCurrentFilter; PhoneNumberDirectoryFilter* directory = Environment.getCurrent.phoneDirectoryManager.getCurrentFilter;
return phoneNumber != nil && [directory containsPhoneNumber:phoneNumber]; return phoneNumber != nil && [directory containsPhoneNumber:phoneNumber];
} }
- (NSString*)nameStringForPhoneIdentifier:(NSString*)identifier{ - (NSString*)nameStringForPhoneIdentifier:(NSString*)identifier{

@ -9,6 +9,7 @@
#import "CodeVerificationViewController.h" #import "CodeVerificationViewController.h"
#import "Environment.h" #import "Environment.h"
#import "ContactsManager.h"
#import "PhoneNumberDirectoryFilterManager.h" #import "PhoneNumberDirectoryFilterManager.h"
#import "RPServerRequestsManager.h" #import "RPServerRequestsManager.h"
#import "LocalizableText.h" #import "LocalizableText.h"
@ -62,7 +63,19 @@
[self registerWithSuccess:^{ [self registerWithSuccess:^{
[_submitCodeSpinner stopAnimating]; [_submitCodeSpinner stopAnimating];
[Environment.getCurrent.phoneDirectoryManager forceUpdate]; [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) { } failure:^(NSError *error) {
[self showAlertForError:error]; [self showAlertForError:error];
[self enableServerActions:YES]; [self enableServerActions:YES];

Loading…
Cancel
Save