/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.tm.archive.sms;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.logging.Log;
import org.tm.archive.attachments.Attachment;
import org.tm.archive.attachments.AttachmentId;
import org.tm.archive.attachments.DatabaseAttachment;
import org.tm.archive.contacts.sync.ContactDiscovery;
import org.tm.archive.contactshare.Contact;
import org.tm.archive.database.AttachmentTable;
import org.tm.archive.database.MessageTable;
import org.tm.archive.database.MessageTable.SyncMessageId;
import org.tm.archive.database.NoSuchMessageException;
import org.tm.archive.database.RecipientTable;
import org.tm.archive.database.SignalDatabase;
import org.tm.archive.database.ThreadTable;
import org.tm.archive.database.model.MessageId;
import org.tm.archive.database.model.MessageRecord;
import org.tm.archive.database.model.MmsMessageRecord;
import org.tm.archive.database.model.ReactionRecord;
import org.tm.archive.database.model.StoryType;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.jobmanager.Job;
import org.tm.archive.jobmanager.JobManager;
import org.tm.archive.jobs.AttachmentCompressionJob;
import org.tm.archive.jobs.AttachmentCopyJob;
import org.tm.archive.jobs.AttachmentMarkUploadedJob;
import org.tm.archive.jobs.AttachmentUploadJob;
import org.tm.archive.jobs.ProfileKeySendJob;
import org.tm.archive.jobs.PushDistributionListSendJob;
import org.tm.archive.jobs.PushGroupSendJob;
import org.tm.archive.jobs.IndividualSendJob;
import org.tm.archive.jobs.ReactionSendJob;
import org.tm.archive.jobs.RemoteDeleteSendJob;
import org.tm.archive.keyvalue.SignalStore;
import org.tm.archive.linkpreview.LinkPreview;
import org.tm.archive.mediasend.Media;
import org.tm.archive.mms.MmsException;
import org.tm.archive.mms.OutgoingMessage;
import org.tm.archive.recipients.Recipient;
import org.tm.archive.recipients.RecipientId;
import org.tm.archive.recipients.RecipientUtil;
import org.tm.archive.service.ExpiringMessageManager;
import org.tm.archive.util.ParcelUtil;
import org.tm.archive.util.SignalLocalMetrics;
import org.tm.archive.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.util.Preconditions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class MessageSender {
private static final String TAG = Log.tag(MessageSender.class);
/**
* Suitable for a 1:1 conversation or a GV1 group only.
*/
@WorkerThread
public static void sendProfileKey(final long threadId) {
ProfileKeySendJob job = ProfileKeySendJob.create(threadId, false);
if (job != null) {
ApplicationDependencies.getJobManager().add(job);
}
}
public static void sendStories(@NonNull final Context context,
@NonNull final List messages,
@Nullable final String metricId,
@Nullable final MessageTable.InsertListener insertListener)
{
Log.i(TAG, "Sending story messages to " + messages.size() + " targets.");
ThreadTable threadTable = SignalDatabase.threads();
MessageTable database = SignalDatabase.messages();
List messageIds = new ArrayList<>(messages.size());
List threads = new ArrayList<>(messages.size());
UploadDependencyGraph dependencyGraph;
try {
database.beginTransaction();
for (OutgoingMessage message : messages) {
long allocatedThreadId = threadTable.getOrCreateValidThreadId(message.getThreadRecipient(), -1L, message.getDistributionType());
long messageId = database.insertMessageOutbox(message.stripAttachments(), allocatedThreadId, false, insertListener);
messageIds.add(messageId);
threads.add(allocatedThreadId);
if (message.getThreadRecipient().isGroup() && message.getAttachments().isEmpty() && message.getLinkPreviews().isEmpty() && message.getSharedContacts().isEmpty()) {
SignalLocalMetrics.GroupMessageSend.onInsertedIntoDatabase(messageId, metricId);
} else {
SignalLocalMetrics.GroupMessageSend.cancel(metricId);
}
}
for (int i = 0; i < messageIds.size(); i++) {
long messageId = messageIds.get(i);
OutgoingMessage message = messages.get(i);
Recipient recipient = message.getThreadRecipient();
if (recipient.isDistributionList()) {
DistributionId distributionId = Objects.requireNonNull(SignalDatabase.distributionLists().getDistributionId(recipient.requireDistributionListId()));
List members = SignalDatabase.distributionLists().getMembers(recipient.requireDistributionListId());
SignalDatabase.storySends().insert(messageId, members, message.getSentTimeMillis(), message.getStoryType().isStoryWithReplies(), distributionId);
}
}
dependencyGraph = UploadDependencyGraph.create(
messages,
ApplicationDependencies.getJobManager(),
attachment -> {
try {
return SignalDatabase.attachments().insertAttachmentForPreUpload(attachment);
} catch (MmsException e) {
Log.e(TAG, e);
throw new IllegalStateException(e);
}
}
);
for (int i = 0; i < messageIds.size(); i++) {
long messageId = messageIds.get(i);
OutgoingMessage message = messages.get(i);
List nodes = dependencyGraph.getDependencyMap().get(message);
if (nodes == null || nodes.isEmpty()) {
if (message.getStoryType().isTextStory()) {
Log.d(TAG, "No attachments for given text story. Skipping.");
continue;
} else {
Log.e(TAG, "No attachments for given media story. Aborting.");
throw new MmsException("No attachment for story.");
}
}
List attachmentIds = nodes.stream().map(UploadDependencyGraph.Node::getAttachmentId).collect(Collectors.toList());
SignalDatabase.attachments().updateMessageId(attachmentIds, messageId, true);
for (final AttachmentId attachmentId : attachmentIds) {
SignalDatabase.attachments().updateAttachmentCaption(attachmentId, message.getBody());
}
}
database.setTransactionSuccessful();
} catch (MmsException e) {
Log.w(TAG, "Failed to send stories.", e);
return;
} finally {
database.endTransaction();
}
List chains = dependencyGraph.consumeDeferredQueue();
for (final JobManager.Chain chain : chains) {
chain.enqueue();
}
for (int i = 0; i < messageIds.size(); i++) {
long messageId = messageIds.get(i);
OutgoingMessage message = messages.get(i);
Recipient recipient = message.getThreadRecipient();
List dependencies = dependencyGraph.getDependencyMap().get(message);
List jobDependencyIds = (dependencies != null) ? dependencies.stream().map(UploadDependencyGraph.Node::getJobId).collect(Collectors.toList())
: Collections.emptyList();
sendMessageInternal(context,
recipient,
SendType.SIGNAL,
messageId,
jobDependencyIds,
false);
}
onMessageSent();
for (long threadId : threads) {
threadTable.update(threadId, true);
}
}
public static long send(final Context context,
final OutgoingMessage message,
final long threadId,
@NonNull SendType sendType,
@Nullable final String metricId,
@Nullable final MessageTable.InsertListener insertListener)
{
Log.i(TAG, "Sending media message to " + message.getThreadRecipient().getId() + ", thread: " + threadId);
try {
ThreadTable threadTable = SignalDatabase.threads();
MessageTable database = SignalDatabase.messages();
long allocatedThreadId = threadTable.getOrCreateValidThreadId(message.getThreadRecipient(), threadId, message.getDistributionType());
Recipient recipient = message.getThreadRecipient();
long messageId = database.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, recipient, message, allocatedThreadId), allocatedThreadId, sendType != SendType.SIGNAL, insertListener);
if (message.getThreadRecipient().isGroup()) {
if (message.getAttachments().isEmpty() && message.getLinkPreviews().isEmpty() && message.getSharedContacts().isEmpty()) {
SignalLocalMetrics.GroupMessageSend.onInsertedIntoDatabase(messageId, metricId);
} else {
SignalLocalMetrics.GroupMessageSend.cancel(messageId);
}
} else {
SignalLocalMetrics.IndividualMessageSend.onInsertedIntoDatabase(messageId, metricId);
}
sendMessageInternal(context, recipient, sendType, messageId, Collections.emptyList(), message.getScheduledDate() > 0);
onMessageSent();
threadTable.update(allocatedThreadId, true);
return allocatedThreadId;
} catch (MmsException e) {
Log.w(TAG, e);
return threadId;
}
}
public static long sendPushWithPreUploadedMedia(final Context context,
final OutgoingMessage message,
final Collection preUploadResults,
final long threadId,
final MessageTable.InsertListener insertListener)
{
Log.i(TAG, "Sending media message with pre-uploads to " + message.getThreadRecipient().getId() + ", thread: " + threadId + ", pre-uploads: " + preUploadResults);
Preconditions.checkArgument(message.getAttachments().isEmpty(), "If the media is pre-uploaded, there should be no attachments on the message.");
try {
ThreadTable threadTable = SignalDatabase.threads();
MessageTable mmsDatabase = SignalDatabase.messages();
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
Recipient recipient = message.getThreadRecipient();
long allocatedThreadId = threadTable.getOrCreateValidThreadId(message.getThreadRecipient(), threadId);
long messageId = mmsDatabase.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, recipient, message, allocatedThreadId),
allocatedThreadId,
false,
insertListener);
List attachmentIds = Stream.of(preUploadResults).map(PreUploadResult::getAttachmentId).toList();
List jobIds = Stream.of(preUploadResults).map(PreUploadResult::getJobIds).flatMap(Stream::of).toList();
attachmentDatabase.updateMessageId(attachmentIds, messageId, message.getStoryType().isStory());
sendMessageInternal(context, recipient, SendType.SIGNAL, messageId, jobIds, false);
onMessageSent();
threadTable.update(allocatedThreadId, true);
return allocatedThreadId;
} catch (MmsException e) {
Log.w(TAG, e);
return threadId;
}
}
public static void sendMediaBroadcast(@NonNull Context context,
@NonNull List messages,
@NonNull Collection preUploadResults,
boolean overwritePreUploadMessageIds)
{
Log.i(TAG, "Sending media broadcast (overwrite: " + overwritePreUploadMessageIds + ") to " + Stream.of(messages).map(m -> m.getThreadRecipient().getId()).toList());
Preconditions.checkArgument(messages.size() > 0, "No messages!");
Preconditions.checkArgument(Stream.of(messages).allMatch(m -> m.getAttachments().isEmpty()), "Messages can't have attachments! They should be pre-uploaded.");
JobManager jobManager = ApplicationDependencies.getJobManager();
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
MessageTable mmsDatabase = SignalDatabase.messages();
ThreadTable threadTable = SignalDatabase.threads();
List preUploadAttachmentIds = Stream.of(preUploadResults).map(PreUploadResult::getAttachmentId).toList();
List preUploadJobIds = Stream.of(preUploadResults).map(PreUploadResult::getJobIds).flatMap(Stream::of).toList();
List messageIds = new ArrayList<>(messages.size());
List messageDependsOnIds = new ArrayList<>(preUploadJobIds);
OutgoingMessage primaryMessage = messages.get(0);
mmsDatabase.beginTransaction();
try {
if (overwritePreUploadMessageIds) {
long primaryThreadId = threadTable.getOrCreateThreadIdFor(primaryMessage.getThreadRecipient(), primaryMessage.getDistributionType());
long primaryMessageId = mmsDatabase.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, primaryMessage.getThreadRecipient(), primaryMessage, primaryThreadId),
primaryThreadId,
false,
null);
attachmentDatabase.updateMessageId(preUploadAttachmentIds, primaryMessageId, primaryMessage.getStoryType().isStory());
if (primaryMessage.getStoryType() != StoryType.NONE) {
for (final AttachmentId preUploadAttachmentId : preUploadAttachmentIds) {
attachmentDatabase.updateAttachmentCaption(preUploadAttachmentId, primaryMessage.getBody());
}
}
messageIds.add(primaryMessageId);
}
List preUploadAttachments = Stream.of(preUploadAttachmentIds)
.map(attachmentDatabase::getAttachment)
.toList();
if (messages.size() > 0) {
List secondaryMessages = overwritePreUploadMessageIds ? messages.subList(1, messages.size()) : messages;
List> attachmentCopies = new ArrayList<>();
for (int i = 0; i < preUploadAttachmentIds.size(); i++) {
attachmentCopies.add(new ArrayList<>(messages.size()));
}
for (OutgoingMessage secondaryMessage : secondaryMessages) {
long allocatedThreadId = threadTable.getOrCreateThreadIdFor(secondaryMessage.getThreadRecipient(), secondaryMessage.getDistributionType());
long messageId = mmsDatabase.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, secondaryMessage.getThreadRecipient(), secondaryMessage, allocatedThreadId),
allocatedThreadId,
false,
null);
List attachmentIds = new ArrayList<>(preUploadAttachmentIds.size());
for (int i = 0; i < preUploadAttachments.size(); i++) {
AttachmentId attachmentId = attachmentDatabase.insertAttachmentForPreUpload(preUploadAttachments.get(i)).attachmentId;
attachmentCopies.get(i).add(attachmentId);
attachmentIds.add(attachmentId);
}
attachmentDatabase.updateMessageId(attachmentIds, messageId, secondaryMessage.getStoryType().isStory());
if (primaryMessage.getStoryType() != StoryType.NONE) {
for (final AttachmentId preUploadAttachmentId : attachmentIds) {
attachmentDatabase.updateAttachmentCaption(preUploadAttachmentId, primaryMessage.getBody());
}
}
messageIds.add(messageId);
}
for (int i = 0; i < attachmentCopies.size(); i++) {
Job copyJob = new AttachmentCopyJob(preUploadAttachmentIds.get(i), attachmentCopies.get(i));
jobManager.add(copyJob, preUploadJobIds);
messageDependsOnIds.add(copyJob.getId());
}
}
for (int i = 0; i < messageIds.size(); i++) {
long messageId = messageIds.get(i);
OutgoingMessage message = messages.get(i);
Recipient recipient = message.getThreadRecipient();
if (recipient.isDistributionList()) {
List members = SignalDatabase.distributionLists().getMembers(recipient.requireDistributionListId());
DistributionId distributionId = Objects.requireNonNull(SignalDatabase.distributionLists().getDistributionId(recipient.requireDistributionListId()));
SignalDatabase.storySends().insert(messageId, members, message.getSentTimeMillis(), message.getStoryType().isStoryWithReplies(), distributionId);
}
}
onMessageSent();
mmsDatabase.setTransactionSuccessful();
} catch (MmsException e) {
Log.w(TAG, "Failed to send messages.", e);
return;
} finally {
mmsDatabase.endTransaction();
}
for (int i = 0; i < messageIds.size(); i++) {
long messageId = messageIds.get(i);
Recipient recipient = messages.get(i).getThreadRecipient();
if (isLocalSelfSend(context, recipient, SendType.SIGNAL)) {
sendLocalMediaSelf(context, messageId);
} else if (recipient.isPushGroup()) {
jobManager.add(new PushGroupSendJob(messageId, recipient.getId(), Collections.emptySet(), true, false), messageDependsOnIds, recipient.getId().toQueueKey());
} else if (recipient.isDistributionList()) {
jobManager.add(new PushDistributionListSendJob(messageId, recipient.getId(), true, Collections.emptySet()), messageDependsOnIds, recipient.getId().toQueueKey());
} else {
jobManager.add(IndividualSendJob.create(messageId, recipient, true, false), messageDependsOnIds, recipient.getId().toQueueKey());
}
}
}
/**
* @return A result if the attachment was enqueued, or null if it failed to enqueue or shouldn't
* be enqueued (like in the case of a local self-send).
*/
public static @Nullable PreUploadResult preUploadPushAttachment(@NonNull Context context, @NonNull Attachment attachment, @Nullable Recipient recipient, @NonNull Media media) {
if (isLocalSelfSend(context, recipient, SendType.SIGNAL)) {
return null;
}
Log.i(TAG, "Pre-uploading attachment for " + (recipient != null ? recipient.getId() : "null"));
try {
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
DatabaseAttachment databaseAttachment = attachmentDatabase.insertAttachmentForPreUpload(attachment);
Job compressionJob = AttachmentCompressionJob.fromAttachment(databaseAttachment, false, -1);
Job uploadJob = new AttachmentUploadJob(databaseAttachment.attachmentId);
ApplicationDependencies.getJobManager()
.startChain(compressionJob)
.then(uploadJob)
.enqueue();
return new PreUploadResult(media, databaseAttachment.attachmentId, Arrays.asList(compressionJob.getId(), uploadJob.getId()));
} catch (MmsException e) {
Log.w(TAG, "preUploadPushAttachment() - Failed to upload!", e);
return null;
}
}
public static void sendNewReaction(@NonNull Context context, @NonNull MessageId messageId, @NonNull String emoji) {
ReactionRecord reaction = new ReactionRecord(emoji, Recipient.self().getId(), System.currentTimeMillis(), System.currentTimeMillis());
SignalDatabase.reactions().addReaction(messageId, reaction);
try {
ApplicationDependencies.getJobManager().add(ReactionSendJob.create(context, messageId, reaction, false));
onMessageSent();
} catch (NoSuchMessageException e) {
Log.w(TAG, "[sendNewReaction] Could not find message! Ignoring.");
}
}
public static void sendReactionRemoval(@NonNull Context context, @NonNull MessageId messageId, @NonNull ReactionRecord reaction) {
SignalDatabase.reactions().deleteReaction(messageId, reaction.getAuthor());
try {
ApplicationDependencies.getJobManager().add(ReactionSendJob.create(context, messageId, reaction, true));
onMessageSent();
} catch (NoSuchMessageException e) {
Log.w(TAG, "[sendReactionRemoval] Could not find message! Ignoring.");
}
}
public static void sendRemoteDelete(long messageId) {
MessageTable db = SignalDatabase.messages();
db.markAsRemoteDelete(messageId);
db.markAsSending(messageId);
try {
RemoteDeleteSendJob.create(messageId).enqueue();
onMessageSent();
} catch (NoSuchMessageException e) {
Log.w(TAG, "[sendRemoteDelete] Could not find message! Ignoring.");
}
}
public static void resendGroupMessage(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set filterRecipientIds) {
if (!messageRecord.isMms()) throw new AssertionError("Not Group");
sendGroupPush(context, messageRecord.getToRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList());
onMessageSent();
}
public static void resendDistributionList(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set filterRecipientIds) {
if (!messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getStoryType().isStory()) {
throw new AssertionError("Not a story");
}
sendDistributionList(context, messageRecord.getToRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList());
onMessageSent();
}
public static void resend(Context context, MessageRecord messageRecord) {
long messageId = messageRecord.getId();
boolean forceSms = messageRecord.isForcedSms();
Recipient recipient = messageRecord.getToRecipient();
SendType sendType;
if (forceSms) {
Recipient threadRecipient = SignalDatabase.threads().getRecipientForThreadId(messageRecord.getThreadId());
if ((threadRecipient != null && threadRecipient.isGroup()) || SignalDatabase.attachments().getAttachmentsForMessage(messageId).size() > 0) {
sendType = SendType.MMS;
} else {
sendType = SendType.SMS;
}
} else {
sendType = SendType.SIGNAL;
}
sendMessageInternal(context, recipient, sendType, messageId, Collections.emptyList(), false);
onMessageSent();
}
public static void onMessageSent() {
EventBus.getDefault().postSticky(MessageSentEvent.INSTANCE);
}
private static @NonNull OutgoingMessage applyUniversalExpireTimerIfNecessary(@NonNull Context context, @NonNull Recipient recipient, @NonNull OutgoingMessage outgoingMessage, long threadId) {
if (!outgoingMessage.isExpirationUpdate() && outgoingMessage.getExpiresIn() == 0 && RecipientUtil.setAndSendUniversalExpireTimerIfNecessary(context, recipient, threadId)) {
return outgoingMessage.withExpiry(TimeUnit.SECONDS.toMillis(SignalStore.settings().getUniversalExpireTimer()));
}
return outgoingMessage;
}
private static void sendMessageInternal(Context context,
Recipient recipient,
SendType sendType,
long messageId,
@NonNull Collection uploadJobIds,
boolean isScheduledSend)
{
if (isLocalSelfSend(context, recipient, sendType) && !isScheduledSend) {
sendLocalMediaSelf(context, messageId);
} else if (recipient.isPushGroup()) {
sendGroupPush(context, recipient, messageId, Collections.emptySet(), uploadJobIds);
} else if (recipient.isDistributionList()) {
sendDistributionList(context, recipient, messageId, Collections.emptySet(), uploadJobIds);
} else if (sendType == SendType.SIGNAL && isPushMediaSend(context, recipient)) {
sendMediaPush(context, recipient, messageId, uploadJobIds);
} else {
Log.w(TAG, "Unknown send type!");
}
}
private static void sendMediaPush(Context context, Recipient recipient, long messageId, @NonNull Collection uploadJobIds) {
JobManager jobManager = ApplicationDependencies.getJobManager();
if (uploadJobIds.size() > 0) {
Job mediaSend = IndividualSendJob.create(messageId, recipient, true, false);
jobManager.add(mediaSend, uploadJobIds);
} else {
IndividualSendJob.enqueue(context, jobManager, messageId, recipient, false);
}
}
private static void sendGroupPush(@NonNull Context context, @NonNull Recipient recipient, long messageId, @NonNull Set filterRecipientIds, @NonNull Collection uploadJobIds) {
JobManager jobManager = ApplicationDependencies.getJobManager();
if (uploadJobIds.size() > 0) {
Job groupSend = new PushGroupSendJob(messageId, recipient.getId(), filterRecipientIds, !uploadJobIds.isEmpty(), false);
jobManager.add(groupSend, uploadJobIds, uploadJobIds.isEmpty() ? null : recipient.getId().toQueueKey());
} else {
PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientIds, false);
}
}
private static void sendDistributionList(@NonNull Context context, @NonNull Recipient recipient, long messageId, @NonNull Set filterRecipientIds, @NonNull Collection uploadJobIds) {
JobManager jobManager = ApplicationDependencies.getJobManager();
if (uploadJobIds.size() > 0) {
Job groupSend = new PushDistributionListSendJob(messageId, recipient.getId(), !uploadJobIds.isEmpty(), filterRecipientIds);
jobManager.add(groupSend, uploadJobIds, uploadJobIds.isEmpty() ? null : recipient.getId().toQueueKey());
} else {
PushDistributionListSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientIds);
}
}
private static boolean isPushMediaSend(Context context, Recipient recipient) {
if (!SignalStore.account().isRegistered()) {
return false;
}
if (recipient.isGroup()) {
return false;
}
return isPushDestination(context, recipient);
}
private static boolean isPushDestination(Context context, Recipient destination) {
if (destination.resolve().getRegistered() == RecipientTable.RegisteredState.REGISTERED) {
return true;
} else if (destination.resolve().getRegistered() == RecipientTable.RegisteredState.NOT_REGISTERED) {
return false;
} else {
try {
RecipientTable.RegisteredState state = ContactDiscovery.refresh(context, destination, false);
return state == RecipientTable.RegisteredState.REGISTERED;
} catch (IOException e1) {
Log.w(TAG, e1);
return false;
}
}
}
public static boolean isLocalSelfSend(@NonNull Context context, @Nullable Recipient recipient, SendType sendType) {
return recipient != null &&
recipient.isSelf() &&
sendType == SendType.SIGNAL &&
SignalStore.account().isRegistered() &&
!TextSecurePreferences.isMultiDevice(context);
}
private static void sendLocalMediaSelf(Context context, long messageId) {
try {
ExpiringMessageManager expirationManager = ApplicationDependencies.getExpiringMessageManager();
MessageTable mmsDatabase = SignalDatabase.messages();
OutgoingMessage message = mmsDatabase.getOutgoingMessage(messageId);
SyncMessageId syncId = new SyncMessageId(Recipient.self().getId(), message.getSentTimeMillis());
List attachments = new LinkedList<>();
attachments.addAll(message.getAttachments());
attachments.addAll(Stream.of(message.getLinkPreviews())
.map(LinkPreview::getThumbnail)
.filter(Optional::isPresent)
.map(Optional::get)
.toList());
attachments.addAll(Stream.of(message.getSharedContacts())
.map(Contact::getAvatar).withoutNulls()
.map(Contact.Avatar::getAttachment).withoutNulls()
.toList());
List compressionJobs = Stream.of(attachments)
.map(a -> AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, false, -1))
.toList();
List fakeUploadJobs = Stream.of(attachments)
.map(a -> new AttachmentMarkUploadedJob(messageId, ((DatabaseAttachment) a).attachmentId))
.toList();
ApplicationDependencies.getJobManager().startChain(compressionJobs)
.then(fakeUploadJobs)
.enqueue();
mmsDatabase.markAsSent(messageId, true);
mmsDatabase.markUnidentified(messageId, true);
mmsDatabase.incrementDeliveryReceiptCount(message.getSentTimeMillis(), Recipient.self().getId(), System.currentTimeMillis());
mmsDatabase.incrementReadReceiptCount(message.getSentTimeMillis(), Recipient.self().getId(), System.currentTimeMillis());
mmsDatabase.incrementViewedReceiptCount(message.getSentTimeMillis(), Recipient.self().getId(), System.currentTimeMillis());
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
mmsDatabase.markExpireStarted(messageId);
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
}
} catch (NoSuchMessageException | MmsException e) {
Log.w(TAG, "Failed to update self-sent message.", e);
}
}
public static class PreUploadResult implements Parcelable {
private final Media media;
private final AttachmentId attachmentId;
private final Collection jobIds;
PreUploadResult(@NonNull Media media, @NonNull AttachmentId attachmentId, @NonNull Collection jobIds) {
this.media = media;
this.attachmentId = attachmentId;
this.jobIds = jobIds;
}
private PreUploadResult(Parcel in) {
this.attachmentId = in.readParcelable(AttachmentId.class.getClassLoader());
this.jobIds = ParcelUtil.readStringCollection(in);
this.media = in.readParcelable(Media.class.getClassLoader());
}
public @NonNull AttachmentId getAttachmentId() {
return attachmentId;
}
public @NonNull Collection getJobIds() {
return jobIds;
}
public @NonNull Media getMedia() {
return media;
}
public static final Creator CREATOR = new Creator() {
@Override
public PreUploadResult createFromParcel(Parcel in) {
return new PreUploadResult(in);
}
@Override
public PreUploadResult[] newArray(int size) {
return new PreUploadResult[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(attachmentId, flags);
ParcelUtil.writeStringCollection(dest, jobIds);
dest.writeParcelable(media, flags);
}
@Override
public @NonNull String toString() {
return "{ID: " + attachmentId.id + ", URI: " + media.getUri() + ", Jobs: " + jobIds.stream().map(j -> "JOB::" + j).collect(Collectors.toList()) + "}";
}
}
public enum MessageSentEvent {
INSTANCE
}
public enum SendType {
SIGNAL, SMS, MMS
}
}