You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m

4674 lines
236 KiB
Objective-C

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "DebugUIMessages.h"
#import "DebugUIContacts.h"
#import "DebugUIMessagesAction.h"
#import "DebugUIMessagesAssetLoader.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
#import <Curve25519Kit/Randomness.h>
#import <SignalMessaging/Environment.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/OWSBatchMessageProcessor.h>
#import <SignalServiceKit/OWSDisappearingConfigurationUpdateInfoMessage.h>
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSMessageUtils.h>
#import <SignalServiceKit/OWSPrimaryStorage+SessionStore.h>
#import <SignalServiceKit/OWSSyncGroupsRequestMessage.h>
#import <SignalServiceKit/OWSVerificationStateChangeMessage.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <SignalServiceKit/TSInvalidIdentityKeyReceivingErrorMessage.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN
@interface TSIncomingMessage (DebugUI)
@property (nonatomic, getter=wasRead) BOOL read;
@end
#pragma mark -
@interface TSOutgoingMessage (PostDatingDebug)
- (void)setReceivedAtTimestamp:(uint64_t)value;
@end
#pragma mark -
@implementation DebugUIMessages
#pragma mark - Factory Methods
- (NSString *)name
{
return @"Messages";
}
#ifdef DEBUG
- (NSArray<OWSTableItem *> *)itemsForActions:(NSArray<DebugUIMessagesAction *> *)actions
{
NSMutableArray<OWSTableItem *> *items = [NSMutableArray new];
for (DebugUIMessagesAction *action in actions) {
[items addObject:[OWSTableItem itemWithTitle:action.label
actionBlock:^{
// For "all in group" actions, do each subaction in the group
// exactly once, in a predictable order.
if ([action isKindOfClass:[DebugUIMessagesGroupAction class]]) {
DebugUIMessagesGroupAction *groupAction
= (DebugUIMessagesGroupAction *)action;
if (groupAction.subactionMode == SubactionMode_Ordered) {
[action prepareAndPerformNTimes:groupAction.subactions.count];
return;
}
}
[DebugUIMessages performActionNTimes:action];
}]];
}
return items;
}
#endif
- (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread
{
OWSAssert(thread);
NSMutableArray<OWSTableItem *> *items = [NSMutableArray new];
#ifdef DEBUG
[items addObject:[OWSTableItem itemWithTitle:@"Delete all messages in thread"
actionBlock:^{
[DebugUIMessages deleteAllMessagesInThread:thread];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"👷 Thrash insert/deletes"
actionBlock:^{
[DebugUIMessages thrashInsertAndDeleteForThread:(TSThread *)thread
counter:300];
}]];
[items addObjectsFromArray:[self itemsForActions:@[
[DebugUIMessages fakeAllContactShareAction:thread],
[DebugUIMessages sendMessageVariationsAction:thread],
// Send Media
[DebugUIMessages sendAllMediaAction:thread],
[DebugUIMessages sendRandomMediaAction:thread],
// Fake Media
[DebugUIMessages fakeAllMediaAction:thread],
[DebugUIMessages fakeRandomMediaAction:thread],
// Fake Text
[DebugUIMessages fakeAllTextAction:thread],
[DebugUIMessages fakeRandomTextAction:thread],
// Sequences
[DebugUIMessages allFakeSequencesAction:thread],
// Quoted Replies
[DebugUIMessages allQuotedReplyAction:thread],
// Exemplary
[DebugUIMessages allFakeAction:thread],
[DebugUIMessages allFakeBackDatedAction:thread],
]]];
[items addObjectsFromArray:@[
#pragma mark - Actions
[OWSTableItem itemWithTitle:@"Send N text messages (1/sec.)"
actionBlock:^{
[DebugUIMessages sendNTextMessagesInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Select Fake"
actionBlock:^{
[DebugUIMessages selectFakeAction:thread];
}],
[OWSTableItem itemWithTitle:@"Select Send Media"
actionBlock:^{
[DebugUIMessages selectSendMediaAction:thread];
}],
[OWSTableItem itemWithTitle:@"Send All Contact Shares"
actionBlock:^{
[DebugUIMessages sendAllContacts:thread];
}],
[OWSTableItem itemWithTitle:@"Select Quoted Reply"
actionBlock:^{
[DebugUIMessages selectQuotedReplyAction:thread];
}],
[OWSTableItem itemWithTitle:@"Select Back-Dated"
actionBlock:^{
[DebugUIMessages selectBackDatedAction:thread];
}],
#pragma mark - Misc.
[OWSTableItem itemWithTitle:@"Perform 100 random actions"
actionBlock:^{
[DebugUIMessages performRandomActions:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Perform 1,000 random actions"
actionBlock:^{
[DebugUIMessages performRandomActions:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 1 fake thread with 1 message"
actionBlock:^{
[DebugUIMessages createFakeThreads:1 withFakeMessages:1];
}],
[OWSTableItem itemWithTitle:@"Create 100 fake threads with 10 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:100 withFakeMessages:10];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake threads with 100 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:10 withFakeMessages:100];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake threads with 10 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:10 withFakeMessages:10];
}],
[OWSTableItem itemWithTitle:@"Create 100 fake threads with 100 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:100 withFakeMessages:100];
}],
[OWSTableItem itemWithTitle:@"Create 1k fake threads with 1 message"
actionBlock:^{
[DebugUIMessages createFakeThreads:1000 withFakeMessages:1];
}],
[OWSTableItem itemWithTitle:@"Create 1k fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10k fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:10 * 1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 100k fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:100 * 1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 100k fake text messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:100 * 1000 thread:thread isTextOnly:YES];
}],
[OWSTableItem itemWithTitle:@"Send text/x-signal-plain"
actionBlock:^{
[DebugUIMessages sendOversizeTextMessage:thread];
}],
[OWSTableItem itemWithTitle:@"Send unknown mimetype"
actionBlock:^{
[DebugUIMessages sendRandomAttachment:thread uti:kUnknownTestAttachmentUTI];
}],
[OWSTableItem itemWithTitle:@"Send pdf"
actionBlock:^{
[DebugUIMessages sendRandomAttachment:thread uti:(NSString *)kUTTypePDF];
}],
[OWSTableItem itemWithTitle:@"Create all system messages"
actionBlock:^{
[DebugUIMessages createSystemMessagesInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Create messages with variety of timestamps"
actionBlock:^{
[DebugUIMessages createTimestampMessagesInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 10 text and system messages"
actionBlock:^{
[DebugUIMessages sendTextAndSystemMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 100 text and system messages"
actionBlock:^{
[DebugUIMessages sendTextAndSystemMessages:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 1,000 text and system messages"
actionBlock:^{
[DebugUIMessages sendTextAndSystemMessages:1000 thread:thread];
}],
[OWSTableItem
itemWithTitle:@"Request Bogus group info"
actionBlock:^{
OWSLogInfo(@"Requesting bogus group info for thread: %@", thread);
OWSSyncGroupsRequestMessage *syncGroupsRequestMessage =
[[OWSSyncGroupsRequestMessage alloc] initWithThread:thread
groupId:[Randomness generateRandomBytes:16]];
[[Environment current].messageSender enqueueMessage:syncGroupsRequestMessage
success:^{
OWSLogWarn(@"Successfully sent Request Group Info message.");
}
failure:^(NSError *error) {
OWSLogError(@"Failed to send Request Group Info message with error: %@", error);
}];
}],
[OWSTableItem itemWithTitle:@"Message with stalled timer"
actionBlock:^{
[DebugUIMessages createDisappearingMessagesWhichFailedToStartInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Inject 10 fake incoming messages"
actionBlock:^{
[DebugUIMessages injectFakeIncomingMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Inject 100 fake incoming messages"
actionBlock:^{
[DebugUIMessages injectFakeIncomingMessages:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Inject 1,000 fake incoming messages"
actionBlock:^{
[DebugUIMessages injectFakeIncomingMessages:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Test Indic Scripts"
actionBlock:^{
[DebugUIMessages testIndicScriptsInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Test Zalgo"
actionBlock:^{
[DebugUIMessages testZalgoTextInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Test Directional Filenames"
actionBlock:^{
[DebugUIMessages testDirectionalFilenamesInThread:thread];
}],
]];
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
NSString *recipientId = contactThread.contactIdentifier;
[items addObject:[OWSTableItem itemWithTitle:@"Create 10 new groups"
actionBlock:^{
[DebugUIMessages createNewGroups:10 recipientId:recipientId];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"Create 100 new groups"
actionBlock:^{
[DebugUIMessages createNewGroups:100 recipientId:recipientId];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"Create 1,000 new groups"
actionBlock:^{
[DebugUIMessages createNewGroups:1000 recipientId:recipientId];
}]];
}
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
[items addObject:[OWSTableItem itemWithTitle:@"Send message to all members"
actionBlock:^{
[DebugUIMessages sendMessages:1 toAllMembersOfGroup:groupThread];
}]];
}
#endif
return [OWSTableSection sectionWithTitle:self.name items:items];
}
#ifdef DEBUG
+ (void)sendMessages:(NSUInteger)count toAllMembersOfGroup:(TSGroupThread *)groupThread
{
for (NSString *recipientId in groupThread.groupModel.groupMemberIds) {
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
[[self sendTextMessagesActionInThread:contactThread] prepareAndPerformNTimes:count];
}
}
+ (void)sendTextMessageInThread:(TSThread *)thread counter:(NSUInteger)counter
{
OWSLogInfo(@"sendTextMessageInThread: %zd", counter);
[DDLog flushLog];
NSString *randomText = [self randomText];
NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText];
OWSMessageSender *messageSender = [Environment current].messageSender;
TSOutgoingMessage *message =
[ThreadUtil sendMessageWithText:text inThread:thread quotedReplyModel:nil messageSender:messageSender];
OWSLogError(@"sendTextMessageInThread timestamp: %llu.", message.timestamp);
}
+ (void)sendNTextMessagesInThread:(TSThread *)thread
{
[self performActionNTimes:[self sendTextMessagesActionInThread:thread]];
}
+ (DebugUIMessagesAction *)sendTextMessagesActionInThread:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction actionWithLabel:@"Send Text Message"
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[self sendTextMessageInThread:thread counter:index];
// TODO:
success();
});
}];
}
+ (void)sendAttachment:(NSString *)filePath
thread:(TSThread *)thread
label:(NSString *)label
hasCaption:(BOOL)hasCaption
success:(nullable void (^)(void))success
failure:(nullable void (^)(void))failure
{
OWSAssert(filePath);
OWSAssert(thread);
OWSMessageSender *messageSender = [Environment current].messageSender;
NSString *filename = [filePath lastPathComponent];
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension];
DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:NO];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal];
NSString *messageBody = nil;
if (hasCaption) {
// We want a message body that is "more than one line on all devices,
// using all dynamic type sizes."
NSString *sampleText = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, "
@"consectetur adipiscing elit.";
messageBody = [[label stringByAppendingString:@" "] stringByAppendingString:sampleText];
messageBody = [messageBody stringByAppendingString:@" 🔤"];
}
attachment.captionText = messageBody;
OWSAssert(attachment);
if ([attachment hasError]) {
OWSLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
[DDLog flushLog];
}
OWSAssert(![attachment hasError]);
[ThreadUtil sendMessageWithAttachment:attachment
inThread:thread
quotedReplyModel:nil
messageSender:messageSender
completion:nil];
success();
}
#pragma mark - Infrastructure
+ (void)performActionNTimes:(DebugUIMessagesAction *)action
{
OWSAssertIsOnMainThread();
OWSAssert(action);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"How many?"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
for (NSNumber *countValue in @[
@(1),
@(10),
@(100),
@(1 * 1000),
@(10 * 1000),
]) {
[alert addAction:[UIAlertAction actionWithTitle:countValue.stringValue
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *ignore) {
[action prepareAndPerformNTimes:countValue.unsignedIntegerValue];
}]];
}
[alert addAction:[OWSAlerts cancelAction]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Send Media
+ (NSArray<DebugUIMessagesAction *> *)allSendMediaActions:(TSThread *)thread
{
OWSAssert(thread);
NSArray<DebugUIMessagesAction *> *actions = @[
[self sendJpegAction:thread hasCaption:NO],
[self sendJpegAction:thread hasCaption:YES],
[self sendGifAction:thread hasCaption:NO],
[self sendGifAction:thread hasCaption:YES],
[self sendLargeGifAction:thread hasCaption:NO],
[self sendLargeGifAction:thread hasCaption:YES],
[self sendMp3Action:thread hasCaption:NO],
[self sendMp3Action:thread hasCaption:YES],
[self sendMp4Action:thread hasCaption:NO],
[self sendMp4Action:thread hasCaption:YES],
];
return actions;
}
+ (DebugUIMessagesAction *)sendJpegAction:(TSThread *)thread hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Jpeg"
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)sendGifAction:(TSThread *)thread hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Gif"
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)sendLargeGifAction:(TSThread *)thread hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Large Gif"
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largeGifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)sendMp3Action:(TSThread *)thread hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Mp3"
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)sendMp4Action:(TSThread *)thread hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Mp4"
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)sendMediaAction:(NSString *)labelParam
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
{
OWSAssert(labelParam.length > 0);
OWSAssert(fakeAssetLoader);
OWSAssert(thread);
NSString *label = labelParam;
if (hasCaption) {
label = [label stringByAppendingString:@" 🔤"];
}
return [DebugUIMessagesSingleAction
actionWithLabel:label
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
OWSAssert(fakeAssetLoader.filePath.length > 0);
[self sendAttachment:fakeAssetLoader.filePath
thread:thread
label:label
hasCaption:hasCaption
success:success
failure:failure];
});
}
prepareBlock:fakeAssetLoader.prepareBlock];
}
+ (DebugUIMessagesAction *)sendAllMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Send Media"
subactions:[self allSendMediaActions:thread]];
}
+ (DebugUIMessagesAction *)sendRandomMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction randomGroupActionWithLabel:@"Random Send Media"
subactions:[self allSendMediaActions:thread]];
}
+ (void)selectSendMediaAction:(TSThread *)thread
{
OWSAssert(thread);
[self selectActionUI:[self allSendMediaActions:thread] label:@"Select Send Media"];
}
#pragma mark - Fake Outgoing Media
+ (DebugUIMessagesAction *)fakeOutgoingJpegAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Jpeg"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingGifAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Gif"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingLargeGifAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Large Gif"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largeGifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMp3Action:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Mp3"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMp4Action:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Mp4"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingCompactPortraitPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Portrait Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingCompactLandscapePngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Landscape Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingTallPortraitPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Tall Portrait Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingWideLandscapePngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Wide Landscape Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingLargePngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Large Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingTinyPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Tiny Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingPngAction:(TSThread *)thread
actionLabel:(NSString *)actionLabel
imageSize:(CGSize)imageSize
backgroundColor:(UIColor *)backgroundColor
textColor:(UIColor *)textColor
imageLabel:(NSString *)imageLabel
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:actionLabel
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader pngInstanceWithSize:imageSize
backgroundColor:backgroundColor
textColor:textColor
label:imageLabel]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingTinyPdfAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Tiny Pdf"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingLargePdfAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Large Pdf"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMissingPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Missing Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMissingPdfAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Missing Pdf"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingOversizeTextAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Oversize Text"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader oversizeTextInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMediaAction:(NSString *)labelParam
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
{
OWSAssert(labelParam.length > 0);
OWSAssert(fakeAssetLoader);
OWSAssert(thread);
NSString *label = [labelParam stringByAppendingString:[self actionLabelForHasCaption:hasCaption
outgoingMessageState:messageState
isDelivered:NO
isRead:NO]];
return
[DebugUIMessagesSingleAction actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(fakeAssetLoader.filePath.length > 0);
[self createFakeOutgoingMedia:index
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:fakeAssetLoader
thread:thread
transaction:transaction];
}
prepareBlock:fakeAssetLoader.prepareBlock];
}
+ (void)createFakeOutgoingMedia:(NSUInteger)index
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
OWSAssert(fakeAssetLoader.filePath);
OWSAssert(transaction);
// Random time within last n years. Helpful for filling out a media gallery over time.
// double yearsMillis = 4.0 * kYearsInMs;
// uint64_t millisAgo = (uint64_t)(((double)arc4random() / ((double)0xffffffff)) * yearsMillis);
// uint64_t timestamp = [NSDate ows_millisecondTimeStamp] - millisAgo;
uint64_t timestamp = [NSDate ows_millisecondTimeStamp];
NSString *messageBody = nil;
if (hasCaption) {
// We want a message body that is "more than one line on all devices,
// using all dynamic type sizes."
NSString *sampleText = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, "
@"consectetur adipiscing elit.";
messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:sampleText];
messageBody = [messageBody stringByAppendingString:[self actionLabelForHasCaption:hasCaption
outgoingMessageState:messageState
isDelivered:NO
isRead:NO]];
}
TSOutgoingMessage *message = [self createFakeOutgoingMessage:thread
messageBody:messageBody
fakeAssetLoader:fakeAssetLoader
messageState:messageState
isDelivered:YES
isRead:NO
quotedMessage:nil
contactShare:nil
transaction:transaction];
// This is a hack to "back-date" the message.
[message setReceivedAtTimestamp:timestamp];
[message saveWithTransaction:transaction];
}
#pragma mark - Fake Incoming Media
+ (DebugUIMessagesAction *)fakeIncomingJpegAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Jpeg"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingGifAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Gif"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingLargeGifAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Large Gif"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largeGifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMp3Action:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Mp3"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMp4Action:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Mp4"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingCompactPortraitPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Portrait Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingCompactLandscapePngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Landscape Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingTallPortraitPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Tall Portrait Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingWideLandscapePngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Wide Landscape Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingLargePngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Large Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingTinyPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Tiny Incoming Large Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingPngAction:(TSThread *)thread
actionLabel:(NSString *)actionLabel
imageSize:(CGSize)imageSize
backgroundColor:(UIColor *)backgroundColor
textColor:(UIColor *)textColor
imageLabel:(NSString *)imageLabel
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:actionLabel
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader pngInstanceWithSize:imageSize
backgroundColor:backgroundColor
textColor:textColor
label:imageLabel]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingTinyPdfAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Tiny Pdf"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingLargePdfAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Large Pdf"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMissingPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Missing Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMissingPdfAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Missing Pdf"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingOversizeTextAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Oversize Text"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader oversizeTextInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMediaAction:(NSString *)labelParam
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
{
OWSAssert(labelParam.length > 0);
OWSAssert(fakeAssetLoader);
OWSAssert(thread);
NSString *label = labelParam;
if (hasCaption) {
label = [label stringByAppendingString:@" 🔤"];
}
if (isAttachmentDownloaded) {
label = [label stringByAppendingString:@" 👍"];
}
return
[DebugUIMessagesSingleAction actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(fakeAssetLoader.filePath.length > 0);
[self createFakeIncomingMedia:index
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:fakeAssetLoader
thread:thread
transaction:transaction];
}
prepareBlock:fakeAssetLoader.prepareBlock];
}
+ (TSIncomingMessage *)createFakeIncomingMedia:(NSUInteger)index
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
NSString *_Nullable caption = nil;
if (hasCaption) {
// We want a message body that is "more than one line on all devices,
// using all dynamic type sizes."
caption = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, "
@"consectetur adipiscing elit.";
}
return [self createFakeIncomingMedia:index
isAttachmentDownloaded:isAttachmentDownloaded
caption:caption
fakeAssetLoader:fakeAssetLoader
thread:thread
transaction:transaction];
}
+ (TSIncomingMessage *)createFakeIncomingMedia:(NSUInteger)index
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
caption:(nullable NSString *)caption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
OWSAssert(fakeAssetLoader.filePath);
OWSAssert(transaction);
// // Random time within last n years. Helpful for filling out a media gallery over time.
// double yearsMillis = 4.0 * kYearsInMs;
// uint64_t millisAgo = (uint64_t)(((double)arc4random() / ((double)0xffffffff)) * yearsMillis);
// uint64_t timestamp = [NSDate ows_millisecondTimeStamp] - millisAgo;
NSString *messageBody = nil;
if (caption) {
messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:caption];
messageBody = [messageBody stringByAppendingString:@" 🔤"];
if (isAttachmentDownloaded) {
messageBody = [messageBody stringByAppendingString:@" 👍"];
}
}
return [self createFakeIncomingMessage:thread
messageBody:messageBody
fakeAssetLoader:fakeAssetLoader
isAttachmentDownloaded:isAttachmentDownloaded
quotedMessage:nil
transaction:transaction];
}
#pragma mark - Fake Media
+ (NSArray<DebugUIMessagesAction *> *)allFakeMediaActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Jpeg ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Gif ⚠️"]];
}
[actions addObjectsFromArray:@[
// Don't bother with multiple GIF states.
[self fakeOutgoingGifAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingLargeGifAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Mp3 ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Mp4 ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Compact Landscape Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Compact Portrait Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Wide Landscape Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Tall Portrait Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Large Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingLargePngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingLargePngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Tiny Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingTinyPngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingTinyPngAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Reserved Color Png ⚠️"]];
}
ConversationStyle *conversationStyle = [[ConversationStyle alloc] initWithThread:thread];
[actions addObjectsFromArray:@[
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing White Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[UIColor whiteColor]
textColor:[UIColor ows_signalBrandBlueColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing White Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[UIColor whiteColor]
textColor:[UIColor ows_signalBrandBlueColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSending
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing White Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[UIColor whiteColor]
textColor:[UIColor ows_signalBrandBlueColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSent
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingFailed]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingFailed]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSending
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingFailed]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSent
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingSending]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingSending]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSending
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingSending]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSent
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingSent]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingSent]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSending
hasCaption:YES],
[self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle bubbleColorOutgoingSent]
textColor:[UIColor whiteColor]
imageLabel:@"W"
messageState:TSOutgoingMessageStateSent
hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Tiny Pdf ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateSending hasCaption:YES],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:YES],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateSent hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Large Pdf ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingLargePdfAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Missing Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingMissingPngAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Large Pdf ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingMissingPdfAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Oversize Text ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeOutgoingOversizeTextAction:thread messageState:TSOutgoingMessageStateFailed hasCaption:NO],
[self fakeOutgoingOversizeTextAction:thread messageState:TSOutgoingMessageStateSending hasCaption:NO],
[self fakeOutgoingOversizeTextAction:thread messageState:TSOutgoingMessageStateSent hasCaption:NO],
]];
// Incoming
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Jpg ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Gif ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingGifAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingLargeGifAction:thread isAttachmentDownloaded:YES hasCaption:NO],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Mp3 ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Mp4 ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Compact Landscape Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Compact Portrait Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Wide Landscape Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Tall Portrait Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Large Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingLargePngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingLargePngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Tiny Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingTinyPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingTinyPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Reserved Color Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingPngAction:thread
actionLabel:@"Fake Incoming White Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[UIColor whiteColor]
textColor:[UIColor ows_signalBrandBlueColor]
imageLabel:@"W"
isAttachmentDownloaded:YES
hasCaption:YES],
[self fakeIncomingPngAction:thread
actionLabel:@"Fake Incoming White Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[UIColor whiteColor]
textColor:[UIColor ows_signalBrandBlueColor]
imageLabel:@"W"
isAttachmentDownloaded:NO
hasCaption:YES],
[self fakeIncomingPngAction:thread
actionLabel:@"Fake Incoming 'Incoming' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle primaryColor]
textColor:[UIColor whiteColor]
imageLabel:@"W"
isAttachmentDownloaded:YES
hasCaption:YES],
[self fakeIncomingPngAction:thread
actionLabel:@"Fake Incoming 'Incoming' Png"
imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[conversationStyle primaryColor]
textColor:[UIColor whiteColor]
imageLabel:@"W"
isAttachmentDownloaded:NO
hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Tiny Pdf ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Large Pdf ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingLargePdfAction:thread isAttachmentDownloaded:YES hasCaption:NO],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Missing Png ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingMissingPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMissingPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Missing Pdf ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingMissingPdfAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMissingPdfAction:thread isAttachmentDownloaded:YES hasCaption:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Oversize Text ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeIncomingOversizeTextAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingOversizeTextAction:thread isAttachmentDownloaded:YES hasCaption:NO],
]];
return actions;
}
+ (DebugUIMessagesAction *)fakeAllMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Media"
subactions:[self allFakeMediaActions:thread includeLabels:YES]];
}
+ (DebugUIMessagesAction *)fakeRandomMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction randomGroupActionWithLabel:@"Random Fake Media"
subactions:[self allFakeMediaActions:thread includeLabels:NO]];
}
#pragma mark - Send Text Messages
+ (DebugUIMessagesAction *)sendShortTextMessageAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction actionWithLabel:@"Send Short Text Message"
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[self sendTextMessageInThread:thread counter:index];
});
}];
}
+ (DebugUIMessagesAction *)sendOversizeTextMessageAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction actionWithLabel:@"Send Oversize Text Message"
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[self sendOversizeTextMessage:thread];
});
}];
}
+ (DebugUIMessagesAction *)sendMessageVariationsAction:(TSThread *)thread
{
OWSAssert(thread);
NSArray<DebugUIMessagesAction *> *actions = @[
[self sendShortTextMessageAction:thread],
[self sendOversizeTextMessageAction:thread],
];
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"Send Conversation Cell Variations" subactions:actions];
}
#pragma mark - Fake Text Messages
+ (DebugUIMessagesAction *)fakeShortIncomingTextMessageAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:@"Fake Short Incoming Text Message"
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody =
[[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:[self randomText]];
[self createFakeIncomingMessage:thread
messageBody:messageBody
fakeAssetLoader:nil
isAttachmentDownloaded:NO
quotedMessage:nil
transaction:transaction];
}];
}
+ (SignalAttachment *)signalAttachmentForFilePath:(NSString *)filePath
{
OWSAssert(filePath);
NSString *filename = [filePath lastPathComponent];
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension];
DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:NO];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal];
if (arc4random_uniform(100) > 50) {
attachment.captionText = [self randomCaptionText];
}
OWSAssert(attachment);
if ([attachment hasError]) {
OWSLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
[DDLog flushLog];
}
OWSAssert(![attachment hasError]);
return attachment;
}
+ (void)sendAttachment:(NSString *)filePath
thread:(TSThread *)thread
success:(nullable void (^)(void))success
failure:(nullable void (^)(void))failure
{
OWSAssert(filePath);
OWSAssert(thread);
SignalAttachment *attachment = [self signalAttachmentForFilePath:filePath];
OWSMessageSender *messageSender = [Environment current].messageSender;
[ThreadUtil sendMessageWithAttachment:attachment
inThread:thread
quotedReplyModel:nil
messageSender:messageSender
completion:nil];
success();
}
+ (DebugUIMessagesAction *)fakeIncomingTextMessageAction:(TSThread *)thread text:(NSString *)text
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Incoming Text Message (%@)", text]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:text];
[self createFakeIncomingMessage:thread
messageBody:messageBody
fakeAssetLoader:nil
isAttachmentDownloaded:NO
quotedMessage:nil
transaction:transaction];
}];
}
+ (DebugUIMessagesAction *)fakeOutgoingTextMessageAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
text:(NSString *)text
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Incoming Text Message (%@)", text]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:text];
[self createFakeOutgoingMessage:thread
messageBody:messageBody
fakeAssetLoader:nil
messageState:messageState
isDelivered:NO
isRead:NO
quotedMessage:nil
contactShare:nil
transaction:transaction];
}];
}
+ (DebugUIMessagesAction *)fakeShortOutgoingTextMessageAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
{
return [self fakeShortOutgoingTextMessageAction:thread messageState:messageState isDelivered:NO isRead:NO];
}
+ (DebugUIMessagesAction *)fakeShortOutgoingTextMessageAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
isDelivered:(BOOL)isDelivered
isRead:(BOOL)isRead
{
return [self fakeShortOutgoingTextMessageAction:(TSThread *)thread
text:[self randomText]
messageState:messageState
isDelivered:isDelivered
isRead:isRead];
}
+ (DebugUIMessagesAction *)fakeShortOutgoingTextMessageAction:(TSThread *)thread
text:(NSString *)text
messageState:(TSOutgoingMessageState)messageState
isDelivered:(BOOL)isDelivered
isRead:(BOOL)isRead
{
OWSAssert(thread);
NSString *label = @"Fake Short Incoming Text Message";
label = [label stringByAppendingString:[self actionLabelForHasCaption:YES
outgoingMessageState:messageState
isDelivered:isDelivered
isRead:isRead]];
return [DebugUIMessagesSingleAction
actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:text];
[self createFakeOutgoingMessage:thread
messageBody:messageBody
fakeAssetLoader:nil
messageState:messageState
isDelivered:isDelivered
isRead:isRead
quotedMessage:nil
contactShare:nil
transaction:transaction];
}];
}
+ (NSArray<DebugUIMessagesAction *> *)allFakeTextActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSArray<NSString *> *messageBodies = @[
@"Hi",
@"1",
@"1⃣2",
@"1⃣2⃣3",
@"",
@"",
];
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"⚠️ Incoming Message Bodies ⚠️"]];
}
[actions addObject:[self fakeShortIncomingTextMessageAction:thread]];
for (NSString *messageBody in messageBodies) {
[actions addObject:[self fakeIncomingTextMessageAction:thread text:messageBody]];
}
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Statuses ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeShortOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateFailed],
[self fakeShortOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSending],
[self fakeShortOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSent],
[self fakeShortOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:NO],
[self fakeShortOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:YES],
]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Message Bodies ⚠️"]];
}
for (NSString *messageBody in messageBodies) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:messageBody]];
}
return actions;
}
+ (DebugUIMessagesAction *)fakeAllTextAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Text"
subactions:[self allFakeTextActions:thread includeLabels:YES]];
}
+ (DebugUIMessagesAction *)fakeRandomTextAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction randomGroupActionWithLabel:@"Random Fake Text"
subactions:[self allFakeTextActions:thread includeLabels:NO]];
}
#pragma mark - Fake Quoted Replies
+ (DebugUIMessagesAction *)
fakeQuotedReplyAction:(TSThread *)thread
quotedMessageLabel:(NSString *)quotedMessageLabel
isQuotedMessageIncoming:(BOOL)isQuotedMessageIncoming
// Optional. At least one of quotedMessageBody and quotedMessageAssetLoader should be non-nil.
quotedMessageBody:(nullable NSString *)quotedMessageBody
// Optional. At least one of quotedMessageBody and quotedMessageAssetLoader should be non-nil.
quotedMessageAssetLoader:(nullable DebugUIMessagesAssetLoader *)quotedMessageAssetLoader
replyLabel:(NSString *)replyLabel
isReplyIncoming:(BOOL)isReplyIncoming
replyMessageBody:(nullable NSString *)replyMessageBody
replyAssetLoader:(nullable DebugUIMessagesAssetLoader *)replyAssetLoader
// Only applies if !isReplyIncoming.
replyMessageState:(TSOutgoingMessageState)replyMessageState
{
OWSAssert(thread);
// Used fixed values for properties that shouldn't matter much.
BOOL quotedMessageIsDelivered = NO;
BOOL quotedMessageIsRead = NO;
TSOutgoingMessageState quotedMessageMessageState = TSOutgoingMessageStateSent;
BOOL replyIsDelivered = NO;
BOOL replyIsRead = NO;
// Seamlessly convert oversize text messages to oversize text attachments.
if ([quotedMessageBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
OWSAssert(!quotedMessageAssetLoader);
quotedMessageAssetLoader = [DebugUIMessagesAssetLoader oversizeTextInstanceWithText:quotedMessageBody];
quotedMessageBody = nil;
}
if (replyMessageBody &&
[replyMessageBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
OWSAssert(!replyAssetLoader);
replyAssetLoader = [DebugUIMessagesAssetLoader oversizeTextInstanceWithText:replyMessageBody];
replyMessageBody = nil;
}
NSMutableString *label = [NSMutableString new];
[label appendString:@"Quoted Reply ("];
[label appendString:replyLabel];
if (isReplyIncoming) {
} else {
[label appendString:[self actionLabelForHasCaption:NO
outgoingMessageState:replyMessageState
isDelivered:replyIsDelivered
isRead:replyIsRead]];
}
[label appendString:@") to ("];
[label appendString:quotedMessageLabel];
if (quotedMessageAssetLoader) {
[label appendFormat:@" %@", quotedMessageAssetLoader.labelEmoji];
}
if (isQuotedMessageIncoming) {
} else {
[label appendString:[self actionLabelForHasCaption:quotedMessageBody.length > 0
outgoingMessageState:quotedMessageMessageState
isDelivered:quotedMessageIsDelivered
isRead:quotedMessageIsRead]];
}
[label appendString:@")"];
NSMutableArray<ActionPrepareBlock> *prepareBlocks = [NSMutableArray new];
if (quotedMessageAssetLoader.prepareBlock) {
[prepareBlocks addObject:quotedMessageAssetLoader.prepareBlock];
}
if (replyAssetLoader.prepareBlock) {
[prepareBlocks addObject:replyAssetLoader.prepareBlock];
}
// We don't need to configure ConversationStyle's view width in this case.
ConversationStyle *conversationStyle = [[ConversationStyle alloc] initWithThread:thread];
return [DebugUIMessagesSingleAction
actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *_Nullable quotedMessageBodyWIndex
= (quotedMessageBody ? [NSString stringWithFormat:@"%zd %@", index, quotedMessageBody] : nil);
TSQuotedMessage *_Nullable quotedMessage = nil;
if (isQuotedMessageIncoming) {
TSIncomingMessage *_Nullable messageToQuote = nil;
messageToQuote = [self createFakeIncomingMessage:thread
messageBody:quotedMessageBodyWIndex
fakeAssetLoader:quotedMessageAssetLoader
isAttachmentDownloaded:YES
quotedMessage:nil
transaction:transaction];
OWSAssert(messageToQuote);
OWSLogVerbose(@"%@", label);
[DDLog flushLog];
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:messageToQuote
isGroupThread:thread.isGroupThread
transaction:transaction
conversationStyle:conversationStyle];
quotedMessage = [
[OWSQuotedReplyModel quotedReplyForSendingWithConversationViewItem:viewItem transaction:transaction]
buildQuotedMessageForSending];
} else {
TSOutgoingMessage *_Nullable messageToQuote = [self createFakeOutgoingMessage:thread
messageBody:quotedMessageBodyWIndex
fakeAssetLoader:quotedMessageAssetLoader
messageState:quotedMessageMessageState
isDelivered:quotedMessageIsDelivered
isRead:quotedMessageIsRead
quotedMessage:nil
contactShare:nil
transaction:transaction];
OWSAssert(messageToQuote);
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:messageToQuote
isGroupThread:thread.isGroupThread
transaction:transaction
conversationStyle:conversationStyle];
quotedMessage = [
[OWSQuotedReplyModel quotedReplyForSendingWithConversationViewItem:viewItem transaction:transaction]
buildQuotedMessageForSending];
}
OWSAssert(quotedMessage);
NSString *_Nullable replyMessageBodyWIndex
= (replyMessageBody ? [NSString stringWithFormat:@"%zd %@", index, replyMessageBody] : nil);
if (isReplyIncoming) {
[self createFakeIncomingMessage:thread
messageBody:replyMessageBodyWIndex
fakeAssetLoader:replyAssetLoader
isAttachmentDownloaded:NO
quotedMessage:quotedMessage
transaction:transaction];
} else {
[self createFakeOutgoingMessage:thread
messageBody:replyMessageBodyWIndex
fakeAssetLoader:replyAssetLoader
messageState:replyMessageState
isDelivered:replyIsDelivered
isRead:replyIsRead
quotedMessage:quotedMessage
contactShare:nil
transaction:transaction];
}
}
prepareBlock:[self groupPrepareBlockWithPrepareBlocks:prepareBlocks]];
}
// Recursively perform a group of "prepare blocks" in sequence, aborting
// if any fail.
+ (ActionPrepareBlock)groupPrepareBlockWithPrepareBlocks:(NSArray<ActionPrepareBlock> *)prepareBlocks
{
return ^(ActionSuccessBlock success, ActionFailureBlock failure) {
[self groupPrepareBlockStepWithPrepareBlocks:[prepareBlocks mutableCopy] success:success failure:failure];
};
}
+ (void)groupPrepareBlockStepWithPrepareBlocks:(NSMutableArray<ActionPrepareBlock> *)prepareBlocks
success:(ActionSuccessBlock)success
failure:(ActionFailureBlock)failure
{
if (prepareBlocks.count < 1) {
success();
return;
}
ActionPrepareBlock nextPrepareBlock = [prepareBlocks lastObject];
[prepareBlocks removeLastObject];
nextPrepareBlock(
^{
[self groupPrepareBlockStepWithPrepareBlocks:prepareBlocks success:success failure:failure];
},
failure);
}
+ (NSArray<DebugUIMessagesAction *> *)allFakeQuotedReplyActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSString *shortText = @"Lorem ipsum";
NSString *mediumText = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, "
@"consectetur adipiscing elit.";
NSString *longText = [self randomOversizeText];
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread
text:@"⚠️ Quoted Replies (Message Lengths) ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Medium Text"
isQuotedMessageIncoming:NO
quotedMessageBody:mediumText
quotedMessageAssetLoader:nil
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Medium Text"
isQuotedMessageIncoming:NO
quotedMessageBody:mediumText
quotedMessageAssetLoader:nil
replyLabel:@"Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Long Text"
isQuotedMessageIncoming:NO
quotedMessageBody:longText
quotedMessageAssetLoader:nil
replyLabel:@"Long Text"
isReplyIncoming:NO
replyMessageBody:longText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread
text:@"⚠️ Quoted Replies (Attachment Types) ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Jpg"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Jpg"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Mp3"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Mp3"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Mp4"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Mp4"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Gif"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Gif"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Pdf"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tinyPdfInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Missing Pdf"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader missingPdfInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tiny Png"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Missing Png"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader missingPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread
text:@"⚠️ Quoted Replies (Attachment Layout) ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tall Portrait Png"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tall Portrait Png"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyLabel:@"Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tall Portrait Png"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Wide Landscape Png"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Wide Landscape Png"
isQuotedMessageIncoming:NO
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyLabel:@"Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Wide Landscape Png"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyLabel:@"Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tiny Png"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tiny Png"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
replyLabel:@"Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
]];
void (^directionActions)(BOOL, BOOL) = ^(BOOL isQuotedMessageIncoming, BOOL isReplyIncoming) {
[actions addObjectsFromArray:@[
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:isQuotedMessageIncoming
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Short Text"
isReplyIncoming:isReplyIncoming
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
]];
};
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread
text:@"⚠️ Quoted Replies (Incoming v. Outgoing) ⚠️"]];
}
directionActions(NO, NO);
directionActions(YES, NO);
directionActions(NO, YES);
directionActions(YES, YES);
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread
text:@"⚠️ Quoted Replies (Message States) ⚠️"]];
}
[actions addObjectsFromArray:@[
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Jpg"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Mp3"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Mp4"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Gif"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Pdf"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tinyPdfInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Missing Pdf"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader missingPdfInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tiny Png"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Missing Png"
isQuotedMessageIncoming:YES
quotedMessageBody:nil
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader missingPngInstance]
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSending],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateFailed],
]];
if (includeLabels) {
[actions addObject:[self fakeIncomingTextMessageAction:thread
text:@"⚠️ Quoted Replies (Reply W. Attachment) ⚠️"]];
}
[actions addObjectsFromArray:@[
// Png + Text -> Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Tall Portrait Png"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyLabel:@"Tall Portrait Png"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Text -> Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Tall Portrait Png"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:nil
replyMessageState:TSOutgoingMessageStateSent],
// Text -> Png
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Tall Portrait Png"
isReplyIncoming:NO
replyMessageBody:nil
replyAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Tall Portrait Png"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Portrait Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Tall Portrait Png"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Landscape Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Wide Landscape Png"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Landscape Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Wide Landscape Png + Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Landscape Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Wide Landscape Png + Short Text"
isReplyIncoming:NO
replyMessageBody:shortText
replyAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Landscape Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Wide Landscape Png + Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyMessageState:TSOutgoingMessageStateSent],
// Png -> Landscape Png + Text
[self fakeQuotedReplyAction:thread
quotedMessageLabel:@"Short Text"
isQuotedMessageIncoming:NO
quotedMessageBody:shortText
quotedMessageAssetLoader:nil
replyLabel:@"Wide Landscape Png + Medium Text"
isReplyIncoming:NO
replyMessageBody:mediumText
replyAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
replyMessageState:TSOutgoingMessageStateSent],
]];
return actions;
}
+ (DebugUIMessagesAction *)allQuotedReplyAction:(TSThread *)thread
{
OWSAssert(thread);
return
[DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Quoted Reply"
subactions:[self allFakeQuotedReplyActions:thread includeLabels:YES]];
}
+ (void)selectQuotedReplyAction:(TSThread *)thread
{
OWSAssertIsOnMainThread();
OWSAssert(thread);
[self selectActionUI:[self allFakeQuotedReplyActions:thread includeLabels:NO] label:@"Select QuotedReply"];
}
+ (DebugUIMessagesAction *)randomQuotedReplyAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction
randomGroupActionWithLabel:@"Random Quoted Reply"
subactions:[self allFakeQuotedReplyActions:thread includeLabels:NO]];
}
#pragma mark - Exemplary
+ (NSArray<DebugUIMessagesAction *> *)allFakeActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
[actions addObjectsFromArray:[self allFakeMediaActions:thread includeLabels:includeLabels]];
[actions addObjectsFromArray:[self allFakeTextActions:thread includeLabels:includeLabels]];
[actions addObjectsFromArray:[self allFakeSequenceActions:thread includeLabels:includeLabels]];
[actions addObjectsFromArray:[self allFakeQuotedReplyActions:thread includeLabels:includeLabels]];
[actions addObjectsFromArray:[self allFakeBackDatedActions:thread includeLabels:includeLabels]];
[actions addObjectsFromArray:[self allFakeContactShareActions:thread includeLabels:includeLabels]];
return actions;
}
+ (DebugUIMessagesAction *)allFakeAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake"
subactions:[self allFakeActions:thread includeLabels:YES]];
}
+ (void)selectFakeAction:(TSThread *)thread
{
OWSAssertIsOnMainThread();
OWSAssert(thread);
[self selectActionUI:[self allFakeActions:thread includeLabels:NO] label:@"Select Fake"];
}
+ (void)selectActionUI:(NSArray<DebugUIMessagesAction *> *)actions label:(NSString *)label
{
OWSAssertIsOnMainThread();
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:label message:nil preferredStyle:UIAlertControllerStyleActionSheet];
for (DebugUIMessagesAction *action in actions) {
[alert addAction:[UIAlertAction actionWithTitle:action.label
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *ignore) {
[self performActionNTimes:action];
}]];
}
[alert addAction:[OWSAlerts cancelAction]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Sequences
+ (NSArray<DebugUIMessagesAction *> *)allFakeSequenceActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Short Message Sequences ⚠️"]];
}
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"Incoming"]];
[actions
addObject:[self fakeOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSent text:@"Outgoing"]];
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"Incoming 1"]];
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"Incoming 2"]];
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"Incoming 3"]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateFailed
text:@"Outgoing Unsent 1"]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateFailed
text:@"Outgoing Unsent 2"]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSending
text:@"Outgoing Sending 1"]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSending
text:@"Outgoing Sending 2"]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"Outgoing Sent 1"]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"Outgoing Sent 2"]];
[actions addObject:[self fakeShortOutgoingTextMessageAction:thread
text:@"Outgoing Delivered 1"
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:NO]];
[actions addObject:[self fakeShortOutgoingTextMessageAction:thread
text:@"Outgoing Delivered 2"
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:NO]];
[actions addObject:[self fakeShortOutgoingTextMessageAction:thread
text:@"Outgoing Read 1"
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:YES]];
[actions addObject:[self fakeShortOutgoingTextMessageAction:thread
text:@"Outgoing Read 2"
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:YES]];
[actions addObject:[self fakeIncomingTextMessageAction:thread text:@"Incoming"]];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Long Message Sequences ⚠️"]];
}
NSString *longText = @"\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla "
@"vitae pretium hendrerit, tellus turpis pharetra libero...";
[actions addObject:[self fakeIncomingTextMessageAction:thread text:[@"Incoming" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:[@"Outgoing" stringByAppendingString:longText]]];
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:[@"Incoming 1" stringByAppendingString:longText]]];
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:[@"Incoming 2" stringByAppendingString:longText]]];
[actions
addObject:[self fakeIncomingTextMessageAction:thread text:[@"Incoming 3" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateFailed
text:[@"Outgoing Unsent 1" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateFailed
text:[@"Outgoing Unsent 2" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSending
text:[@"Outgoing Sending 1" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSending
text:[@"Outgoing Sending 2" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:[@"Outgoing Sent 1" stringByAppendingString:longText]]];
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:[@"Outgoing Sent 2" stringByAppendingString:longText]]];
[actions
addObject:[self fakeShortOutgoingTextMessageAction:thread
text:[@"Outgoing Delivered 1" stringByAppendingString:longText]
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:NO]];
[actions
addObject:[self fakeShortOutgoingTextMessageAction:thread
text:[@"Outgoing Delivered 2" stringByAppendingString:longText]
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:NO]];
[actions addObject:[self fakeShortOutgoingTextMessageAction:thread
text:[@"Outgoing Read 1" stringByAppendingString:longText]
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:YES]];
[actions addObject:[self fakeShortOutgoingTextMessageAction:thread
text:[@"Outgoing Read 2" stringByAppendingString:longText]
messageState:TSOutgoingMessageStateSent
isDelivered:YES
isRead:YES]];
[actions addObject:[self fakeIncomingTextMessageAction:thread text:[@"Incoming" stringByAppendingString:longText]]];
return actions;
}
+ (DebugUIMessagesAction *)allFakeSequencesAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Sequences"
subactions:[self allFakeSequenceActions:thread includeLabels:YES]];
}
#pragma mark - Back-dated
+ (DebugUIMessagesAction *)fakeBackDatedMessageAction:(TSThread *)thread
label:(NSString *)label
dateOffset:(int64_t)dateOffset
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Back-Date Message (%@)", label]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody =
[[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:self.randomText];
TSOutgoingMessage *message = [self createFakeOutgoingMessage:thread
messageBody:messageBody
fakeAssetLoader:nil
messageState:TSOutgoingMessageStateSent
isDelivered:NO
isRead:NO
quotedMessage:nil
contactShare:nil
transaction:transaction];
[message setReceivedAtTimestamp:(uint64_t)((int64_t)[NSDate ows_millisecondTimeStamp] + dateOffset)];
[message saveWithTransaction:transaction];
}];
}
+ (NSArray<DebugUIMessagesAction *> *)allFakeBackDatedActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Back-Dated ⚠️"]];
}
[actions
addObject:[self fakeBackDatedMessageAction:thread label:@"One Minute Ago" dateOffset:-(int64_t)kMinuteInMs]];
[actions addObject:[self fakeBackDatedMessageAction:thread label:@"One Hour Ago" dateOffset:-(int64_t)kHourInMs]];
[actions addObject:[self fakeBackDatedMessageAction:thread label:@"One Day Ago" dateOffset:-(int64_t)kDayInMs]];
[actions
addObject:[self fakeBackDatedMessageAction:thread label:@"Two Days Ago" dateOffset:-(int64_t)kDayInMs * 2]];
[actions
addObject:[self fakeBackDatedMessageAction:thread label:@"Ten Days Ago" dateOffset:-(int64_t)kDayInMs * 10]];
[actions
addObject:[self fakeBackDatedMessageAction:thread label:@"400 Days Ago" dateOffset:-(int64_t)kDayInMs * 400]];
return actions;
}
+ (DebugUIMessagesAction *)allFakeBackDatedAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Back-Dated"
subactions:[self allFakeBackDatedActions:thread includeLabels:YES]];
}
+ (void)selectBackDatedAction:(TSThread *)thread
{
OWSAssertIsOnMainThread();
OWSAssert(thread);
[self selectActionUI:[self allFakeBackDatedActions:thread includeLabels:NO] label:@"Select Back-Dated"];
}
#pragma mark - Contact Shares
typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transaction);
+ (DebugUIMessagesAction *)fakeContactShareMessageAction:(TSThread *)thread
label:(NSString *)label
contactBlock:(OWSContactBlock)contactBlock
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Contact Share (%@)", label]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = contactBlock(transaction);
TSOutgoingMessage *message = [self createFakeOutgoingMessage:thread
messageBody:nil
fakeAssetLoader:nil
messageState:TSOutgoingMessageStateSent
isDelivered:NO
isRead:NO
quotedMessage:nil
contactShare:contact
transaction:transaction];
[message saveWithTransaction:transaction];
}];
}
+ (NSArray<DebugUIMessagesAction *> *)allFakeContactShareActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Share Contact ⚠️"]];
}
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Name & Number"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Alice";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneType = OWSContactPhoneType_Home;
phoneNumber.phoneNumber = @"+13213214321";
contact.phoneNumbers = @[
phoneNumber,
];
return contact;
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Name & Email"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Bob";
OWSContactEmail *email = [OWSContactEmail new];
email.emailType = OWSContactEmailType_Home;
email.email = @"a@b.com";
contact.emails = @[
email,
];
return contact;
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Complicated"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Alice";
name.familyName = @"Carol";
name.middleName = @"Bob";
name.namePrefix = @"Ms.";
name.nameSuffix = @"Esq.";
name.organizationName = @"Falafel Hut";
OWSContactPhoneNumber *phoneNumber1 = [OWSContactPhoneNumber new];
phoneNumber1.phoneType = OWSContactPhoneType_Home;
phoneNumber1.phoneNumber = @"+13213215555";
OWSContactPhoneNumber *phoneNumber2 = [OWSContactPhoneNumber new];
phoneNumber2.phoneType = OWSContactPhoneType_Custom;
phoneNumber2.label = @"Carphone";
phoneNumber2.phoneNumber = @"+13332226666";
contact.phoneNumbers = @[
phoneNumber1,
phoneNumber2,
];
NSMutableArray<OWSContactEmail *> *emails = [NSMutableArray new];
for (NSUInteger i = 0; i < 16; i++) {
OWSContactEmail *email = [OWSContactEmail new];
email.emailType = OWSContactEmailType_Home;
email.email = [NSString stringWithFormat:@"a%zd@b.com", i];
[emails addObject:email];
}
contact.emails = emails;
OWSContactAddress *address1 = [OWSContactAddress new];
address1.addressType = OWSContactAddressType_Home;
address1.street = @"123 home st.";
address1.neighborhood = @"round the bend.";
address1.city = @"homeville";
address1.region = @"HO";
address1.postcode = @"12345";
address1.country = @"USA";
OWSContactAddress *address2 = [OWSContactAddress new];
address2.addressType = OWSContactAddressType_Custom;
address2.label = @"Otra casa";
address2.pobox = @"caja 123";
address2.street = @"123 casa calle";
address2.city = @"barrio norte";
address2.region = @"AB";
address2.postcode = @"53421";
address2.country = @"MX";
contact.addresses = @[
address1,
address2,
];
UIImage *avatarImage =
[OWSAvatarBuilder buildRandomAvatarWithDiameter:200];
[contact saveAvatarImage:avatarImage transaction:transaction];
return contact;
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Long values"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Bobasdjasdlkjasldkjas";
name.familyName = @"Bobasdjasdlkjasldkjas";
OWSContactEmail *email = [OWSContactEmail new];
email.emailType = OWSContactEmailType_Mobile;
email.email = @"asdlakjsaldkjasldkjasdlkjasdlkjasdlkajsa@b.com";
contact.emails = @[
email,
];
return contact;
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"System Contact w/o Signal"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Add Me To Your Contacts";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneType = OWSContactPhoneType_Work;
phoneNumber.phoneNumber = @"+324602053911";
contact.phoneNumbers = @[
phoneNumber,
];
return contact;
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"System Contact w. Signal"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Add Me To Your Contacts";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneType = OWSContactPhoneType_Work;
phoneNumber.phoneNumber = @"+32460205392";
contact.phoneNumbers = @[
phoneNumber,
];
return contact;
}]];
return actions;
}
+ (DebugUIMessagesAction *)fakeAllContactShareAction:(TSThread *)thread
{
OWSAssert(thread);
return
[DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Contact Shares"
subactions:[self allFakeContactShareActions:thread includeLabels:YES]];
}
+ (DebugUIMessagesAction *)sendContactShareMessageAction:(TSThread *)thread
label:(NSString *)label
contactBlock:(OWSContactBlock)contactBlock
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Send Contact Share (%@)", label]
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
OWSContact *contact = contactBlock(transaction);
OWSLogVerbose(@"sending contact: %@", contact.debugDescription);
OWSMessageSender *messageSender = [Environment current].messageSender;
[ThreadUtil sendMessageWithContactShare:contact inThread:thread messageSender:messageSender completion:nil];
success();
}];
}
+ (NSArray<DebugUIMessagesAction *> *)allSendContactShareActions:(TSThread *)thread includeLabels:(BOOL)includeLabels
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
if (includeLabels) {
[actions addObject:[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSent
text:@"⚠️ Send Share Contact ⚠️"]];
}
[actions addObject:[self sendContactShareMessageAction:thread
label:@"Name & Number"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Alice";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneType = OWSContactPhoneType_Home;
phoneNumber.phoneNumber = @"+13213214321";
contact.phoneNumbers = @[
phoneNumber,
];
return contact;
}]];
[actions addObject:[self sendContactShareMessageAction:thread
label:@"Name & Email"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Bob";
OWSContactEmail *email = [OWSContactEmail new];
email.emailType = OWSContactEmailType_Home;
email.email = @"a@b.com";
contact.emails = @[
email,
];
return contact;
}]];
[actions addObject:[self sendContactShareMessageAction:thread
label:@"Complicated"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Alice";
name.familyName = @"Carol";
name.middleName = @"Bob";
name.namePrefix = @"Ms.";
name.nameSuffix = @"Esq.";
name.organizationName = @"Falafel Hut";
OWSContactPhoneNumber *phoneNumber1 = [OWSContactPhoneNumber new];
phoneNumber1.phoneType = OWSContactPhoneType_Home;
phoneNumber1.phoneNumber = @"+13213214321";
OWSContactPhoneNumber *phoneNumber2 = [OWSContactPhoneNumber new];
phoneNumber2.phoneType = OWSContactPhoneType_Custom;
phoneNumber2.label = @"Carphone";
phoneNumber2.phoneNumber = @"+13332221111";
contact.phoneNumbers = @[
phoneNumber1,
phoneNumber2,
];
NSMutableArray<OWSContactEmail *> *emails = [NSMutableArray new];
for (NSUInteger i = 0; i < 16; i++) {
OWSContactEmail *email = [OWSContactEmail new];
email.emailType = OWSContactEmailType_Home;
email.email = [NSString stringWithFormat:@"a%zd@b.com", i];
[emails addObject:email];
}
contact.emails = emails;
OWSContactAddress *address1 = [OWSContactAddress new];
address1.addressType = OWSContactAddressType_Home;
address1.street = @"123 home st.";
address1.neighborhood = @"round the bend.";
address1.city = @"homeville";
address1.region = @"HO";
address1.postcode = @"12345";
address1.country = @"USA";
OWSContactAddress *address2 = [OWSContactAddress new];
address2.addressType = OWSContactAddressType_Custom;
address2.label = @"Otra casa";
address2.pobox = @"caja 123";
address2.street = @"123 casa calle";
address2.city = @"barrio norte";
address2.region = @"AB";
address2.postcode = @"53421";
address2.country = @"MX";
contact.addresses = @[
address1,
address2,
];
UIImage *avatarImage =
[OWSAvatarBuilder buildRandomAvatarWithDiameter:200];
[contact saveAvatarImage:avatarImage transaction:transaction];
return contact;
}]];
[actions addObject:[self sendContactShareMessageAction:thread
label:@"Long values"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Bobasdjasdlkjasldkjas";
name.familyName = @"Bobasdjasdlkjasldkjas";
OWSContactEmail *email = [OWSContactEmail new];
email.emailType = OWSContactEmailType_Mobile;
email.email = @"asdlakjsaldkjasldkjasdlkjasdlkjasdlkajsa@b.com";
contact.emails = @[
email,
];
return contact;
}]];
[actions addObject:[self sendContactShareMessageAction:thread
label:@"System Contact w/o Signal"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Add Me To Your Contacts";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneType = OWSContactPhoneType_Work;
phoneNumber.phoneNumber = @"+324602053911";
contact.phoneNumbers = @[
phoneNumber,
];
return contact;
}]];
[actions addObject:[self sendContactShareMessageAction:thread
label:@"System Contact w. Signal"
contactBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = [OWSContact new];
OWSContactName *name = [OWSContactName new];
contact.name = name;
name.givenName = @"Add Me To Your Contacts";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneType = OWSContactPhoneType_Work;
phoneNumber.phoneNumber = @"+32460205392";
contact.phoneNumbers = @[
phoneNumber,
];
return contact;
}]];
return actions;
}
+ (void)sendAllContacts:(TSThread *)thread
{
NSArray<DebugUIMessagesAction *> *subactions = [self allSendContactShareActions:thread includeLabels:NO];
DebugUIMessagesAction *action =
[DebugUIMessagesGroupAction allGroupActionWithLabel:@"Send All Contact Shares" subactions:subactions];
[action prepareAndPerformNTimes:subactions.count];
}
#pragma mark -
+ (NSString *)randomOversizeText
{
NSMutableString *message = [NSMutableString new];
while (message.length < kOversizeTextMessageSizeThreshold) {
[message appendString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla "
@"vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel "
@"sem. Fusce sed nisl a lorem gravida tincidunt. Suspendisse efficitur non quam ac "
@"sodales. Aenean ut velit maximus, posuere sem a, accumsan nunc. Donec ullamcorper "
@"turpis lorem. Quisque dignissim purus eu placerat ultricies. Proin at urna eget mi "
@"semper congue. Aenean non elementum ex. Praesent pharetra quam at sem vestibulum, "
@"vestibulum ornare dolor elementum. Vestibulum massa tortor, scelerisque sit amet "
@"pulvinar a, rhoncus vitae nisl. Sed mi nunc, tempus at varius in, malesuada vitae "
@"dui. Vivamus efficitur pulvinar erat vitae congue. Proin vehicula turpis non felis "
@"congue facilisis. Nullam aliquet dapibus ligula ac mollis. Etiam sit amet posuere "
@"lorem, in rhoncus nisi.\n\n"];
}
return message;
}
+ (void)sendOversizeTextMessage:(TSThread *)thread
{
OWSMessageSender *messageSender = [Environment current].messageSender;
NSString *message = [self randomOversizeText];
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:message];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
[ThreadUtil sendMessageWithAttachment:attachment
inThread:thread
quotedReplyModel:nil
messageSender:messageSender
completion:nil];
}
+ (NSData *)createRandomNSDataOfSize:(size_t)size
{
OWSAssert(size % 4 == 0);
OWSAssert(size < INT_MAX);
return [Randomness generateRandomBytes:(int)size];
}
+ (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti
{
[self sendRandomAttachment:thread uti:uti length:256];
}
+ (NSString *)randomCaptionText
{
return [NSString stringWithFormat:@"%@ (caption)", [self randomText]];
}
+ (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti length:(NSUInteger)length
{
OWSMessageSender *messageSender = [Environment current].messageSender;
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:length] utiType:uti];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti imageQuality:TSImageQualityOriginal];
if (arc4random_uniform(100) > 50) {
// give 1/2 our attachments captions, and add a hint that it's a caption since we
// style them indistinguishably from a separate text message.
attachment.captionText = [self randomCaptionText];
}
[ThreadUtil sendMessageWithAttachment:attachment
inThread:thread
quotedReplyModel:nil
messageSender:messageSender
ignoreErrors:YES
completion:nil];
}
+ (SSKProtoEnvelope *)createEnvelopeForThread:(TSThread *)thread
{
OWSAssert(thread);
uint64_t timestamp = [NSDate ows_millisecondTimeStamp];
NSString *source = ^{
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *gThread = (TSGroupThread *)thread;
return gThread.groupModel.groupMemberIds[0];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
return contactThread.contactIdentifier;
} else {
OWSFailDebug(@"failure: unknown thread type");
return @"unknown-source-id";
}
}();
SSKProtoEnvelopeBuilder *envelopeBuilder =
[[SSKProtoEnvelopeBuilder alloc] initWithType:SSKProtoEnvelopeTypeCiphertext
source:source
sourceDevice:1
timestamp:timestamp];
NSError *error;
SSKProtoEnvelope *_Nullable envelope = [envelopeBuilder buildAndReturnError:&error];
if (error || !envelope) {
OWSFailDebug(@"Could not construct envelope: %@.", error);
return nil;
}
return envelope;
}
+ (NSArray<TSInteraction *> *)unsavedSystemMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
NSMutableArray<TSInteraction *> *result = [NSMutableArray new];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncoming
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeOutgoing
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncomingMissed
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeOutgoingIncomplete
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncomingIncomplete
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncomingDeclined
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeOutgoingMissed
inThread:contactThread]];
}
{
NSNumber *durationSeconds = [OWSDisappearingMessagesConfiguration validDurationsSeconds][0];
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:YES
durationSeconds:(uint32_t)[durationSeconds intValue]];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:@"Alice"
createdInExistingGroup:NO]];
}
{
NSNumber *durationSeconds = [OWSDisappearingMessagesConfiguration validDurationsSeconds][0];
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:YES
durationSeconds:(uint32_t)[durationSeconds intValue]];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:nil
createdInExistingGroup:YES]];
}
{
NSNumber *durationSeconds = [[OWSDisappearingMessagesConfiguration validDurationsSeconds] lastObject];
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:YES
durationSeconds:(uint32_t)[durationSeconds intValue]];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:@"Alice"
createdInExistingGroup:NO]];
}
{
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:NO
durationSeconds:0];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:@"Alice"
createdInExistingGroup:NO]];
}
[result addObject:[TSInfoMessage userNotRegisteredMessageInThread:thread recipientId:@"+19174054215"]];
[result addObject:[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeSessionDidEnd]];
// TODO: customMessage?
[result addObject:[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeGroupUpdate]];
// TODO: customMessage?
[result addObject:[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeGroupQuit]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateDefault
isLocalChange:YES]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateVerified
isLocalChange:YES]];
[result
addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateNoLongerVerified
isLocalChange:YES]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateDefault
isLocalChange:NO]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateVerified
isLocalChange:NO]];
[result
addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateNoLongerVerified
isLocalChange:NO]];
[result addObject:[TSErrorMessage missingSessionWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSErrorMessage invalidKeyExceptionWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSErrorMessage invalidVersionWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSErrorMessage corruptedMessageWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
TSInvalidIdentityKeyReceivingErrorMessage *_Nullable blockingSNChangeMessage =
[TSInvalidIdentityKeyReceivingErrorMessage untrustedKeyWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction];
OWSAssert(blockingSNChangeMessage);
[result addObject:blockingSNChangeMessage];
[result addObject:[[TSErrorMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
failedMessageType:TSErrorMessageNonBlockingIdentityChange
recipientId:@"+19174054215"]];
}];
return result;
}
+ (void)createSystemMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
NSArray<TSInteraction *> *messages = [self unsavedSystemMessagesInThread:thread];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSInteraction *message in messages) {
[message saveWithTransaction:transaction];
}
}];
}
+ (void)createSystemMessageInThread:(TSThread *)thread
{
OWSAssert(thread);
NSArray<TSInteraction *> *messages = [self unsavedSystemMessagesInThread:thread];
TSInteraction *message = messages[(NSUInteger)arc4random_uniform((uint32_t)messages.count)];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message saveWithTransaction:transaction];
}];
}
+ (void)sendTextAndSystemMessages:(NSUInteger)counter thread:(TSThread *)thread
{
if (counter < 1) {
return;
}
if (arc4random_uniform(2) == 0) {
[self sendTextMessageInThread:thread counter:counter];
} else {
[self createSystemMessageInThread:thread];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self sendTextAndSystemMessages:counter - 1 thread:thread];
});
}
+ (NSString *)randomText
{
NSArray<NSString *> *randomTexts = @[
@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
(@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
@"Suspendisse rutrum, nulla vitae pretium hendrerit, tellus "
@"turpis pharetra libero, vitae sodales tortor ante vel sem."),
@"In a time of universal deceit - telling the truth is a revolutionary act.",
@"If you want a vision of the future, imagine a boot stamping on a human face - forever.",
@"Who controls the past controls the future. Who controls the present controls the past.",
@"All animals are equal, but some animals are more equal than others.",
@"War is peace. Freedom is slavery. Ignorance is strength.",
(@"All the war-propaganda, all the screaming and lies and hatred, comes invariably from people who are not "
@"fighting."),
(@"Political language. . . is designed to make lies sound truthful and murder respectable, and to give an "
@"appearance of solidity to pure wind."),
(@"The nationalist not only does not disapprove of atrocities committed by his own side, but he has a "
@"remarkable capacity for not even hearing about them."),
(@"Every generation imagines itself to be more intelligent than the one that went before it, and wiser than "
@"the "
@"one that comes after it."),
@"War against a foreign country only happens when the moneyed classes think they are going to profit from it.",
@"People have only as much liberty as they have the intelligence to want and the courage to take.",
(@"You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in your "
@"spirit, or it is nowhere."),
(@"That is what I have always understood to be the essence of anarchism: the conviction that the burden of "
@"proof has to be placed on authority, and that it should be dismantled if that burden cannot be met."),
(@"Ask for work. If they don't give you work, ask for bread. If they do not give you work or bread, then take "
@"bread."),
@"Every society has the criminals it deserves.",
(@"Anarchism is founded on the observation that since few men are wise enough to rule themselves, even fewer "
@"are wise enough to rule others."),
@"If you would know who controls you see who you may not criticise.",
@"At one time in the world there were woods that no one owned."
];
NSString *randomText = randomTexts[(NSUInteger)arc4random_uniform((uint32_t)randomTexts.count)];
return randomText;
}
+ (void)createFakeThreads:(NSUInteger)threadCount withFakeMessages:(NSUInteger)messageCount
{
[DebugUIContacts
createRandomContacts:threadCount
contactHandler:^(CNContact *_Nonnull contact, NSUInteger idx, BOOL *_Nonnull stop) {
NSString *phoneNumberText = contact.phoneNumbers.firstObject.value.stringValue;
OWSAssert(phoneNumberText);
PhoneNumber *phoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumberText];
OWSAssert(phoneNumber);
OWSAssert(phoneNumber.toE164);
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:phoneNumber.toE164];
[self sendFakeMessages:messageCount thread:contactThread];
OWSLogError(@"Create fake thread: %@, interactions: %lu",
phoneNumber.toE164,
(unsigned long)contactThread.numberOfInteractions);
}];
}
+ (void)sendFakeMessages:(NSUInteger)counter thread:(TSThread *)thread
{
[self sendFakeMessages:counter thread:thread isTextOnly:NO];
}
+ (void)sendFakeMessages:(NSUInteger)counter thread:(TSThread *)thread isTextOnly:(BOOL)isTextOnly
{
const NSUInteger kMaxBatchSize = 2500;
if (counter < kMaxBatchSize) {
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self sendFakeMessages:counter thread:thread isTextOnly:isTextOnly transaction:transaction];
}];
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSUInteger remainder = counter;
while (remainder > 0) {
NSUInteger batchSize = MIN(kMaxBatchSize, remainder);
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self sendFakeMessages:batchSize thread:thread isTextOnly:isTextOnly transaction:transaction];
}];
remainder -= batchSize;
OWSLogInfo(@"sendFakeMessages %lu / %lu", (unsigned long)(counter - remainder), (unsigned long)counter);
}
});
}
}
+ (void)thrashInsertAndDeleteForThread:(TSThread *)thread counter:(NSUInteger)counter
{
if (counter == 0) {
return;
}
uint32_t sendDelay = arc4random_uniform((uint32_t)(0.01 * NSEC_PER_SEC));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, sendDelay), dispatch_get_main_queue(), ^{
[self sendFakeMessages:1 thread:thread];
});
uint32_t deleteDelay = arc4random_uniform((uint32_t)(0.01 * NSEC_PER_SEC));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, deleteDelay), dispatch_get_main_queue(), ^{
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self deleteRandomMessages:1 thread:thread transaction:transaction];
}];
[self thrashInsertAndDeleteForThread:thread counter:counter - 1];
});
}
// TODO: Remove.
+ (void)sendFakeMessages:(NSUInteger)counter
thread:(TSThread *)thread
isTextOnly:(BOOL)isTextOnly
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSLogInfo(@"sendFakeMessages: %lu", (unsigned long)counter);
for (NSUInteger i = 0; i < counter; i++) {
NSString *randomText = [self randomText];
switch (arc4random_uniform(isTextOnly ? 2 : 4)) {
case 0: {
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:randomText
attachmentIds:@[]
expiresInSeconds:0
quotedMessage:nil
contactShare:nil];
[message markAsReadNowWithSendReadReceipt:NO transaction:transaction];
break;
}
case 1: {
[self createFakeOutgoingMessage:thread
messageBody:randomText
fakeAssetLoader:nil
messageState:TSOutgoingMessageStateFailed
isDelivered:NO
isRead:NO
quotedMessage:nil
contactShare:nil
transaction:transaction];
break;
}
case 2: {
UInt32 filesize = 64;
TSAttachmentPointer *pointer =
[[TSAttachmentPointer alloc] initWithServerId:237391539706350548
key:[self createRandomNSDataOfSize:filesize]
digest:nil
byteCount:filesize
contentType:@"audio/mp3"
sourceFilename:@"test.mp3"
attachmentType:TSAttachmentTypeDefault];
pointer.state = TSAttachmentPointerStateFailed;
[pointer saveWithTransaction:transaction];
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:nil
attachmentIds:@[
pointer.uniqueId,
]
expiresInSeconds:0
quotedMessage:nil
contactShare:nil];
[message markAsReadNowWithSendReadReceipt:NO transaction:transaction];
break;
}
case 3: {
NSString *filename = @"test.mp3";
UInt32 filesize = 16;
TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3"
byteCount:filesize
sourceFilename:filename];
NSError *error;
BOOL success = [attachmentStream writeData:[self createRandomNSDataOfSize:filesize] error:&error];
OWSAssert(success && !error);
[attachmentStream saveWithTransaction:transaction];
[self createFakeOutgoingMessage:thread
messageBody:nil
attachmentId:attachmentStream.uniqueId
filename:filename
messageState:TSOutgoingMessageStateFailed
isDelivered:NO
isRead:NO
isVoiceMessage:NO
quotedMessage:nil
contactShare:nil
transaction:transaction];
break;
}
}
}
}
#pragma mark -
+ (void)createNewGroups:(NSUInteger)counter recipientId:(NSString *)recipientId
{
if (counter < 1) {
return;
}
NSString *groupName = [NSUUID UUID].UUIDString;
NSMutableArray<NSString *> *recipientIds = [@[
recipientId,
[TSAccountManager localNumber],
] mutableCopy];
NSData *groupId = [Randomness generateRandomBytes:16];
TSGroupModel *groupModel =
[[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId];
__block TSGroupThread *thread;
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
}];
OWSAssert(thread);
TSOutgoingMessage *message =
[TSOutgoingMessage outgoingMessageInThread:thread groupMetaMessage:TSGroupMessageNew expiresInSeconds:0];
[message updateWithCustomMessage:NSLocalizedString(@"GROUP_CREATED", nil)];
OWSMessageSender *messageSender = [Environment current].messageSender;
void (^completion)(void) = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[ThreadUtil sendMessageWithText:[@(counter) description]
inThread:thread
quotedReplyModel:nil
messageSender:messageSender];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self createNewGroups:counter - 1 recipientId:recipientId];
});
});
};
[messageSender enqueueMessage:message
success:completion
failure:^(NSError *error) {
completion();
}];
}
+ (void)injectFakeIncomingMessages:(NSUInteger)counter thread:(TSThread *)thread
{
// Wait 5 seconds so debug user has time to navigate to another
// view before message processing occurs.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
for (NSUInteger i = 0; i < counter; i++) {
[self injectIncomingMessageInThread:thread counter:counter - i];
}
});
}
+ (void)injectIncomingMessageInThread:(TSThread *)thread counter:(NSUInteger)counter
{
OWSAssert(thread);
OWSLogInfo(@"injectIncomingMessageInThread: %lu", (unsigned long)counter);
NSString *randomText = [self randomText];
NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText];
SSKProtoDataMessageBuilder *dataMessageBuilder = [SSKProtoDataMessageBuilder new];
[dataMessageBuilder setBody:text];
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
SSKProtoGroupContextBuilder *groupBuilder =
[[SSKProtoGroupContextBuilder alloc] initWithId:groupThread.groupModel.groupId
type:SSKProtoGroupContextTypeDeliver];
[dataMessageBuilder setGroup:groupBuilder.buildIgnoringErrors];
}
SSKProtoContentBuilder *payloadBuilder = [SSKProtoContentBuilder new];
[payloadBuilder setDataMessage:dataMessageBuilder.buildIgnoringErrors];
NSData *plaintextData = [payloadBuilder buildIgnoringErrors].serializedDataIgnoringErrors;
// Try to use an arbitrary member of the current thread that isn't
// ourselves as the sender.
NSString *_Nullable recipientId = [[thread recipientIdentifiers] firstObject];
// This might be an "empty" group with no other members. If so, use a fake
// sender id.
if (!recipientId) {
recipientId = @"+12345678901";
}
uint64_t timestamp = [NSDate ows_millisecondTimeStamp];
NSString *source = recipientId;
uint32_t sourceDevice = 1;
SSKProtoEnvelopeType envelopeType = SSKProtoEnvelopeTypeCiphertext;
NSData *content = plaintextData;
SSKProtoEnvelopeBuilder *envelopeBuilder = [[SSKProtoEnvelopeBuilder alloc] initWithType:envelopeType
source:source
sourceDevice:sourceDevice
timestamp:timestamp];
envelopeBuilder.content = content;
NSError *error;
NSData *_Nullable envelopeData = [envelopeBuilder buildSerializedDataAndReturnError:&error];
if (error || !envelopeData) {
OWSFailDebug(@"Could not serialize envelope: %@.", error);
return;
}
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[OWSBatchMessageProcessor sharedInstance] enqueueEnvelopeData:envelopeData
plaintextData:plaintextData
transaction:transaction];
}];
}
+ (void)performRandomActions:(NSUInteger)counter thread:(TSThread *)thread
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)),
dispatch_get_main_queue(),
^{
[self performRandomActionInThread:thread counter:counter];
if (counter > 0) {
[self performRandomActions:counter - 1 thread:thread];
}
});
}
+ (void)performRandomActionInThread:(TSThread *)thread counter:(NSUInteger)counter
{
typedef void (^TransactionBlock)(YapDatabaseReadWriteTransaction *transaction);
NSArray<TransactionBlock> *actionBlocks = @[
^(YapDatabaseReadWriteTransaction *transaction) {
// injectIncomingMessageInThread doesn't take a transaction.
dispatch_async(dispatch_get_main_queue(), ^{
[self injectIncomingMessageInThread:thread counter:counter];
});
},
^(YapDatabaseReadWriteTransaction *transaction) {
// sendTextMessageInThread doesn't take a transaction.
dispatch_async(dispatch_get_main_queue(), ^{
[self sendTextMessageInThread:thread counter:counter];
});
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self sendFakeMessages:messageCount thread:thread isTextOnly:NO transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self deleteRandomMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self deleteLastMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self deleteRandomRecentMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self insertAndDeleteNewOutgoingMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self resurrectNewOutgoingMessages1:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self resurrectNewOutgoingMessages2:messageCount thread:thread transaction:transaction];
},
];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger actionCount = 1 + (NSUInteger)arc4random_uniform(3);
for (NSUInteger actionIdx = 0; actionIdx < actionCount; actionIdx++) {
TransactionBlock actionBlock = actionBlocks[(NSUInteger)arc4random_uniform((uint32_t)actionBlocks.count)];
actionBlock(transaction);
}
}];
}
+ (void)deleteRandomMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSLogInfo(@"deleteRandomMessages: %zd", count);
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
NSUInteger messageCount = [interactionsByThread numberOfItemsInGroup:thread.uniqueId];
NSMutableArray<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger messageIdx = 0; messageIdx < messageCount; messageIdx++) {
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
for (NSUInteger i = 0; i < count && messageIndices.count > 0; i++) {
NSUInteger idx = (NSUInteger)arc4random_uniform((uint32_t)messageIndices.count);
NSNumber *messageIdx = messageIndices[idx];
[messageIndices removeObjectAtIndex:idx];
TSInteraction *_Nullable interaction =
[interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId];
OWSAssert(interaction);
[interactions addObject:interaction];
}
for (TSInteraction *interaction in interactions) {
[interaction removeWithTransaction:transaction];
}
}
+ (void)deleteLastMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSLogInfo(@"deleteLastMessages");
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
NSUInteger messageCount = (NSUInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId];
NSMutableArray<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger i = 0; i < count && i < messageCount; i++) {
NSUInteger messageIdx = messageCount - (1 + i);
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
for (NSNumber *messageIdx in messageIndices) {
TSInteraction *_Nullable interaction =
[interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId];
OWSAssert(interaction);
[interactions addObject:interaction];
}
for (TSInteraction *interaction in interactions) {
[interaction removeWithTransaction:transaction];
}
}
+ (void)deleteRandomRecentMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSLogInfo(@"deleteRandomRecentMessages: %zd", count);
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
NSInteger messageCount = (NSInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId];
NSMutableArray<NSNumber *> *messageIndices = [NSMutableArray new];
const NSInteger kRecentMessageCount = 10;
for (NSInteger i = 0; i < kRecentMessageCount; i++) {
NSInteger messageIdx = messageCount - (1 + i);
if (messageIdx >= 0) {
[messageIndices addObject:@(messageIdx)];
}
}
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
for (NSUInteger i = 0; i < count && messageIndices.count > 0; i++) {
NSUInteger idx = (NSUInteger)arc4random_uniform((uint32_t)messageIndices.count);
NSNumber *messageIdx = messageIndices[idx];
[messageIndices removeObjectAtIndex:idx];
TSInteraction *_Nullable interaction =
[interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId];
OWSAssert(interaction);
[interactions addObject:interaction];
}
for (TSInteraction *interaction in interactions) {
[interaction removeWithTransaction:transaction];
}
}
+ (void)insertAndDeleteNewOutgoingMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSLogInfo(@"insertAndDeleteNewOutgoingMessages: %zd", count);
NSMutableArray<TSOutgoingMessage *> *messages = [NSMutableArray new];
for (NSUInteger i =0; i < count; i++) {
NSString *text = [self randomText];
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction];
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
messageBody:text
attachmentId:nil
expiresInSeconds:expiresInSeconds];
OWSLogError(@"insertAndDeleteNewOutgoingMessages timestamp: %llu.", message.timestamp);
[messages addObject:message];
}
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:transaction];
}
for (TSOutgoingMessage *message in messages) {
[message removeWithTransaction:transaction];
}
}
+ (void)resurrectNewOutgoingMessages1:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)initialTransaction
{
OWSLogInfo(@"resurrectNewOutgoingMessages1.1: %zd", count);
NSMutableArray<TSOutgoingMessage *> *messages = [NSMutableArray new];
for (NSUInteger i =0; i < count; i++) {
NSString *text = [self randomText];
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId
transaction:initialTransaction];
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
messageBody:text
attachmentId:nil
expiresInSeconds:expiresInSeconds];
OWSLogError(@"resurrectNewOutgoingMessages1 timestamp: %llu.", message.timestamp);
[messages addObject:message];
}
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:initialTransaction];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
OWSLogInfo(@"resurrectNewOutgoingMessages1.2: %zd", count);
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSOutgoingMessage *message in messages) {
[message removeWithTransaction:transaction];
}
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:transaction];
}
}];
});
}
+ (void)resurrectNewOutgoingMessages2:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)initialTransaction
{
OWSLogInfo(@"resurrectNewOutgoingMessages2.1: %zd", count);
NSMutableArray<TSOutgoingMessage *> *messages = [NSMutableArray new];
for (NSUInteger i =0; i < count; i++) {
NSString *text = [self randomText];
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId
transaction:initialTransaction];
TSOutgoingMessage *message = [[TSOutgoingMessage alloc]
initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:text
attachmentIds:[NSMutableArray new]
expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds
: 0)expireStartedAt:0
isVoiceMessage:NO
groupMetaMessage:TSGroupMessageUnspecified
quotedMessage:nil
contactShare:nil];
OWSLogError(@"resurrectNewOutgoingMessages2 timestamp: %llu.", message.timestamp);
[messages addObject:message];
}
for (TSOutgoingMessage *message in messages) {
[message updateWithFakeMessageState:TSOutgoingMessageStateSending transaction:initialTransaction];
[message saveWithTransaction:initialTransaction];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
OWSLogInfo(@"resurrectNewOutgoingMessages2.2: %zd", count);
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSOutgoingMessage *message in messages) {
[message removeWithTransaction:transaction];
}
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
OWSLogInfo(@"resurrectNewOutgoingMessages2.3: %zd", count);
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:transaction];
}
}];
});
});
}
+ (void)createTimestampMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
long long now = (long long)[NSDate ows_millisecondTimeStamp];
NSArray<NSNumber *> *timestamps = @[
@(now + 1 * (long long)kHourInMs),
@(now),
@(now - 1 * (long long)kHourInMs),
@(now - 12 * (long long)kHourInMs),
@(now - 1 * (long long)kDayInMs),
@(now - 2 * (long long)kDayInMs),
@(now - 3 * (long long)kDayInMs),
@(now - 6 * (long long)kDayInMs),
@(now - 7 * (long long)kDayInMs),
@(now - 8 * (long long)kDayInMs),
@(now - 2 * (long long)kWeekInMs),
@(now - 1 * (long long)kMonthInMs),
@(now - 2 * (long long)kMonthInMs),
];
NSMutableArray<NSString *> *recipientIds = [thread.recipientIdentifiers mutableCopy];
[recipientIds removeObject:[TSAccountManager localNumber]];
NSString *recipientId = (recipientIds.count > 0 ? recipientIds.firstObject : @"+19174054215");
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSNumber *timestamp in timestamps) {
NSString *randomText = [self randomText];
{
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp.unsignedLongLongValue
inThread:thread
authorId:recipientId
sourceDeviceId:0
messageBody:randomText
attachmentIds:[NSMutableArray new]
expiresInSeconds:0
quotedMessage:nil
contactShare:nil];
[message markAsReadNowWithSendReadReceipt:NO transaction:transaction];
}
{
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:timestamp.unsignedLongLongValue
inThread:thread
messageBody:randomText
attachmentIds:[NSMutableArray new]
expiresInSeconds:0
expireStartedAt:0
isVoiceMessage:NO
groupMetaMessage:TSGroupMessageUnspecified
quotedMessage:nil
contactShare:nil];
[message saveWithTransaction:transaction];
[message updateWithFakeMessageState:TSOutgoingMessageStateSent transaction:transaction];
[message updateWithSentRecipient:recipientId transaction:transaction];
[message updateWithDeliveredRecipient:recipientId deliveryTimestamp:timestamp transaction:transaction];
[message updateWithReadRecipientId:recipientId
readTimestamp:timestamp.unsignedLongLongValue
transaction:transaction];
}
}
}];
}
+ (void)createDisappearingMessagesWhichFailedToStartInThread:(TSThread *)thread
{
uint64_t now = [NSDate ows_millisecondTimeStamp];
TSIncomingMessage *message = [[TSIncomingMessage alloc]
initIncomingMessageWithTimestamp:now
inThread:thread
authorId:thread.recipientIdentifiers.firstObject
sourceDeviceId:0
messageBody:[NSString
stringWithFormat:@"Should disappear 60s after %lu", (unsigned long)now]
attachmentIds:[NSMutableArray new]
expiresInSeconds:60
quotedMessage:nil
contactShare:nil];
// private setter to avoid starting expire machinery.
message.read = YES;
[message save];
}
+ (void)testIndicScriptsInThread:(TSThread *)thread
{
NSArray<NSString *> *strings = @[
@"\u0C1C\u0C4D\u0C1E\u200C\u0C3E",
@"\u09B8\u09CD\u09B0\u200C\u09C1",
@"non-crashing string",
];
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *string in strings) {
// DO NOT log these strings with the debugger attached.
// OWSLogInfo(@"%@", string);
{
[self createFakeIncomingMessage:thread
messageBody:string
fakeAssetLoader:nil
isAttachmentDownloaded:NO
quotedMessage:nil
transaction:transaction];
}
{
NSString *recipientId = @"+19174054215";
NSString *groupName = string;
NSMutableArray<NSString *> *recipientIds = [@[
recipientId,
[TSAccountManager localNumber],
] mutableCopy];
NSData *groupId = [Randomness generateRandomBytes:16];
TSGroupModel *groupModel =
[[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId];
TSGroupThread *groupThread =
[TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
OWSAssert(groupThread);
}
}
}];
}
+ (void)testZalgoTextInThread:(TSThread *)thread
{
NSArray<NSString *> *strings = @[
@"Ṱ̴̤̺̣͚͚̭̰̤̮̑̓̀͂͘͡h̵̢̤͔̼̗̦̖̬͌̀͒̀͘i̴̮̤͎͎̝̖̻͓̅̆͆̓̎͘͡ͅŝ̡̡̳͔̓͗̾̀̇͒͘͢͢͡͡ ỉ̛̲̩̫̝͉̀̒͐͋̾͘͢͡͞s̶̨̫̞̜̹͛́̇͑̅̒̊̈ s̵͍̲̗̠̗͈̦̬̉̿͂̏̐͆̾͐͊̾ǫ̶͍̼̝̉͊̉͢͜͞͝ͅͅṁ̵̡̨̬̤̝͔̣̄̍̋͊̿̄͋̈ͅe̪̪̻̱͖͚͈̲̍̃͘͠͝ z̷̢̢̛̩̦̱̺̼͑́̉̾ą͕͎̠̮̹̱̓̔̓̈̈́̅̐͢l̵̨͚̜͉̟̜͉͎̃͆͆͒͑̍̈̚͜͞ğ͔̖̫̞͎͍̒̂́̒̿̽̆͟o̶̢̬͚̘̤̪͇̻̒̋̇̊̏͢͡͡͠ͅ t̡̛̥̦̪̮̅̓̑̈́̉̓̽͛͢͡ȩ̡̩͓͈̩͎͗̔͑̌̓͊͆͝x̫̦͓̤͓̘̝̪͊̆͌͊̽̃̏͒͘͘͢ẗ̶̢̨̛̰̯͕͔́̐͗͌͟͠.̷̩̼̼̩̞̘̪́͗̅͊̎̾̅̏̀̕͟ͅ",
@"This is some normal text",
];
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *string in strings) {
OWSLogInfo(@"sending zalgo");
{
[self createFakeIncomingMessage:thread
messageBody:string
fakeAssetLoader:nil
isAttachmentDownloaded:NO
quotedMessage:nil
transaction:transaction];
}
{
NSString *recipientId = @"+19174054215";
NSString *groupName = string;
NSMutableArray<NSString *> *recipientIds = [@[
recipientId,
[TSAccountManager localNumber],
] mutableCopy];
NSData *groupId = [Randomness generateRandomBytes:16];
TSGroupModel *groupModel =
[[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId];
TSGroupThread *groupThread =
[TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
OWSAssert(groupThread);
}
}
}];
}
+ (void)testDirectionalFilenamesInThread:(TSThread *)thread
{
NSMutableArray<NSString *> *filenames = [@[
@"a_test\u202Dabc.exe",
@"b_test\u202Eabc.exe",
@"c_testabc.exe",
] mutableCopy];
__block void (^sendUnsafeFile)(void);
sendUnsafeFile = ^{
if (filenames.count < 1) {
return;
}
NSString *filename = filenames.lastObject;
[filenames removeLastObject];
OWSMessageSender *messageSender = [Environment current].messageSender;
NSString *utiType = (NSString *)kUTTypeData;
const NSUInteger kDataLength = 32;
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:kDataLength] utiType:utiType];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal];
OWSAssert(attachment);
if ([attachment hasError]) {
OWSLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
[DDLog flushLog];
}
OWSAssert(![attachment hasError]);
[ThreadUtil sendMessageWithAttachment:attachment
inThread:thread
quotedReplyModel:nil
messageSender:messageSender
completion:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
sendUnsafeFile();
sendUnsafeFile = nil;
});
};
}
+ (void)deleteAllMessagesInThread:(TSThread *)thread
{
[OWSPrimaryStorage.sharedManager.newDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[thread removeAllThreadInteractionsWithTransaction:transaction];
}];
}
#pragma mark - Utility
+ (NSString *)actionLabelForHasCaption:(BOOL)hasCaption
outgoingMessageState:(TSOutgoingMessageState)outgoingMessageState
isDelivered:(BOOL)isDelivered
isRead:(BOOL)isRead
{
NSMutableString *label = [NSMutableString new];
if (hasCaption) {
[label appendString:@" 🔤"];
}
if (outgoingMessageState == TSOutgoingMessageStateFailed) {
[label appendString:@" (Unsent)"];
} else if (outgoingMessageState == TSOutgoingMessageStateSending) {
[label appendString:@" (Sending)"];
} else if (outgoingMessageState == TSOutgoingMessageStateSent) {
if (isRead) {
[label appendString:@" (Read)"];
} else if (isDelivered) {
[label appendString:@" (Delivered)"];
} else {
[label appendString:@" (Sent)"];
}
} else {
OWSFailDebug(@"unknown message state.");
}
return label;
}
+ (TSOutgoingMessage *)createFakeOutgoingMessage:(TSThread *)thread
messageBody:(nullable NSString *)messageBody
fakeAssetLoader:(nullable DebugUIMessagesAssetLoader *)fakeAssetLoader
messageState:(TSOutgoingMessageState)messageState
isDelivered:(BOOL)isDelivered
isRead:(BOOL)isRead
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
contactShare:(nullable OWSContact *)contactShare
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
// Seamlessly convert oversize text messages to oversize text attachments.
if ([messageBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
OWSAssert(!fakeAssetLoader);
fakeAssetLoader = [DebugUIMessagesAssetLoader oversizeTextInstanceWithText:messageBody];
messageBody = nil;
}
TSAttachment *_Nullable attachment = nil;
if (fakeAssetLoader) {
attachment = [self createFakeAttachment:fakeAssetLoader isAttachmentDownloaded:YES transaction:transaction];
}
return [self createFakeOutgoingMessage:thread
messageBody:messageBody
attachmentId:attachment.uniqueId
filename:fakeAssetLoader.filename
messageState:messageState
isDelivered:isDelivered
isRead:isRead
isVoiceMessage:attachment.isVoiceMessage
quotedMessage:quotedMessage
contactShare:contactShare
transaction:transaction];
}
+ (TSOutgoingMessage *)createFakeOutgoingMessage:(TSThread *)thread
messageBody:(nullable NSString *)messageBody
attachmentId:(nullable NSString *)attachmentId
filename:(nullable NSString *)filename
messageState:(TSOutgoingMessageState)messageState
isDelivered:(BOOL)isDelivered
isRead:(BOOL)isRead
isVoiceMessage:(BOOL)isVoiceMessage
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
contactShare:(nullable OWSContact *)contactShare
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
OWSAssert(transaction);
OWSAssert(messageBody.length > 0 || attachmentId.length > 0 || contactShare);
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
if (attachmentId) {
[attachmentIds addObject:attachmentId];
}
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:messageBody
attachmentIds:attachmentIds
expiresInSeconds:0
expireStartedAt:0
isVoiceMessage:isVoiceMessage
groupMetaMessage:TSGroupMessageUnspecified
quotedMessage:quotedMessage
contactShare:contactShare];
if (attachmentId.length > 0 && filename.length > 0) {
message.attachmentFilenameMap[attachmentId] = filename;
}
[message saveWithTransaction:transaction];
[message updateWithFakeMessageState:messageState transaction:transaction];
if (isDelivered) {
NSString *_Nullable recipientId = thread.recipientIdentifiers.lastObject;
OWSAssert(recipientId.length > 0);
[message updateWithDeliveredRecipient:recipientId
deliveryTimestamp:@([NSDate ows_millisecondTimeStamp])
transaction:transaction];
}
if (isRead) {
NSString *_Nullable recipientId = thread.recipientIdentifiers.lastObject;
OWSAssert(recipientId.length > 0);
[message updateWithReadRecipientId:recipientId
readTimestamp:[NSDate ows_millisecondTimeStamp]
transaction:transaction];
}
return message;
}
+ (TSIncomingMessage *)createFakeIncomingMessage:(TSThread *)thread
messageBody:(nullable NSString *)messageBody
fakeAssetLoader:(nullable DebugUIMessagesAssetLoader *)fakeAssetLoader
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
// Seamlessly convert oversize text messages to oversize text attachments.
if ([messageBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
OWSAssert(!fakeAssetLoader);
fakeAssetLoader = [DebugUIMessagesAssetLoader oversizeTextInstanceWithText:messageBody];
messageBody = nil;
}
TSAttachment *_Nullable attachment = nil;
if (fakeAssetLoader) {
attachment = [self createFakeAttachment:fakeAssetLoader
isAttachmentDownloaded:isAttachmentDownloaded
transaction:transaction];
}
return [self createFakeIncomingMessage:thread
messageBody:messageBody
attachmentId:attachment.uniqueId
filename:fakeAssetLoader.filename
isAttachmentDownloaded:isAttachmentDownloaded
quotedMessage:quotedMessage
transaction:transaction];
}
+ (TSIncomingMessage *)createFakeIncomingMessage:(TSThread *)thread
messageBody:(nullable NSString *)messageBody
attachmentId:(nullable NSString *)attachmentId
filename:(nullable NSString *)filename
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
OWSAssert(transaction);
OWSAssert(messageBody.length > 0 || attachmentId.length > 0);
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
if (attachmentId) {
[attachmentIds addObject:attachmentId];
}
// // Random time within last n years. Helpful for filling out a media gallery over time.
// double yearsMillis = 4.0 * kYearsInMs;
// uint64_t millisAgo = (uint64_t)(((double)arc4random() / ((double)0xffffffff)) * yearsMillis);
// uint64_t timestamp = [NSDate ows_millisecondTimeStamp] - millisAgo;
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:messageBody
attachmentIds:attachmentIds
expiresInSeconds:0
quotedMessage:quotedMessage
contactShare:nil];
[message markAsReadNowWithSendReadReceipt:NO transaction:transaction];
return message;
}
+ (TSAttachment *)createFakeAttachment:(DebugUIMessagesAssetLoader *)fakeAssetLoader
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(fakeAssetLoader);
OWSAssert(fakeAssetLoader.filePath);
OWSAssert(transaction);
if (isAttachmentDownloaded) {
DataSource *dataSource =
[DataSourcePath dataSourceWithFilePath:fakeAssetLoader.filePath shouldDeleteOnDeallocation:NO];
NSString *filename = dataSource.sourceFilename;
// To support "fake missing" attachments, we sometimes lie about the
// length of the data.
UInt32 nominalDataLength = (UInt32)MAX((NSUInteger)1, dataSource.dataLength);
TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:fakeAssetLoader.mimeType
byteCount:nominalDataLength
sourceFilename:filename];
NSError *error;
BOOL success = [attachmentStream writeData:dataSource.data error:&error];
OWSAssert(success && !error);
[attachmentStream saveWithTransaction:transaction];
return attachmentStream;
} else {
UInt32 filesize = 64;
TSAttachmentPointer *attachmentPointer =
[[TSAttachmentPointer alloc] initWithServerId:237391539706350548
key:[self createRandomNSDataOfSize:filesize]
digest:nil
byteCount:filesize
contentType:fakeAssetLoader.mimeType
sourceFilename:fakeAssetLoader.filename
attachmentType:TSAttachmentTypeDefault];
attachmentPointer.state = TSAttachmentPointerStateFailed;
[attachmentPointer saveWithTransaction:transaction];
return attachmentPointer;
}
}
#endif
@end
NS_ASSUME_NONNULL_END