Merge branch 'charlesmchen/profiles6'

pull/1/head
Matthew Chen 7 years ago
commit a1411ff81b

@ -58,7 +58,7 @@
34B3F8861E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8571E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m */; };
34B3F8871E8DF1700035BE1A /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */; };
34B3F8881E8DF1700035BE1A /* OversizeTextMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */; };
34B3F8891E8DF1700035BE1A /* OWSConversationSettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsTableViewController.m */; };
34B3F8891E8DF1700035BE1A /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */; };
34B3F88A1E8DF1700035BE1A /* OWSLinkDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85E1E8DF1700035BE1A /* OWSLinkDeviceViewController.m */; };
34B3F88B1E8DF1700035BE1A /* OWSLinkedDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8601E8DF1700035BE1A /* OWSLinkedDevicesTableViewController.m */; };
34B3F88C1E8DF1700035BE1A /* OWSMessagesToolbarContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 34B3F8611E8DF1700035BE1A /* OWSMessagesToolbarContentView.xib */; };
@ -471,8 +471,8 @@
34B3F8581E8DF1700035BE1A /* NotificationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsViewController.h; sourceTree = "<group>"; };
34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsViewController.m; sourceTree = "<group>"; };
34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OversizeTextMessageViewController.swift; sourceTree = "<group>"; };
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsTableViewController.h; sourceTree = "<group>"; };
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSConversationSettingsTableViewController.m; sourceTree = "<group>"; };
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewController.h; sourceTree = "<group>"; };
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSConversationSettingsViewController.m; sourceTree = "<group>"; };
34B3F85D1E8DF1700035BE1A /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = "<group>"; };
34B3F85E1E8DF1700035BE1A /* OWSLinkDeviceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkDeviceViewController.m; sourceTree = "<group>"; };
34B3F85F1E8DF1700035BE1A /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = "<group>"; };
@ -1004,8 +1004,8 @@
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */,
34533F161EA8D2070006114F /* OWSAudioAttachmentPlayer.h */,
34533F171EA8D2070006114F /* OWSAudioAttachmentPlayer.m */,
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsTableViewController.h */,
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsTableViewController.m */,
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */,
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */,
34D5CCAB1EAE7136005515DB /* OWSConversationSettingsViewDelegate.h */,
3497DBEA1ECE257500DB2605 /* OWSCountryMetadata.h */,
3497DBEB1ECE257500DB2605 /* OWSCountryMetadata.m */,
@ -2283,7 +2283,7 @@
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
34B3F8721E8DF1700035BE1A /* AdvancedSettingsTableViewController.m in Sources */,
45F170D61E315310003FC1F2 /* Weak.swift in Sources */,
34B3F8891E8DF1700035BE1A /* OWSConversationSettingsTableViewController.m in Sources */,
34B3F8891E8DF1700035BE1A /* OWSConversationSettingsViewController.m in Sources */,
34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */,
34B3F8731E8DF1700035BE1A /* AttachmentApprovalViewController.swift in Sources */,
B6B1013C196D213F007E3930 /* SignalKeyingStorage.m in Sources */,

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "table_ic_share_profile@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "table_ic_share_profile@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "table_ic_share_profile@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -17,7 +17,7 @@
#import "OWSAudioAttachmentPlayer.h"
#import "OWSCall.h"
#import "OWSContactsManager.h"
#import "OWSConversationSettingsTableViewController.h"
#import "OWSConversationSettingsViewController.h"
#import "OWSConversationSettingsViewDelegate.h"
#import "OWSDisappearingMessagesJob.h"
#import "OWSExpirableMessageView.h"
@ -1973,7 +1973,7 @@ typedef enum : NSUInteger {
return;
}
OWSConversationSettingsTableViewController *settingsVC = [OWSConversationSettingsTableViewController new];
OWSConversationSettingsViewController *settingsVC = [OWSConversationSettingsViewController new];
settingsVC.conversationSettingsViewDelegate = self;
[settingsVC configureWithThread:self.thread];
settingsVC.showVerificationOnAppear = showVerification;

@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface OWSConversationSettingsTableViewController : OWSTableViewController
@interface OWSConversationSettingsViewController : OWSTableViewController
@property (nonatomic, weak) id<OWSConversationSettingsViewDelegate> conversationSettingsViewDelegate;

