|
|
|
@ -9,6 +9,7 @@
|
|
|
|
|
#import "PhoneNumber.h"
|
|
|
|
|
#import "SignalRecipient.h"
|
|
|
|
|
#import "TSAccountManager.h"
|
|
|
|
|
#import "TextSecureKitEnv.h"
|
|
|
|
|
|
|
|
|
|
@import Contacts;
|
|
|
|
|
|
|
|
|
@ -17,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
@interface Contact ()
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap;
|
|
|
|
|
@property (nonatomic, readonly) NSUInteger imageHash;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
@ -26,25 +28,24 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
@synthesize comparableNameFirstLast = _comparableNameFirstLast;
|
|
|
|
|
@synthesize comparableNameLastFirst = _comparableNameLastFirst;
|
|
|
|
|
@synthesize image = _image;
|
|
|
|
|
|
|
|
|
|
#if TARGET_OS_IOS
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithSystemContact:(CNContact *)contact
|
|
|
|
|
- (instancetype)initWithSystemContact:(CNContact *)cnContact
|
|
|
|
|
{
|
|
|
|
|
self = [super init];
|
|
|
|
|
if (!self) {
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_cnContactId = contact.identifier;
|
|
|
|
|
_firstName = contact.givenName.ows_stripped;
|
|
|
|
|
_lastName = contact.familyName.ows_stripped;
|
|
|
|
|
_fullName = [Contact formattedFullNameWithCNContact:contact];
|
|
|
|
|
_cnContactId = cnContact.identifier;
|
|
|
|
|
_firstName = cnContact.givenName.ows_stripped;
|
|
|
|
|
_lastName = cnContact.familyName.ows_stripped;
|
|
|
|
|
_fullName = [Contact formattedFullNameWithCNContact:cnContact];
|
|
|
|
|
|
|
|
|
|
NSMutableArray<NSString *> *phoneNumbers = [NSMutableArray new];
|
|
|
|
|
NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap = [NSMutableDictionary new];
|
|
|
|
|
for (CNLabeledValue *phoneNumberField in contact.phoneNumbers) {
|
|
|
|
|
for (CNLabeledValue *phoneNumberField in cnContact.phoneNumbers) {
|
|
|
|
|
if ([phoneNumberField.value isKindOfClass:[CNPhoneNumber class]]) {
|
|
|
|
|
CNPhoneNumber *phoneNumber = (CNPhoneNumber *)phoneNumberField.value;
|
|
|
|
|
[phoneNumbers addObject:phoneNumber.stringValue];
|
|
|
|
@ -96,18 +97,21 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
[self parsedPhoneNumbersFromUserTextPhoneNumbers:phoneNumbers phoneNumberNameMap:phoneNumberNameMap];
|
|
|
|
|
|
|
|
|
|
NSMutableArray<NSString *> *emailAddresses = [NSMutableArray new];
|
|
|
|
|
for (CNLabeledValue *emailField in contact.emailAddresses) {
|
|
|
|
|
for (CNLabeledValue *emailField in cnContact.emailAddresses) {
|
|
|
|
|
if ([emailField.value isKindOfClass:[NSString class]]) {
|
|
|
|
|
[emailAddresses addObject:(NSString *)emailField.value];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_emails = [emailAddresses copy];
|
|
|
|
|
|
|
|
|
|
if (contact.thumbnailImageData) {
|
|
|
|
|
_imageData = [contact.thumbnailImageData copy];
|
|
|
|
|
} else if (contact.imageData) {
|
|
|
|
|
// This only occurs when sharing a contact via the share extension
|
|
|
|
|
_imageData = [contact.imageData copy];
|
|
|
|
|
NSData *_Nullable avatarData = [Contact avatarDataForCNContact:cnContact];
|
|
|
|
|
if (avatarData) {
|
|
|
|
|
NSUInteger hashValue = 0;
|
|
|
|
|
NSData *hashData = [Cryptography computeSHA256Digest:avatarData truncatedToBytes:sizeof(hashValue)];
|
|
|
|
|
[hashData getBytes:&hashValue length:sizeof(hashValue)];
|
|
|
|
|
_imageHash = hashValue;
|
|
|
|
|
} else {
|
|
|
|
|
_imageHash = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
@ -124,29 +128,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
return [[self alloc] initWithSystemContact:cnContact];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (nullable UIImage *)image
|
|
|
|
|
{
|
|
|
|
|
if (_image) {
|
|
|
|
|
return _image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!self.imageData) {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_image = [UIImage imageWithData:self.imageData];
|
|
|
|
|
return _image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
|
|
|
|
|
{
|
|
|
|
|
if ([propertyKey isEqualToString:@"cnContact"] || [propertyKey isEqualToString:@"image"]) {
|
|
|
|
|
return MTLPropertyStorageTransitory;
|
|
|
|
|
} else {
|
|
|
|
|
return [super storageBehaviorForPropertyWithKey:propertyKey];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif // TARGET_OS_IOS
|
|
|
|
|
|
|
|
|
|
- (NSArray<PhoneNumber *> *)parsedPhoneNumbersFromUserTextPhoneNumbers:(NSArray<NSString *> *)userTextPhoneNumbers
|
|
|
|
@ -277,6 +258,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (nullable NSData *)avatarDataForCNContact:(nullable CNContact *)cnContact
|
|
|
|
|
{
|
|
|
|
|
if (cnContact.thumbnailImageData) {
|
|
|
|
|
return cnContact.thumbnailImageData.copy;
|
|
|
|
|
} else if (cnContact.imageData) {
|
|
|
|
|
// This only occurs when sharing a contact via the share extension
|
|
|
|
|
return cnContact.imageData.copy;
|
|
|
|
|
} else {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This method is used to de-bounce system contact fetch notifications
|
|
|
|
|
// by checking for changes in the contact data.
|
|
|
|
|
- (NSUInteger)hash
|
|
|
|
|
{
|
|
|
|
|
// base hash is some arbitrary number
|
|
|
|
@ -284,13 +279,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
hash = hash ^ self.fullName.hash;
|
|
|
|
|
|
|
|
|
|
if (self.imageData) {
|
|
|
|
|
NSUInteger thumbnailHash = 0;
|
|
|
|
|
NSData *thumbnailHashData =
|
|
|
|
|
[Cryptography computeSHA256Digest:self.imageData truncatedToBytes:sizeof(thumbnailHash)];
|
|
|
|
|
[thumbnailHashData getBytes:&thumbnailHash length:sizeof(thumbnailHash)];
|
|
|
|
|
hash = hash ^ thumbnailHash;
|
|
|
|
|
}
|
|
|
|
|
hash = hash ^ self.imageHash;
|
|
|
|
|
|
|
|
|
|
for (PhoneNumber *phoneNumber in self.parsedPhoneNumbers) {
|
|
|
|
|
hash = hash ^ phoneNumber.toE164.hash;
|
|
|
|
|