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.
This commit is contained in:
Frederic Jacobs 2015-08-24 01:47:25 +02:00
parent 0090030f3d
commit c95f190140
6 changed files with 82 additions and 37 deletions

View File

@ -50,7 +50,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<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>
<string>Signal needs access to your microphone to make and receive phone calls.</string>
<key>UIAppFonts</key>

View File

@ -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];

View File

@ -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;

View File

@ -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{

View File

@ -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];