@ -2,7 +2,7 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSConversationSettingsTableViewController.h"
#import "OWSConversationSettingsViewController.h"
#import "BlockListUIUtils.h"
#import "ContactsViewHelper.h"
#import "Environment.h"
@ -24,6 +24,7 @@
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSNotifyRemoteOfUpdatedDisappearingConfigurationJob.h>
#import <SignalServiceKit/OWSProfilesManager.h>
#import <SignalServiceKit/TSGroupThread.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSStorageManager.h>
@ -33,7 +34,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface OWSConversationSettingsTableViewController () <ContactEditingDelegate, ContactsViewHelperDelegate>
@interface OWSConversationSettingsViewController () <ContactEditingDelegate, ContactsViewHelperDelegate>
@property (nonatomic) TSThread *thread;
@ -52,7 +53,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@implementation OWSConversationSettingsTableViewController
@implementation OWSConversationSettingsViewController
- (instancetype)init
{
@ -78,14 +79,15 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil {
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (!self) {
return self;
}
[self commonInit];
return self;
}
@ -244,7 +246,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSTableContents *contents = [OWSTableContents new];
contents.title = NSLocalizedString(@"CONVERSATION_SETTINGS", @"title for conversation settings screen");
__weak OWSConversationSettingsTableViewController *weakSelf = self;
__weak OWSConversationSettingsViewController *weakSelf = self;
// Main section.
@ -271,7 +273,7 @@ NS_ASSUME_NONNULL_BEGIN
iconName:@"table_ic_add_to_existing_contact"];
}
actionBlock:^{
OWSConversationSettingsTableViewController *strongSelf = weakSelf;
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
TSContactThread *contactThread = (TSContactThread *)strongSelf.thread;
NSString *recipientId = contactThread.contactIdentifier;
@ -292,10 +294,41 @@ NS_ASSUME_NONNULL_BEGIN
}]];
}
if ([OWSProfilesManager.sharedManager isThreadInProfileWhitelist:self.thread]) {
[mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
return [weakSelf
labelCellWithName:(self.isGroupThread
? NSLocalizedString(
@"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_GROUP",
@"Indicates that user's profile has been shared with a group.")
: NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_USER",
@"Indicates that user's profile has been shared with a user."))iconName
:@"table_ic_share_profile"];
}
actionBlock:^{
[weakSelf showShareProfileAlert];
}]];
} else {
[mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
return
[weakSelf disclosureCellWithName:(self.isGroupThread
? NSLocalizedString(
@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_GROUP",
@"Action that shares user profile with a group.")
: NSLocalizedString(
@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_USER",
@"Action that shares user profile with a user."))iconName
:@"table_ic_share_profile"];
}
actionBlock:^{
[weakSelf showShareProfileAlert];
}]];
}
[mainSection
addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
OWSConversationSettingsTableViewController *strongSelf = weakSelf;
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
@ -354,7 +387,7 @@ NS_ASSUME_NONNULL_BEGIN
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
OWSConversationSettingsTableViewController *strongSelf = weakSelf;
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
@ -442,7 +475,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSTableSection *muteSection = [OWSTableSection new];
[muteSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
OWSConversationSettingsTableViewController *strongSelf = weakSelf;
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
@ -514,7 +547,7 @@ NS_ASSUME_NONNULL_BEGIN
[weakSelf disclosureCellWithName:NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_USER",
@"table cell label in conversation settings")
iconName:@"table_ic_block"];
OWSConversationSettingsTableViewController *strongSelf = weakSelf;
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
@ -538,7 +571,7 @@ NS_ASSUME_NONNULL_BEGIN
return 12.f;
}
- (UITableViewCell *)disclosureCellWithName:(NSString *)name iconName:(NSString *)iconName
- (UITableViewCell *)cellWithName:(NSString *)name iconName:(NSString *)iconName
{
OWSAssert(name.length > 0);
OWSAssert(iconName.length > 0);
@ -546,7 +579,6 @@ NS_ASSUME_NONNULL_BEGIN
UITableViewCell *cell = [UITableViewCell new];
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
UIImageView *iconView = [self viewForIconWithName:iconName];
[cell.contentView addSubview:iconView];
@ -566,6 +598,20 @@ NS_ASSUME_NONNULL_BEGIN
return cell;
}
- (UITableViewCell *)disclosureCellWithName:(NSString *)name iconName:(NSString *)iconName
{
UITableViewCell *cell = [self cellWithName:name iconName:iconName];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (UITableViewCell *)labelCellWithName:(NSString *)name iconName:(NSString *)iconName
{
UITableViewCell *cell = [self cellWithName:name iconName:iconName];
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
- (UIView *)mainSectionHeader
{
UIView *mainSectionHeader = [UIView new];
@ -728,6 +774,45 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Actions
- (void)showShareProfileAlert
{
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *leaveAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE",
@"Button to confirm that user wants to share their profile with a user or group.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[self shareProfile];
}];
[alertController addAction:leaveAction];
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *_Nonnull action) {
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)shareProfile
{
if (self.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)self.thread;
NSData *groupId = groupThread.groupModel.groupId;
[OWSProfilesManager.sharedManager addGroupIdToProfileWhitelist:groupId];
} else {
NSString *recipientId = self.thread.contactIdentifier;
[OWSProfilesManager.sharedManager addUserToProfileWhitelist:recipientId];
}
[self updateTableContents];
}
- (void)showVerificationView
{
NSString *recipientId = self.thread.contactIdentifier;
@ -949,7 +1034,7 @@ NS_ASSUME_NONNULL_BEGIN
message:message
preferredStyle:UIAlertControllerStyleActionSheet];
__weak OWSConversationSettingsTableViewController *weakSelf = self;
__weak OWSConversationSettingsViewController *weakSelf = self;
if (self.thread.isMuted) {
UIAlertAction *action = [UIAlertAction actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_UNMUTE_ACTION",
@"Label for button to unmute a thread.")

@ -213,14 +213,14 @@ NS_ASSUME_NONNULL_BEGIN
{
__weak ProfileViewController *weakSelf = self;
[OWSProfilesManager.sharedManager
updateLocalProfileName:self.nameTextField.text
localProfileAvatarImage:self.avatar
success:^{
[weakSelf.navigationController popViewControllerAnimated:YES];
}
failure:^{
// <#code#>
}];
updateLocalProfileName:self.nameTextField.text
avatarImage:self.avatar
success:^{
[weakSelf.navigationController popViewControllerAnimated:YES];
}
failure:^{
// <#code#>
}];
}
#pragma mark - UITextFieldDelegate

@ -9,6 +9,7 @@
#import "ViewControllerUtils.h"
#import <SignalServiceKit/ContactsUpdater.h>
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/OWSProfilesManager.h>
#import <SignalServiceKit/SignalAccount.h>
#import <SignalServiceKit/TSStorageManager.h>
@ -205,6 +206,8 @@ NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_Account
self.signalAccountMap = [signalAccountMap copy];
self.signalAccounts = [signalAccounts copy];
[OWSProfilesManager.sharedManager setContactRecipientIds:signalAccountMap.allKeys];
[self updateCachedDisplayNames];
});
});

@ -340,6 +340,21 @@
/* Label for button to unmute a thread. */
"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute";
/* Indicates that user's profile has been shared with a group. */
"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_GROUP" = "Your profile is shared this group.";
/* Indicates that user's profile has been shared with a user. */
"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_USER" = "Your profile is shared this user.";
/* Button to confirm that user wants to share their profile with a user or group. */
"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE" = "Share Profile";
/* Action that shares user profile with a group. */
"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_GROUP" = "Share Your Profile";
/* Action that shares user profile with a user. */
"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_USER" = "Share Your Profile";
/* ActionSheet title */
"CORRUPTED_SESSION_DESCRIPTION" = "Resetting your session will allow you to receive future messages from %@, but it will not recover any already corrupted messages.";

@ -35,6 +35,7 @@ message Content {
optional SyncMessage syncMessage = 2;
optional CallMessage callMessage = 3;
optional NullMessage nullMessage = 4;
optional bytes profileKey = 5;
}
message NullMessage {

@ -3,8 +3,9 @@
//
#import "OWSOutgoingSyncMessage.h"
#import "OWSSignalServiceProtos.pb.h"
#import "Cryptography.h"
#import "OWSProfilesManager.h"
#import "OWSSignalServiceProtos.pb.h"
NS_ASSUME_NONNULL_BEGIN
@ -40,11 +41,11 @@ NS_ASSUME_NONNULL_BEGIN
return [OWSSignalServiceProtosSyncMessageBuilder new];
}
- (NSData *)buildPlainTextData
- (NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
[contentBuilder setSyncMessage:[self buildSyncMessage]];
[self addLocalProfileKeyIfNecessary:contentBuilder recipient:recipient];
return [[contentBuilder build] data];
}

