|  |  |  | @ -16,12 +16,13 @@ NSString *const OWSContactsManagerSignalRecipientsDidChangeNotification = | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | @interface OWSContactsManager () | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | @property id addressBookReference; | 
		
	
		
			
				|  |  |  |  | @property TOCFuture *futureAddressBook; | 
		
	
		
			
				|  |  |  |  | @property ObservableValueController *observableContactsController; | 
		
	
		
			
				|  |  |  |  | @property TOCCancelTokenSource *life; | 
		
	
		
			
				|  |  |  |  | @property (atomic) id addressBookReference; | 
		
	
		
			
				|  |  |  |  | @property (atomic) TOCFuture *futureAddressBook; | 
		
	
		
			
				|  |  |  |  | @property (atomic) ObservableValueController *observableContactsController; | 
		
	
		
			
				|  |  |  |  | @property (atomic) TOCCancelTokenSource *life; | 
		
	
		
			
				|  |  |  |  | @property (atomic) NSDictionary *latestContactsById; | 
		
	
		
			
				|  |  |  |  | @property (atomic) NSDictionary<NSString *, Contact *> *contactMap; | 
		
	
		
			
				|  |  |  |  | @property (nonatomic) BOOL isContactsUpdateInFlight; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | @end | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -86,6 +87,7 @@ NSString *const OWSContactsManagerSignalRecipientsDidChangeNotification = | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | - (void)doAfterEnvironmentInitSetup { | 
		
	
		
			
				|  |  |  |  |     if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(9, 0)) { | 
		
	
		
			
				|  |  |  |  |         OWSAssert(!self.contactStore); | 
		
	
		
			
				|  |  |  |  |         self.contactStore = [[CNContactStore alloc] init]; | 
		
	
		
			
				|  |  |  |  |         [self.contactStore requestAccessForEntityType:CNEntityTypeContacts | 
		
	
		
			
				|  |  |  |  |                                     completionHandler:^(BOOL granted, NSError *_Nullable error) { | 
		
	
	
		
			
				
					|  |  |  | @ -96,7 +98,7 @@ NSString *const OWSContactsManagerSignalRecipientsDidChangeNotification = | 
		
	
		
			
				|  |  |  |  |                                     }]; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     [self setupAddressBook]; | 
		
	
		
			
				|  |  |  |  |     [self setupAddressBookIfNecessary]; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     [self.observableContactsController watchLatestValueOnArbitraryThread:^(NSArray *latestContacts) { | 
		
	
		
			
				|  |  |  |  |       @synchronized(self) { | 
		
	
	
		
			
				
					|  |  |  | @ -107,9 +109,7 @@ NSString *const OWSContactsManagerSignalRecipientsDidChangeNotification = | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | - (void)verifyABPermission { | 
		
	
		
			
				|  |  |  |  |     if (!self.addressBookReference) { | 
		
	
		
			
				|  |  |  |  |         [self setupAddressBook]; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     [self setupAddressBookIfNecessary]; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | #pragma mark - Address Book callbacks | 
		
	
	
		
			
				
					|  |  |  | @ -131,34 +131,78 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | #pragma mark - Setup | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | - (void)setupAddressBook { | 
		
	
		
			
				|  |  |  |  | - (void)setupAddressBookIfNecessary | 
		
	
		
			
				|  |  |  |  | { | 
		
	
		
			
				|  |  |  |  |     @synchronized(self) | 
		
	
		
			
				|  |  |  |  |     { | 
		
	
		
			
				|  |  |  |  |         // We only need to set up our address book once; | 
		
	
		
			
				|  |  |  |  |         // after that we only need to respond to onAddressBookChanged. | 
		
	
		
			
				|  |  |  |  |         if (self.addressBookReference) { | 
		
	
		
			
				|  |  |  |  |             return; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         // De-bounce address book setup. | 
		
	
		
			
				|  |  |  |  |         if (self.isContactsUpdateInFlight) { | 
		
	
		
			
				|  |  |  |  |             return; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         self.isContactsUpdateInFlight = YES; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     dispatch_async(ADDRESSBOOK_QUEUE, ^{ | 
		
	
		
			
				|  |  |  |  |       [[OWSContactsManager asyncGetAddressBook] thenDo:^(id addressBook) { | 
		
	
		
			
				|  |  |  |  |         self.addressBookReference = addressBook; | 
		
	
		
			
				|  |  |  |  |         ABAddressBookRef cfAddressBook = (__bridge ABAddressBookRef)addressBook; | 
		
	
		
			
				|  |  |  |  |         ABAddressBookRegisterExternalChangeCallback(cfAddressBook, onAddressBookChanged, (__bridge void *)self); | 
		
	
		
			
				|  |  |  |  |         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | 
		
	
		
			
				|  |  |  |  |             [self handleAddressBookChanged]; | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |       }]; | 
		
	
		
			
				|  |  |  |  |         TOCFuture *future = [OWSContactsManager asyncGetAddressBook]; | 
		
	
		
			
				|  |  |  |  |         [future thenDo:^(id addressBook) { | 
		
	
		
			
				|  |  |  |  |             // Success. | 
		
	
		
			
				|  |  |  |  |             @synchronized(self) | 
		
	
		
			
				|  |  |  |  |             { | 
		
	
		
			
				|  |  |  |  |                 OWSAssert(self.isContactsUpdateInFlight); | 
		
	
		
			
				|  |  |  |  |                 OWSAssert(!self.addressBookReference); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |                 self.addressBookReference = addressBook; | 
		
	
		
			
				|  |  |  |  |                 self.isContactsUpdateInFlight = NO; | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |             ABAddressBookRef cfAddressBook = (__bridge ABAddressBookRef)addressBook; | 
		
	
		
			
				|  |  |  |  |             ABAddressBookRegisterExternalChangeCallback(cfAddressBook, onAddressBookChanged, (__bridge void *)self); | 
		
	
		
			
				|  |  |  |  |             dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | 
		
	
		
			
				|  |  |  |  |                 [self handleAddressBookChanged]; | 
		
	
		
			
				|  |  |  |  |             }); | 
		
	
		
			
				|  |  |  |  |         }]; | 
		
	
		
			
				|  |  |  |  |         [future catchDo:^(id failure) { | 
		
	
		
			
				|  |  |  |  |             // Failure. | 
		
	
		
			
				|  |  |  |  |             @synchronized(self) | 
		
	
		
			
				|  |  |  |  |             { | 
		
	
		
			
				|  |  |  |  |                 OWSAssert(self.isContactsUpdateInFlight); | 
		
	
		
			
				|  |  |  |  |                 OWSAssert(!self.addressBookReference); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |                 self.isContactsUpdateInFlight = NO; | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |         }]; | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | - (void)intersectContacts { | 
		
	
		
			
				|  |  |  |  |     [self intersectContactsWithRetryDelay:60]; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | - (void)intersectContactsWithRetryDelay:(CGFloat)retryDelaySeconds | 
		
	
		
			
				|  |  |  |  | { | 
		
	
		
			
				|  |  |  |  |     void (^success)() = ^{ | 
		
	
		
			
				|  |  |  |  |         DDLogInfo(@"%@ Successfully intersected contacts.", self.tag); | 
		
	
		
			
				|  |  |  |  |         [self fireSignalRecipientsDidChange]; | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |     void (^failure)(NSError *error) = ^(NSError *error) { | 
		
	
		
			
				|  |  |  |  |         DDLogWarn(@"%@ Failed to intersect contacts with error: %@. Rescheduling", self.tag, error); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         // Retry with exponential backoff. | 
		
	
		
			
				|  |  |  |  |         // | 
		
	
		
			
				|  |  |  |  |         // TODO: Abort if another contact | 
		
	
		
			
				|  |  |  |  |         // intersection succeeds in the meantime. | 
		
	
		
			
				|  |  |  |  |         dispatch_after( | 
		
	
		
			
				|  |  |  |  |             dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryDelaySeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | 
		
	
		
			
				|  |  |  |  |                 [self intersectContactsWithRetryDelay:retryDelaySeconds * 2.f]; | 
		
	
		
			
				|  |  |  |  |             }); | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |     [[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.allContacts | 
		
	
		
			
				|  |  |  |  |                                                                            success:^{ | 
		
	
		
			
				|  |  |  |  |                                                                                DDLogInfo(@"%@ Successfully intersected contacts.", self.tag); | 
		
	
		
			
				|  |  |  |  |                                                                                [self fireSignalRecipientsDidChange]; | 
		
	
		
			
				|  |  |  |  |                                                                            } | 
		
	
		
			
				|  |  |  |  |                                                                            failure:^(NSError *error) { | 
		
	
		
			
				|  |  |  |  |                                                                                DDLogWarn(@"%@ Failed to intersect contacts with error: %@. Rescheduling", self.tag, error); | 
		
	
		
			
				|  |  |  |  |                                                                                 | 
		
	
		
			
				|  |  |  |  |                                                                                [NSTimer scheduledTimerWithTimeInterval:60 | 
		
	
		
			
				|  |  |  |  |                                                                                                                 target:self | 
		
	
		
			
				|  |  |  |  |                                                                                                               selector:@selector(intersectContacts) | 
		
	
		
			
				|  |  |  |  |                                                                                                               userInfo:nil | 
		
	
		
			
				|  |  |  |  |                                                                                                                repeats:NO]; | 
		
	
		
			
				|  |  |  |  |                                                                            }]; | 
		
	
		
			
				|  |  |  |  |                                                                            success:success | 
		
	
		
			
				|  |  |  |  |                                                                            failure:failure]; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | - (void)fireSignalRecipientsDidChange | 
		
	
	
		
			
				
					|  |  |  | 
 |