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