@ -32,6 +32,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
@class OWSSignalServiceProtosAttachmentPointer;
@class OWSSignalServiceProtosDataMessageBuilder;
@class OWSSignalServiceProtosContentBuilder;
@class SignalRecipient;
@interface TSOutgoingMessage : TSMessage
@ -107,7 +109,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
/**
* The data representation of this message, to be encrypted, before being sent.
*/
- (NSData *)buildPlainTextData;
- (NSData *)buildPlainTextData:(SignalRecipient *)recipient;
/**
* Intermediate protobuf representation
@ -176,6 +178,11 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
- (void)updateWithSentRecipient:(NSString *)contactId transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateWithSentRecipient:(NSString *)contactId;
#pragma mark -
- (void)addLocalProfileKeyIfNecessary:(OWSSignalServiceProtosContentBuilder *)contentBuilder
recipient:(SignalRecipient *)recipient;
@end
NS_ASSUME_NONNULL_END

@ -4,7 +4,10 @@
#import "TSOutgoingMessage.h"
#import "NSDate+millisecondTimeStamp.h"
#import "OWSOutgoingSyncMessage.h"
#import "OWSProfilesManager.h"
#import "OWSSignalServiceProtos.pb.h"
#import "SignalRecipient.h"
#import "TSAttachmentStream.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
@ -455,11 +458,43 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
return [[self dataMessageBuilder] build];
}
- (NSData *)buildPlainTextData
- (void)addLocalProfileKeyIfNecessary:(OWSSignalServiceProtosContentBuilder *)contentBuilder
recipient:(SignalRecipient *)recipient
{
OWSAssert(contentBuilder);
OWSAssert(recipient);
OWSAssert(OWSProfilesManager.sharedManager.localProfileKey.length > 0);
BOOL shouldIncludeProfileKey = NO;
if ([self isKindOfClass:[OWSOutgoingSyncMessage class]]) {
// Always sync the profile key to linked devices.
shouldIncludeProfileKey = YES;
} else {
OWSAssert(self.thread);
// For 1:1 threads, we want to include the profile key IFF the
// contact is in the whitelist.
//
// For Group threads, we want to include the profile key IFF the
// recipient OR the group is in the whitelist.
if ([OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipient.recipientId]) {
shouldIncludeProfileKey = YES;
} else if ([OWSProfilesManager.sharedManager isThreadInProfileWhitelist:self.thread]) {
shouldIncludeProfileKey = YES;
}
}
if (shouldIncludeProfileKey) {
[contentBuilder setProfileKey:OWSProfilesManager.sharedManager.localProfileKey];
}
}
- (NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
contentBuilder.dataMessage = [self buildDataMessage];
[self addLocalProfileKeyIfNecessary:contentBuilder recipient:recipient];
return [[contentBuilder build] data];
}

@ -1166,8 +1166,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
inThread:(TSThread *)thread
{
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
NSData *plainText = [message buildPlainTextData];
NSData *plainText = [message buildPlainTextData:recipient];
DDLogDebug(@"%@ built message: %@ plainTextData.length: %lu", self.tag, [message class], (unsigned long)plainText.length);
for (NSNumber *deviceNumber in recipient.devices) {

@ -9,7 +9,9 @@
#import "OWSCallHangupMessage.h"
#import "OWSCallIceUpdateMessage.h"
#import "OWSCallOfferMessage.h"
#import "OWSProfilesManager.h"
#import "OWSSignalServiceProtos.pb.h"
#import "TSContactThread.h"
NS_ASSUME_NONNULL_BEGIN
@ -120,11 +122,11 @@ NS_ASSUME_NONNULL_BEGIN
// return _thread;
//}
- (NSData *)buildPlainTextData
- (NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
[contentBuilder setCallMessage:[self asProtobuf]];
[self addLocalProfileKeyIfNecessary:contentBuilder recipient:recipient];
return [[contentBuilder build] data];
}

@ -3,11 +3,12 @@
//
#import "OWSOutgoingNullMessage.h"
#import "OWSSignalServiceProtos.pb.h"
#import "Cryptography.h"
#import "OWSVerificationStateSyncMessage.h"
#import "NSDate+millisecondTimeStamp.h"
#import "TSContactThread.h"
#import "OWSProfilesManager.h"
#import "OWSSignalServiceProtos.pb.h"
#import "OWSVerificationStateSyncMessage.h"
NS_ASSUME_NONNULL_BEGIN
@ -35,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - override TSOutgoingMessage
- (NSData *)buildPlainTextData
- (NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
OWSSignalServiceProtosNullMessageBuilder *nullMessageBuilder = [OWSSignalServiceProtosNullMessageBuilder new];
@ -56,7 +57,9 @@ NS_ASSUME_NONNULL_BEGIN
nullMessageBuilder.padding = [Cryptography generateRandomBytes:contentLength];
contentBuilder.nullMessage = [nullMessageBuilder build];
[self addLocalProfileKeyIfNecessary:contentBuilder recipient:recipient];
return [contentBuilder build].data;
}

@ -1,6 +1,4 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
// Generated by the protocol buffer compiler. DO NOT EDIT!
#import <ProtocolBuffers/ProtocolBuffers.h>
@ -277,25 +275,30 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
#define Content_syncMessage @"syncMessage"
#define Content_callMessage @"callMessage"
#define Content_nullMessage @"nullMessage"
#define Content_profileKey @"profileKey"
@interface OWSSignalServiceProtosContent : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasDataMessage_:1;
BOOL hasSyncMessage_:1;
BOOL hasCallMessage_:1;
BOOL hasNullMessage_:1;
BOOL hasProfileKey_:1;
OWSSignalServiceProtosDataMessage* dataMessage;
OWSSignalServiceProtosSyncMessage* syncMessage;
OWSSignalServiceProtosCallMessage* callMessage;
OWSSignalServiceProtosNullMessage* nullMessage;
NSData* profileKey;
}
- (BOOL) hasDataMessage;
- (BOOL) hasSyncMessage;
- (BOOL) hasCallMessage;
- (BOOL) hasNullMessage;
- (BOOL) hasProfileKey;
@property (readonly, strong) OWSSignalServiceProtosDataMessage* dataMessage;
@property (readonly, strong) OWSSignalServiceProtosSyncMessage* syncMessage;
@property (readonly, strong) OWSSignalServiceProtosCallMessage* callMessage;
@property (readonly, strong) OWSSignalServiceProtosNullMessage* nullMessage;
@property (readonly, strong) NSData* profileKey;
+ (instancetype) defaultInstance;
- (instancetype) defaultInstance;
@ -359,6 +362,11 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (OWSSignalServiceProtosContentBuilder*) setNullMessageBuilder:(OWSSignalServiceProtosNullMessageBuilder*) builderForValue;
- (OWSSignalServiceProtosContentBuilder*) mergeNullMessage:(OWSSignalServiceProtosNullMessage*) value;
- (OWSSignalServiceProtosContentBuilder*) clearNullMessage;
- (BOOL) hasProfileKey;
- (NSData*) profileKey;
- (OWSSignalServiceProtosContentBuilder*) setProfileKey:(NSData*) value;
- (OWSSignalServiceProtosContentBuilder*) clearProfileKey;
@end
#define NullMessage_padding @"padding"

@ -1,6 +1,4 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
// Generated by the protocol buffer compiler. DO NOT EDIT!
#import "OWSSignalServiceProtos.pb.h"
// @@protoc_insertion_point(imports)
@ -562,6 +560,7 @@ NSString *NSStringFromOWSSignalServiceProtosEnvelopeType(OWSSignalServiceProtosE
@property (strong) OWSSignalServiceProtosSyncMessage* syncMessage;
@property (strong) OWSSignalServiceProtosCallMessage* callMessage;
@property (strong) OWSSignalServiceProtosNullMessage* nullMessage;
@property (strong) NSData* profileKey;
@end
@implementation OWSSignalServiceProtosContent
@ -594,12 +593,20 @@ NSString *NSStringFromOWSSignalServiceProtosEnvelopeType(OWSSignalServiceProtosE
hasNullMessage_ = !!_value_;
}
@synthesize nullMessage;
- (BOOL) hasProfileKey {
return !!hasProfileKey_;
}
- (void) setHasProfileKey:(BOOL) _value_ {
hasProfileKey_ = !!_value_;
}
@synthesize profileKey;
- (instancetype) init {
if ((self = [super init])) {
self.dataMessage = [OWSSignalServiceProtosDataMessage defaultInstance];
self.syncMessage = [OWSSignalServiceProtosSyncMessage defaultInstance];
self.callMessage = [OWSSignalServiceProtosCallMessage defaultInstance];
self.nullMessage = [OWSSignalServiceProtosNullMessage defaultInstance];
self.profileKey = [NSData data];
}
return self;
}
@ -631,6 +638,9 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
if (self.hasNullMessage) {
[output writeMessage:4 value:self.nullMessage];
}
if (self.hasProfileKey) {
[output writeData:5 value:self.profileKey];
}
[self.unknownFields writeToCodedOutputStream:output];
}
- (SInt32) serializedSize {
@ -652,6 +662,9 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
if (self.hasNullMessage) {
size_ += computeMessageSize(4, self.nullMessage);
}
if (self.hasProfileKey) {
size_ += computeDataSize(5, self.profileKey);
}
size_ += self.unknownFields.serializedSize;
memoizedSerializedSize = size_;
return size_;
@ -711,6 +724,9 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
withIndent:[NSString stringWithFormat:@"%@ ", indent]];
[output appendFormat:@"%@}\n", indent];
}
if (self.hasProfileKey) {
[output appendFormat:@"%@%@: %@\n", indent, @"profileKey", self.profileKey];
}
[self.unknownFields writeDescriptionTo:output withIndent:indent];
}
- (void) storeInDictionary:(NSMutableDictionary *)dictionary {
@ -734,6 +750,9 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
[self.nullMessage storeInDictionary:messageDictionary];
[dictionary setObject:[NSDictionary dictionaryWithDictionary:messageDictionary] forKey:@"nullMessage"];
}
if (self.hasProfileKey) {
[dictionary setObject: self.profileKey forKey: @"profileKey"];
}
[self.unknownFields storeInDictionary:dictionary];
}
- (BOOL) isEqual:(id)other {
@ -753,6 +772,8 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
(!self.hasCallMessage || [self.callMessage isEqual:otherMessage.callMessage]) &&
self.hasNullMessage == otherMessage.hasNullMessage &&
(!self.hasNullMessage || [self.nullMessage isEqual:otherMessage.nullMessage]) &&
self.hasProfileKey == otherMessage.hasProfileKey &&
(!self.hasProfileKey || [self.profileKey isEqual:otherMessage.profileKey]) &&
(self.unknownFields == otherMessage.unknownFields || (self.unknownFields != nil && [self.unknownFields isEqual:otherMessage.unknownFields]));
}
- (NSUInteger) hash {
@ -769,6 +790,9 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
if (self.hasNullMessage) {
hashCode = hashCode * 31 + [self.nullMessage hash];
}
if (self.hasProfileKey) {
hashCode = hashCode * 31 + [self.profileKey hash];
}
hashCode = hashCode * 31 + [self.unknownFields hash];
return hashCode;
}
@ -824,6 +848,9 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
if (other.hasNullMessage) {
[self mergeNullMessage:other.nullMessage];
}
if (other.hasProfileKey) {
[self setProfileKey:other.profileKey];
}
[self mergeUnknownFields:other.unknownFields];
return self;
}
@ -881,6 +908,10 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
[self setNullMessage:[subBuilder buildPartial]];
break;
}
case 42: {
[self setProfileKey:[input readData]];
break;
}
}
}
}
@ -1004,6 +1035,22 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan
resultContent.nullMessage = [OWSSignalServiceProtosNullMessage defaultInstance];
return self;
}
- (BOOL) hasProfileKey {
return resultContent.hasProfileKey;
}
- (NSData*) profileKey {
return resultContent.profileKey;
}
- (OWSSignalServiceProtosContentBuilder*) setProfileKey:(NSData*) value {
resultContent.hasProfileKey = YES;
resultContent.profileKey = value;
return self;
}
- (OWSSignalServiceProtosContentBuilder*) clearProfileKey {
resultContent.hasProfileKey = NO;
resultContent.profileKey = [NSData data];
return self;
}
@end
@interface OWSSignalServiceProtosNullMessage ()

@ -19,6 +19,7 @@
#import "OWSIncomingMessageFinder.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSMessageSender.h"
#import "OWSProfilesManager.h"
#import "OWSReadReceiptsProcessor.h"
#import "OWSRecordTranscriptJob.h"
#import "OWSSyncContactsMessage.h"
@ -494,6 +495,14 @@ NS_ASSUME_NONNULL_BEGIN
if (envelope.hasContent) {
OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData];
DDLogInfo(@"%@ handling content: <Content: %@>", self.tag, [self descriptionForContent:content]);
if ([content hasProfileKey]) {
NSData *profileKey = [content profileKey];
NSString *recipientId = envelope.source;
[OWSProfilesManager.sharedManager setProfileKey:profileKey
forRecipientId:recipientId];
}
if (content.hasSyncMessage) {
[self handleIncomingEnvelope:envelope withSyncMessage:content.syncMessage];
} else if (content.hasDataMessage) {

@ -5,6 +5,9 @@
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kNSNotificationName_LocalProfileDidChange;
extern NSString *const kNSNotificationName_OtherUsersProfileDidChange;
@class TSThread;
// This class can be safely accessed and used from any thread.
@interface OWSProfilesManager : NSObject
@ -13,19 +16,51 @@ extern NSString *const kNSNotificationName_LocalProfileDidChange;
+ (instancetype)sharedManager;
@property (atomic, nullable, readonly) NSString *localProfileName;
@property (atomic, nullable, readonly) UIImage *localProfileAvatarImage;
#pragma mark - Local Profile
@property (atomic, readonly) NSData *localProfileKey;
// These two methods should only be called from the main thread.
- (NSString *)localProfileName;
- (UIImage *)localProfileAvatarImage;
// This method is used to update the "local profile" state on the client
// and the service. Client state is only updated if service state is
// successfully updated.
- (void)updateLocalProfileName:(nullable NSString *)localProfileName
localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage
//
// This method should only be called from the main thread.
- (void)updateLocalProfileName:(nullable NSString *)profileName
avatarImage:(nullable UIImage *)avatarImage
success:(void (^)())successBlock
failure:(void (^)())failureBlock;
// This method should only be called from the main thread.
- (void)appLaunchDidBegin;
#pragma mark - Profile Whitelist
- (void)addUserToProfileWhitelist:(NSString *)recipientId;
- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId;
- (void)addGroupIdToProfileWhitelist:(NSData *)groupId;
- (void)setContactRecipientIds:(NSArray<NSString *> *)contactRecipientIds;
- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread;
#pragma mark - Other User's Profiles
- (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId;
- (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId;
- (nullable NSString *)profileNameForRecipientId:(NSString *)recipientId;
- (nullable UIImage *)profileAvatarForRecipientId:(NSString *)recipientId;
- (void)fetchProfileForRecipientId:(NSString *)recipientId;
@end
NS_ASSUME_NONNULL_END

@ -3,23 +3,34 @@
//
#import "OWSProfilesManager.h"
#import "NSData+hexString.h"
#import "NSDate+OWS.h"
#import "OWSMessageSender.h"
#import "SecurityUtils.h"
#import "TSAccountManager.h"
#import "TSGroupThread.h"
#import "TSStorageManager.h"
#import "TextSecureKitEnv.h"
#import "TSThread.h"
#import "TSYapDatabaseObject.h"
#import "TextSecureKitEnv.h"
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface UserProfile : TSYapDatabaseObject
@interface AvatarMetadata : TSYapDatabaseObject
@property (nonatomic, readonly) NSString *recipientId;
@property (nonatomic, nullable) NSString *profileName;
@property (nonatomic, nullable) NSString *avatarUrl;
@property (nonatomic, nullable) NSString *avatarDigest;
// This filename is relative to OWSProfilesManager.profileAvatarsDirPath.
@property (nonatomic, readonly) NSString *fileName;
@property (nonatomic, readonly) NSString *avatarUrl;
@property (nonatomic, readonly) NSString *avatarDigest;
@property (nonatomic, nullable) NSString *avatarFileName;
// This should reflect when either:
//
// * The last successful update was started.
// * The in-flight update was started.
@property (nonatomic, nullable) NSDate *lastUpdateDate;
- (instancetype)init NS_UNAVAILABLE;
@ -27,46 +38,44 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@implementation AvatarMetadata
@implementation UserProfile
+ (NSString *)collection
- (instancetype)initWithRecipientId:(NSString *)recipientId
profileName:(NSString *_Nullable)profileName
avatarUrl:(NSString *_Nullable)avatarUrl
avatarDigest:(NSString *_Nullable)avatarDigest
avatarFileName:(NSString *_Nullable)avatarFileName
{
return @"AvatarMetadata";
}
- (instancetype)initWithFileName:(NSString *)fileName
avatarUrl:(NSString *)avatarUrl
avatarDigest:(NSString *)avatarDigest
{
// TODO: Local filenames for avatars are guaranteed to be unique.
self = [super initWithUniqueId:fileName];
self = [super initWithUniqueId:recipientId];
if (!self) {
return self;
}
OWSAssert(fileName.length > 0);
OWSAssert(avatarUrl.length > 0);
OWSAssert(avatarDigest.length > 0);
_fileName = fileName;
OWSAssert(recipientId.length > 0);
_recipientId = recipientId;
_profileName = profileName;
_avatarUrl = avatarUrl;
_avatarDigest = avatarDigest;
_avatarFileName = avatarFileName;
return self;
}
#pragma mark - NSObject
- (BOOL)isEqual:(AvatarMetadata *)other
- (BOOL)isEqual:(UserProfile *)other
{
return ([other isKindOfClass:[AvatarMetadata class]] && [self.fileName isEqualToString:other.fileName] &&
[self.avatarUrl isEqualToString:other.avatarUrl] && [self.avatarDigest isEqualToString:other.avatarDigest]);
return ([other isKindOfClass:[UserProfile class]] && [self.recipientId isEqualToString:other.recipientId] &&
[self.profileName isEqualToString:other.profileName] && [self.avatarUrl isEqualToString:other.avatarUrl] &&
[self.avatarDigest isEqualToString:other.avatarDigest] &&
[self.avatarFileName isEqualToString:other.avatarFileName]);
}
- (NSUInteger)hash
{
return self.fileName.hash ^ self.avatarUrl.hash ^ self.avatarDigest.hash;
return self.recipientId.hash ^ self.profileName.hash ^ self.avatarUrl.hash ^ self.avatarDigest.hash
^ self.avatarFileName.hash;
}
@end
@ -74,13 +83,17 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange";
NSString *const kNSNotificationName_OtherUsersProfileDidChange = @"kNSNotificationName_OtherUsersProfileDidChange";
NSString *const kOWSProfilesManager_Collection = @"kOWSProfilesManager_Collection";
// This key is used to persist the local user's profile key.
NSString *const kOWSProfilesManager_LocalProfileSecretKey = @"kOWSProfilesManager_LocalProfileSecretKey";
NSString *const kOWSProfilesManager_LocalProfileNameKey = @"kOWSProfilesManager_LocalProfileNameKey";
NSString *const kOWSProfilesManager_LocalProfileAvatarMetadataKey
= @"kOWSProfilesManager_LocalProfileAvatarMetadataKey";
NSString *const kOWSProfilesManager_UserWhitelistCollection = @"kOWSProfilesManager_UserWhitelistCollection";
NSString *const kOWSProfilesManager_GroupWhitelistCollection = @"kOWSProfilesManager_GroupWhitelistCollection";
NSString *const kOWSProfilesManager_OtherUsersProfileKeysCollection
= @"kOWSProfilesManager_OtherUsersProfileKeysCollection";
// TODO:
static const NSInteger kProfileKeyLength = 16;
@ -90,13 +103,22 @@ static const NSInteger kProfileKeyLength = 16;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@property (atomic, readonly, nullable) NSData *localProfileKey;
// This property should only be mutated on the main thread,
//
// NOTE: Do not access this property directly; use getOrCreateLocalUserProfile instead.
@property (nonatomic, nullable) UserProfile *localUserProfile;
// This property should only be mutated on the main thread,
@property (nonatomic, nullable) UIImage *localCachedAvatarImage;
// These caches are lazy-populated. The single point of truth is the database.
//
// These three properties can be accessed on any thread.
@property (atomic, readonly) NSMutableDictionary<NSString *, NSNumber *> *userProfileWhitelistCache;
@property (atomic, readonly) NSMutableDictionary<NSString *, NSNumber *> *groupProfileWhitelistCache;
@property (atomic, readonly) NSMutableDictionary<NSString *, NSData *> *otherUsersProfileKeyCache;
// These properties should only be mutated on the main thread,
// but they may be accessed on other threads.
@property (atomic, nullable) NSString *localProfileName;
@property (atomic, nullable) UIImage *localProfileAvatarImage;
@property (atomic, nullable) AvatarMetadata *localProfileAvatarMetadata;
// This property should only be mutated on the main thread,
@property (nonatomic, readonly) NSCache<NSString *, UIImage *> *otherUsersProfileAvatarImageCache;
@end
@ -104,8 +126,6 @@ static const NSInteger kProfileKeyLength = 16;
@implementation OWSProfilesManager
@synthesize localProfileKey = _localProfileKey;
+ (instancetype)sharedManager
{
static OWSProfilesManager *sharedMyManager = nil;
@ -133,11 +153,16 @@ static const NSInteger kProfileKeyLength = 16;
return self;
}
OWSAssert([NSThread isMainThread]);
OWSAssert(storageManager);
OWSAssert(messageSender);
_messageSender = messageSender;
_dbConnection = storageManager.newDatabaseConnection;
_userProfileWhitelistCache = [NSMutableDictionary new];
_groupProfileWhitelistCache = [NSMutableDictionary new];
_otherUsersProfileKeyCache = [NSMutableDictionary new];
_otherUsersProfileAvatarImageCache = [NSCache new];
OWSSingletonAssert();
@ -158,8 +183,6 @@ static const NSInteger kProfileKeyLength = 16;
}
OWSAssert(_localProfileKey.length == kProfileKeyLength);
[self loadLocalProfileAsync];
return self;
}
@ -181,6 +204,48 @@ static const NSInteger kProfileKeyLength = 16;
// Do nothing; we only want to make sure this singleton is created on startup.
}
#pragma mark - User Profile Accessor
- (UserProfile *)getOrCreateUserProfileForRecipientId:(NSString *)recipientId
{
OWSAssert([NSThread isMainThread]);
OWSAssert(recipientId.length > 0);
__block UserProfile *instance;
// Make sure to read on the local db connection for consistency.
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
instance = [UserProfile fetchObjectWithUniqueID:recipientId transaction:transaction];
if (!instance) {
instance = [[UserProfile alloc] initWithRecipientId:recipientId
profileName:nil
avatarUrl:nil
avatarDigest:nil
avatarFileName:nil];
[instance saveWithTransaction:transaction];
}
}];
OWSAssert(instance);
return instance;
}
- (nullable UserProfile *)getOrCreateLocalUserProfile
{
OWSAssert([NSThread isMainThread]);
if (!self.localUserProfile) {
NSString *_Nullable recipientId = [TSAccountManager localNumber];
if (!recipientId) {
OWSFail(@"Missing local number.");
return nil;
}
self.localUserProfile = [self getOrCreateUserProfileForRecipientId:recipientId];
}
return self.localUserProfile;
}
#pragma mark - Local Profile Key
+ (NSData *)generateLocalProfileKey
@ -192,51 +257,22 @@ static const NSInteger kProfileKeyLength = 16;
#pragma mark - Local Profile
// This method is use to update client "local profile" state.
- (void)updateLocalProfileName:(nullable NSString *)localProfileName
localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage
localProfileAvatarMetadata:(nullable AvatarMetadata *)localProfileAvatarMetadata
- (NSString *)localProfileName
{
OWSAssert([NSThread isMainThread]);
// The avatar image and filename should both be set, or neither should be set.
if (!localProfileAvatarMetadata && localProfileAvatarImage) {
OWSFail(@"Missing avatar metadata.");
localProfileAvatarImage = nil;
}
if (localProfileAvatarMetadata && !localProfileAvatarImage) {
OWSFail(@"Missing avatar image.");
localProfileAvatarMetadata = nil;
}
self.localProfileName = localProfileName;
self.localProfileAvatarImage = localProfileAvatarImage;
self.localProfileAvatarMetadata = localProfileAvatarMetadata;
return [self getOrCreateLocalUserProfile].profileName;
}
if (localProfileName) {
[self.dbConnection setObject:localProfileName
forKey:kOWSProfilesManager_LocalProfileNameKey
inCollection:kOWSProfilesManager_Collection];
} else {
[self.dbConnection removeObjectForKey:kOWSProfilesManager_LocalProfileNameKey
inCollection:kOWSProfilesManager_Collection];
}
if (localProfileAvatarMetadata) {
[self.dbConnection setObject:localProfileAvatarMetadata
forKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey
inCollection:kOWSProfilesManager_Collection];
} else {
[self.dbConnection removeObjectForKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey
inCollection:kOWSProfilesManager_Collection];
}
- (UIImage *)localProfileAvatarImage
{
OWSAssert([NSThread isMainThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
return self.localCachedAvatarImage;
}
- (void)updateLocalProfileName:(nullable NSString *)localProfileName
localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage
- (void)updateLocalProfileName:(nullable NSString *)profileName
avatarImage:(nullable UIImage *)avatarImage
success:(void (^)())successBlock
failure:(void (^)())failureBlockParameter
{
@ -255,15 +291,31 @@ static const NSInteger kProfileKeyLength = 16;
//
// * Try to update the service.
// * Update client state on success.
void (^tryToUpdateService)(AvatarMetadata *_Nullable) = ^(AvatarMetadata *_Nullable avatarMetadata) {
[self updateProfileOnService:localProfileName
avatarMetadata:avatarMetadata
void (^tryToUpdateService)(NSString *_Nullable, NSString *_Nullable, NSString *_Nullable) = ^(
NSString *_Nullable avatarUrl, NSString *_Nullable avatarDigest, NSString *_Nullable avatarFileName) {
[self updateProfileOnService:profileName
avatarUrl:avatarUrl
avatarDigest:avatarDigest
success:^{
// All reads and writes to user profiles should happen on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self updateLocalProfileName:localProfileName
localProfileAvatarImage:localProfileAvatarImage
localProfileAvatarMetadata:avatarMetadata];
UserProfile *userProfile = [self getOrCreateLocalUserProfile];
OWSAssert(userProfile);
userProfile.profileName = profileName;
userProfile.avatarUrl = avatarUrl;
userProfile.avatarDigest = avatarDigest;
userProfile.avatarFileName = avatarFileName;
// Make sure to save on the local db connection for consistency.
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[userProfile saveWithTransaction:transaction];
}];
self.localCachedAvatarImage = avatarImage;
successBlock();
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
});
}
failure:^{
@ -271,24 +323,31 @@ static const NSInteger kProfileKeyLength = 16;
}];
};
UserProfile *userProfile = [self getOrCreateLocalUserProfile];
OWSAssert(userProfile);
// If we have a new avatar image, we must first:
//
// * Encode it to JPEG.
// * Write it to disk.
// * Upload it to service.
if (localProfileAvatarImage) {
if (self.localProfileAvatarMetadata && self.localProfileAvatarImage == localProfileAvatarImage) {
if (avatarImage) {
if (self.localCachedAvatarImage == avatarImage) {
OWSAssert(userProfile.avatarUrl.length > 0);
OWSAssert(userProfile.avatarDigest.length > 0);
OWSAssert(userProfile.avatarFileName.length > 0);
DDLogVerbose(@"%@ Updating local profile on service with unchanged avatar.", self.tag);
// If the avatar hasn't changed, reuse the existing metadata.
tryToUpdateService(self.localProfileAvatarMetadata);
tryToUpdateService(userProfile.avatarUrl, userProfile.avatarDigest, userProfile.avatarFileName);
} else {
DDLogVerbose(@"%@ Updating local profile on service with new avatar.", self.tag);
[self writeAvatarToDisk:localProfileAvatarImage
[self writeAvatarToDisk:avatarImage
success:^(NSData *data, NSString *fileName) {
[self uploadAvatarToService:data
fileName:fileName
success:^(AvatarMetadata *avatarMetadata) {
tryToUpdateService(avatarMetadata);
success:^(NSString *avatarUrl, NSString *avatarDigest) {
tryToUpdateService(avatarUrl, avatarDigest, fileName);
}
failure:^{
failureBlock();
@ -300,7 +359,7 @@ static const NSInteger kProfileKeyLength = 16;
}
} else {
DDLogVerbose(@"%@ Updating local profile on service with no avatar.", self.tag);
tryToUpdateService(nil);
tryToUpdateService(nil, nil, nil);
}
}
@ -334,7 +393,7 @@ static const NSInteger kProfileKeyLength = 16;
// TODO: The exact API & encryption scheme for avatars is not yet settled.
- (void)uploadAvatarToService:(NSData *)data
fileName:(NSString *)fileName
success:(void (^)(AvatarMetadata *avatarMetadata))successBlock
success:(void (^)(NSString *avatarUrl, NSString *avatarDigest))successBlock
failure:(void (^)())failureBlock
{
OWSAssert(data.length > 0);
@ -345,11 +404,9 @@ static const NSInteger kProfileKeyLength = 16;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// TODO:
NSString *avatarUrl = @"avatarUrl";
NSString *avatarDigest = @"digest";
AvatarMetadata *avatarMetadata =
[[AvatarMetadata alloc] initWithFileName:fileName avatarUrl:avatarUrl avatarDigest:avatarDigest];
NSString *avatarDigest = @"avatarDigest";
if (YES) {
successBlock(avatarMetadata);
successBlock(avatarUrl, avatarDigest);
return;
}
failureBlock();
@ -358,7 +415,8 @@ static const NSInteger kProfileKeyLength = 16;
// TODO: The exact API & encryption scheme for profiles is not yet settled.
- (void)updateProfileOnService:(nullable NSString *)localProfileName
avatarMetadata:(nullable AvatarMetadata *)avatarMetadata
avatarUrl:(nullable NSString *)avatarUrl
avatarDigest:(nullable NSString *)avatarDigest
success:(void (^)())successBlock
failure:(void (^)())failureBlock
{
@ -375,32 +433,201 @@ static const NSInteger kProfileKeyLength = 16;
});
}
- (void)loadLocalProfileAsync
#pragma mark - Profile Whitelist
- (void)addUserToProfileWhitelist:(NSString *)recipientId
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *_Nullable localProfileName = [self.dbConnection objectForKey:kOWSProfilesManager_LocalProfileNameKey
inCollection:kOWSProfilesManager_Collection];
AvatarMetadata *_Nullable localProfileAvatarMetadata =
[self.dbConnection objectForKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey
inCollection:kOWSProfilesManager_Collection];
UIImage *_Nullable localProfileAvatarImage = nil;
if (localProfileAvatarMetadata) {
localProfileAvatarImage = [self loadProfileAvatarWithFilename:localProfileAvatarMetadata.fileName];
if (!localProfileAvatarImage) {
localProfileAvatarMetadata = nil;
}
OWSAssert([NSThread isMainThread]);
OWSAssert(recipientId.length > 0);
[self.dbConnection setBool:YES forKey:recipientId inCollection:kOWSProfilesManager_UserWhitelistCollection];
self.userProfileWhitelistCache[recipientId] = @(YES);
}
- (void)addUsersToProfileWhitelist:(NSArray<NSString *> *)recipientIds
{
OWSAssert([NSThread isMainThread]);
OWSAssert(recipientIds);
NSMutableArray<NSString *> *newRecipientIds = [NSMutableArray new];
for (NSString *recipientId in recipientIds) {
if (!self.userProfileWhitelistCache[recipientId]) {
[newRecipientIds addObject:recipientId];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.localProfileName = localProfileName;
self.localProfileAvatarImage = localProfileAvatarImage;
self.localProfileAvatarMetadata = localProfileAvatarMetadata;
if (newRecipientIds.count < 1) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
});
});
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in recipientIds) {
[transaction setObject:@(YES) forKey:recipientId inCollection:kOWSProfilesManager_UserWhitelistCollection];
self.userProfileWhitelistCache[recipientId] = @(YES);
}
}];
}
- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
NSNumber *_Nullable value = self.userProfileWhitelistCache[recipientId];
if (value) {
return [value boolValue];
}
value = @([self.dbConnection hasObjectForKey:recipientId inCollection:kOWSProfilesManager_UserWhitelistCollection]);
self.userProfileWhitelistCache[recipientId] = value;
return [value boolValue];
}
- (void)addGroupIdToProfileWhitelist:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
NSString *groupIdKey = [groupId hexadecimalString];
[self.dbConnection setObject:@(1) forKey:groupIdKey inCollection:kOWSProfilesManager_GroupWhitelistCollection];
self.groupProfileWhitelistCache[groupIdKey] = @(YES);
}
- (BOOL)isGroupIdInProfileWhitelist:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
NSString *groupIdKey = [groupId hexadecimalString];
NSNumber *_Nullable value = self.groupProfileWhitelistCache[groupIdKey];
if (value) {
return [value boolValue];
}
value =
@(nil != [self.dbConnection objectForKey:groupIdKey inCollection:kOWSProfilesManager_GroupWhitelistCollection]);
self.groupProfileWhitelistCache[groupIdKey] = value;
return [value boolValue];
}
- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread
{
OWSAssert(thread);
if (thread.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
NSData *groupId = groupThread.groupModel.groupId;
return [OWSProfilesManager.sharedManager isGroupIdInProfileWhitelist:groupId];
} else {
NSString *recipientId = thread.contactIdentifier;
return [OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipientId];
}
}
- (void)setContactRecipientIds:(NSArray<NSString *> *)contactRecipientIds
{
OWSAssert([NSThread isMainThread]);
OWSAssert(contactRecipientIds);
// TODO: The persisted whitelist could either be:
//
// * Just users manually added to the whitelist.
// * Also include users auto-added by, for example, being in the user's
// contacts or when the user initiates a 1:1 conversation with them, etc.
[self addUsersToProfileWhitelist:contactRecipientIds];
}
#pragma mark - Other User's Profiles
- (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId
{
OWSAssert(profileKey.length == kProfileKeyLength);
OWSAssert(recipientId.length > 0);
if (profileKey.length != kProfileKeyLength) {
return;
}
NSData *_Nullable existingProfileKey = [self profileKeyForRecipientId:recipientId];
if (existingProfileKey &&
[existingProfileKey isEqual:profileKey]) {
// Ignore redundant update.
return;
}
[self.dbConnection setObject:profileKey
forKey:recipientId
inCollection:kOWSProfilesManager_OtherUsersProfileKeysCollection];
self.otherUsersProfileKeyCache[recipientId] = profileKey;
}
- (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
NSData *_Nullable profileKey = self.otherUsersProfileKeyCache[recipientId];
if (profileKey.length > 0) {
return profileKey;
}
profileKey =
[self.dbConnection dataForKey:recipientId inCollection:kOWSProfilesManager_OtherUsersProfileKeysCollection];
if (profileKey) {
OWSAssert(profileKey.length == kProfileKeyLength);
self.otherUsersProfileKeyCache[recipientId] = profileKey;
}
return profileKey;
}
- (nullable NSString *)profileNameForRecipientId:(NSString *)recipientId
{
OWSAssert([NSThread isMainThread]);
OWSAssert(recipientId.length > 0);
[self fetchProfileForRecipientId:recipientId];
UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId];
return userProfile.profileName;
}
- (nullable UIImage *)profileAvatarForRecipientId:(NSString *)recipientId
{
OWSAssert([NSThread isMainThread]);
OWSAssert(recipientId.length > 0);
[self fetchProfileForRecipientId:recipientId];
UIImage *_Nullable image = [self.otherUsersProfileAvatarImageCache objectForKey:recipientId];
if (image) {
return image;
}
UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId];
if (userProfile.avatarFileName) {
image = [self loadProfileAvatarWithFilename:userProfile.avatarFileName];
if (image) {
[self.otherUsersProfileAvatarImageCache setObject:image forKey:recipientId];
}
}
return image;
}
- (void)fetchProfileForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId];
// Throttle and debounce the updates.
const NSTimeInterval kMaxRefreshFrequency = 5 * kMinuteInterval;
if (userProfile.lastUpdateDate && fabs([userProfile.lastUpdateDate timeIntervalSinceNow]) < kMaxRefreshFrequency) {
// This profile was updated recently or already has an update in flight.
return;
}
userProfile.lastUpdateDate = [NSDate new];
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[userProfile saveWithTransaction:transaction];
}];
// TODO: Actually update the profile.
}
#pragma mark - Avatar Disk Cache

@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface YapDatabaseConnection (OWS)
- (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection;
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection;
- (int)intForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection;
@ -26,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection;
- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection;
- (void)removeObjectForKey:(NSString *)string inCollection:(NSString *)collection;
- (void)setInt:(int)integer forKey:(NSString *)key inCollection:(NSString *)collection;
- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection;

@ -12,6 +12,14 @@ NS_ASSUME_NONNULL_BEGIN
@implementation YapDatabaseConnection (OWS)
- (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection
{
OWSAssert(key.length > 0);
OWSAssert(collection.length > 0);
return nil != [self objectForKey:key inCollection:collection];
}
- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection
{
OWSAssert(key.length > 0);
@ -104,6 +112,14 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection
{
OWSAssert(key.length > 0);
OWSAssert(collection.length > 0);
[self setObject:@(value) forKey:key inCollection:collection];
}
- (void)removeObjectForKey:(NSString *)key inCollection:(NSString *)collection
{
OWSAssert(key.length > 0);
@ -114,12 +130,12 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (void)setInt:(int)integer forKey:(NSString *)key inCollection:(NSString *)collection
- (void)setInt:(int)value forKey:(NSString *)key inCollection:(NSString *)collection
{
OWSAssert(key.length > 0);
OWSAssert(collection.length > 0);
[self setObject:@(integer) forKey:key inCollection:collection];
[self setObject:@(value) forKey:key inCollection:collection];
}
- (int)incrementIntForKey:(NSString *)key inCollection:(NSString *)collection

Loading…
Cancel
Save