That fuck shit the fascists are using
at master 727 lines 34 kB view raw
1/* 2 * Copyright (C) 2011 Whisper Systems 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17package org.tm.archive.sms; 18 19import android.content.Context; 20import android.os.Parcel; 21import android.os.Parcelable; 22 23import androidx.annotation.NonNull; 24import androidx.annotation.Nullable; 25import androidx.annotation.WorkerThread; 26 27import com.annimon.stream.Stream; 28 29import org.greenrobot.eventbus.EventBus; 30import org.signal.core.util.logging.Log; 31import org.tm.archive.attachments.Attachment; 32import org.tm.archive.attachments.AttachmentId; 33import org.tm.archive.attachments.DatabaseAttachment; 34import org.tm.archive.contacts.sync.ContactDiscovery; 35import org.tm.archive.contactshare.Contact; 36import org.tm.archive.database.AttachmentTable; 37import org.tm.archive.database.MessageTable; 38import org.tm.archive.database.MessageTable.SyncMessageId; 39import org.tm.archive.database.NoSuchMessageException; 40import org.tm.archive.database.RecipientTable; 41import org.tm.archive.database.SignalDatabase; 42import org.tm.archive.database.ThreadTable; 43import org.tm.archive.database.model.MessageId; 44import org.tm.archive.database.model.MessageRecord; 45import org.tm.archive.database.model.MmsMessageRecord; 46import org.tm.archive.database.model.ReactionRecord; 47import org.tm.archive.database.model.StoryType; 48import org.tm.archive.dependencies.ApplicationDependencies; 49import org.tm.archive.jobmanager.Job; 50import org.tm.archive.jobmanager.JobManager; 51import org.tm.archive.jobs.AttachmentCompressionJob; 52import org.tm.archive.jobs.AttachmentCopyJob; 53import org.tm.archive.jobs.AttachmentMarkUploadedJob; 54import org.tm.archive.jobs.AttachmentUploadJob; 55import org.tm.archive.jobs.ProfileKeySendJob; 56import org.tm.archive.jobs.PushDistributionListSendJob; 57import org.tm.archive.jobs.PushGroupSendJob; 58import org.tm.archive.jobs.IndividualSendJob; 59import org.tm.archive.jobs.ReactionSendJob; 60import org.tm.archive.jobs.RemoteDeleteSendJob; 61import org.tm.archive.keyvalue.SignalStore; 62import org.tm.archive.linkpreview.LinkPreview; 63import org.tm.archive.mediasend.Media; 64import org.tm.archive.mms.MmsException; 65import org.tm.archive.mms.OutgoingMessage; 66import org.tm.archive.recipients.Recipient; 67import org.tm.archive.recipients.RecipientId; 68import org.tm.archive.recipients.RecipientUtil; 69import org.tm.archive.service.ExpiringMessageManager; 70import org.tm.archive.util.ParcelUtil; 71import org.tm.archive.util.SignalLocalMetrics; 72import org.tm.archive.util.TextSecurePreferences; 73import org.whispersystems.signalservice.api.push.DistributionId; 74import org.whispersystems.signalservice.api.util.Preconditions; 75 76import java.io.IOException; 77import java.util.ArrayList; 78import java.util.Arrays; 79import java.util.Collection; 80import java.util.Collections; 81import java.util.LinkedList; 82import java.util.List; 83import java.util.Objects; 84import java.util.Optional; 85import java.util.Set; 86import java.util.concurrent.TimeUnit; 87import java.util.stream.Collectors; 88 89public class MessageSender { 90 91 private static final String TAG = Log.tag(MessageSender.class); 92 93 /** 94 * Suitable for a 1:1 conversation or a GV1 group only. 95 */ 96 @WorkerThread 97 public static void sendProfileKey(final long threadId) { 98 ProfileKeySendJob job = ProfileKeySendJob.create(threadId, false); 99 if (job != null) { 100 ApplicationDependencies.getJobManager().add(job); 101 } 102 } 103 104 public static void sendStories(@NonNull final Context context, 105 @NonNull final List<OutgoingMessage> messages, 106 @Nullable final String metricId, 107 @Nullable final MessageTable.InsertListener insertListener) 108 { 109 Log.i(TAG, "Sending story messages to " + messages.size() + " targets."); 110 ThreadTable threadTable = SignalDatabase.threads(); 111 MessageTable database = SignalDatabase.messages(); 112 List<Long> messageIds = new ArrayList<>(messages.size()); 113 List<Long> threads = new ArrayList<>(messages.size()); 114 UploadDependencyGraph dependencyGraph; 115 116 try { 117 database.beginTransaction(); 118 119 for (OutgoingMessage message : messages) { 120 long allocatedThreadId = threadTable.getOrCreateValidThreadId(message.getThreadRecipient(), -1L, message.getDistributionType()); 121 long messageId = database.insertMessageOutbox(message.stripAttachments(), allocatedThreadId, false, insertListener); 122 123 messageIds.add(messageId); 124 threads.add(allocatedThreadId); 125 126 if (message.getThreadRecipient().isGroup() && message.getAttachments().isEmpty() && message.getLinkPreviews().isEmpty() && message.getSharedContacts().isEmpty()) { 127 SignalLocalMetrics.GroupMessageSend.onInsertedIntoDatabase(messageId, metricId); 128 } else { 129 SignalLocalMetrics.GroupMessageSend.cancel(metricId); 130 } 131 } 132 133 for (int i = 0; i < messageIds.size(); i++) { 134 long messageId = messageIds.get(i); 135 OutgoingMessage message = messages.get(i); 136 Recipient recipient = message.getThreadRecipient(); 137 138 if (recipient.isDistributionList()) { 139 DistributionId distributionId = Objects.requireNonNull(SignalDatabase.distributionLists().getDistributionId(recipient.requireDistributionListId())); 140 List<RecipientId> members = SignalDatabase.distributionLists().getMembers(recipient.requireDistributionListId()); 141 SignalDatabase.storySends().insert(messageId, members, message.getSentTimeMillis(), message.getStoryType().isStoryWithReplies(), distributionId); 142 } 143 } 144 145 dependencyGraph = UploadDependencyGraph.create( 146 messages, 147 ApplicationDependencies.getJobManager(), 148 attachment -> { 149 try { 150 return SignalDatabase.attachments().insertAttachmentForPreUpload(attachment); 151 } catch (MmsException e) { 152 Log.e(TAG, e); 153 throw new IllegalStateException(e); 154 } 155 } 156 ); 157 158 for (int i = 0; i < messageIds.size(); i++) { 159 long messageId = messageIds.get(i); 160 OutgoingMessage message = messages.get(i); 161 List<UploadDependencyGraph.Node> nodes = dependencyGraph.getDependencyMap().get(message); 162 163 if (nodes == null || nodes.isEmpty()) { 164 if (message.getStoryType().isTextStory()) { 165 Log.d(TAG, "No attachments for given text story. Skipping."); 166 continue; 167 } else { 168 Log.e(TAG, "No attachments for given media story. Aborting."); 169 throw new MmsException("No attachment for story."); 170 } 171 } 172 173 List<AttachmentId> attachmentIds = nodes.stream().map(UploadDependencyGraph.Node::getAttachmentId).collect(Collectors.toList()); 174 SignalDatabase.attachments().updateMessageId(attachmentIds, messageId, true); 175 for (final AttachmentId attachmentId : attachmentIds) { 176 SignalDatabase.attachments().updateAttachmentCaption(attachmentId, message.getBody()); 177 } 178 } 179 180 database.setTransactionSuccessful(); 181 } catch (MmsException e) { 182 Log.w(TAG, "Failed to send stories.", e); 183 return; 184 } finally { 185 database.endTransaction(); 186 } 187 188 List<JobManager.Chain> chains = dependencyGraph.consumeDeferredQueue(); 189 for (final JobManager.Chain chain : chains) { 190 chain.enqueue(); 191 } 192 193 for (int i = 0; i < messageIds.size(); i++) { 194 long messageId = messageIds.get(i); 195 OutgoingMessage message = messages.get(i); 196 Recipient recipient = message.getThreadRecipient(); 197 List<UploadDependencyGraph.Node> dependencies = dependencyGraph.getDependencyMap().get(message); 198 199 List<String> jobDependencyIds = (dependencies != null) ? dependencies.stream().map(UploadDependencyGraph.Node::getJobId).collect(Collectors.toList()) 200 : Collections.emptyList(); 201 202 sendMessageInternal(context, 203 recipient, 204 SendType.SIGNAL, 205 messageId, 206 jobDependencyIds, 207 false); 208 } 209 210 onMessageSent(); 211 212 for (long threadId : threads) { 213 threadTable.update(threadId, true); 214 } 215 } 216 217 public static long send(final Context context, 218 final OutgoingMessage message, 219 final long threadId, 220 @NonNull SendType sendType, 221 @Nullable final String metricId, 222 @Nullable final MessageTable.InsertListener insertListener) 223 { 224 Log.i(TAG, "Sending media message to " + message.getThreadRecipient().getId() + ", thread: " + threadId); 225 try { 226 ThreadTable threadTable = SignalDatabase.threads(); 227 MessageTable database = SignalDatabase.messages(); 228 229 long allocatedThreadId = threadTable.getOrCreateValidThreadId(message.getThreadRecipient(), threadId, message.getDistributionType()); 230 Recipient recipient = message.getThreadRecipient(); 231 long messageId = database.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, recipient, message, allocatedThreadId), allocatedThreadId, sendType != SendType.SIGNAL, insertListener); 232 233 if (message.getThreadRecipient().isGroup()) { 234 if (message.getAttachments().isEmpty() && message.getLinkPreviews().isEmpty() && message.getSharedContacts().isEmpty()) { 235 SignalLocalMetrics.GroupMessageSend.onInsertedIntoDatabase(messageId, metricId); 236 } else { 237 SignalLocalMetrics.GroupMessageSend.cancel(messageId); 238 } 239 } else { 240 SignalLocalMetrics.IndividualMessageSend.onInsertedIntoDatabase(messageId, metricId); 241 } 242 243 sendMessageInternal(context, recipient, sendType, messageId, Collections.emptyList(), message.getScheduledDate() > 0); 244 onMessageSent(); 245 threadTable.update(allocatedThreadId, true); 246 247 return allocatedThreadId; 248 } catch (MmsException e) { 249 Log.w(TAG, e); 250 return threadId; 251 } 252 } 253 254 public static long sendPushWithPreUploadedMedia(final Context context, 255 final OutgoingMessage message, 256 final Collection<PreUploadResult> preUploadResults, 257 final long threadId, 258 final MessageTable.InsertListener insertListener) 259 { 260 Log.i(TAG, "Sending media message with pre-uploads to " + message.getThreadRecipient().getId() + ", thread: " + threadId + ", pre-uploads: " + preUploadResults); 261 Preconditions.checkArgument(message.getAttachments().isEmpty(), "If the media is pre-uploaded, there should be no attachments on the message."); 262 263 try { 264 ThreadTable threadTable = SignalDatabase.threads(); 265 MessageTable mmsDatabase = SignalDatabase.messages(); 266 AttachmentTable attachmentDatabase = SignalDatabase.attachments(); 267 268 Recipient recipient = message.getThreadRecipient(); 269 long allocatedThreadId = threadTable.getOrCreateValidThreadId(message.getThreadRecipient(), threadId); 270 long messageId = mmsDatabase.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, recipient, message, allocatedThreadId), 271 allocatedThreadId, 272 false, 273 insertListener); 274 275 List<AttachmentId> attachmentIds = Stream.of(preUploadResults).map(PreUploadResult::getAttachmentId).toList(); 276 List<String> jobIds = Stream.of(preUploadResults).map(PreUploadResult::getJobIds).flatMap(Stream::of).toList(); 277 278 attachmentDatabase.updateMessageId(attachmentIds, messageId, message.getStoryType().isStory()); 279 280 sendMessageInternal(context, recipient, SendType.SIGNAL, messageId, jobIds, false); 281 onMessageSent(); 282 threadTable.update(allocatedThreadId, true); 283 284 return allocatedThreadId; 285 } catch (MmsException e) { 286 Log.w(TAG, e); 287 return threadId; 288 } 289 } 290 291 public static void sendMediaBroadcast(@NonNull Context context, 292 @NonNull List<OutgoingMessage> messages, 293 @NonNull Collection<PreUploadResult> preUploadResults, 294 boolean overwritePreUploadMessageIds) 295 { 296 Log.i(TAG, "Sending media broadcast (overwrite: " + overwritePreUploadMessageIds + ") to " + Stream.of(messages).map(m -> m.getThreadRecipient().getId()).toList()); 297 Preconditions.checkArgument(messages.size() > 0, "No messages!"); 298 Preconditions.checkArgument(Stream.of(messages).allMatch(m -> m.getAttachments().isEmpty()), "Messages can't have attachments! They should be pre-uploaded."); 299 300 JobManager jobManager = ApplicationDependencies.getJobManager(); 301 AttachmentTable attachmentDatabase = SignalDatabase.attachments(); 302 MessageTable mmsDatabase = SignalDatabase.messages(); 303 ThreadTable threadTable = SignalDatabase.threads(); 304 List<AttachmentId> preUploadAttachmentIds = Stream.of(preUploadResults).map(PreUploadResult::getAttachmentId).toList(); 305 List<String> preUploadJobIds = Stream.of(preUploadResults).map(PreUploadResult::getJobIds).flatMap(Stream::of).toList(); 306 List<Long> messageIds = new ArrayList<>(messages.size()); 307 List<String> messageDependsOnIds = new ArrayList<>(preUploadJobIds); 308 OutgoingMessage primaryMessage = messages.get(0); 309 310 mmsDatabase.beginTransaction(); 311 try { 312 if (overwritePreUploadMessageIds) { 313 long primaryThreadId = threadTable.getOrCreateThreadIdFor(primaryMessage.getThreadRecipient(), primaryMessage.getDistributionType()); 314 long primaryMessageId = mmsDatabase.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, primaryMessage.getThreadRecipient(), primaryMessage, primaryThreadId), 315 primaryThreadId, 316 false, 317 null); 318 319 attachmentDatabase.updateMessageId(preUploadAttachmentIds, primaryMessageId, primaryMessage.getStoryType().isStory()); 320 if (primaryMessage.getStoryType() != StoryType.NONE) { 321 for (final AttachmentId preUploadAttachmentId : preUploadAttachmentIds) { 322 attachmentDatabase.updateAttachmentCaption(preUploadAttachmentId, primaryMessage.getBody()); 323 } 324 } 325 messageIds.add(primaryMessageId); 326 } 327 328 List<DatabaseAttachment> preUploadAttachments = Stream.of(preUploadAttachmentIds) 329 .map(attachmentDatabase::getAttachment) 330 .toList(); 331 332 if (messages.size() > 0) { 333 List<OutgoingMessage> secondaryMessages = overwritePreUploadMessageIds ? messages.subList(1, messages.size()) : messages; 334 List<List<AttachmentId>> attachmentCopies = new ArrayList<>(); 335 336 for (int i = 0; i < preUploadAttachmentIds.size(); i++) { 337 attachmentCopies.add(new ArrayList<>(messages.size())); 338 } 339 340 for (OutgoingMessage secondaryMessage : secondaryMessages) { 341 long allocatedThreadId = threadTable.getOrCreateThreadIdFor(secondaryMessage.getThreadRecipient(), secondaryMessage.getDistributionType()); 342 long messageId = mmsDatabase.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, secondaryMessage.getThreadRecipient(), secondaryMessage, allocatedThreadId), 343 allocatedThreadId, 344 false, 345 null); 346 List<AttachmentId> attachmentIds = new ArrayList<>(preUploadAttachmentIds.size()); 347 348 for (int i = 0; i < preUploadAttachments.size(); i++) { 349 AttachmentId attachmentId = attachmentDatabase.insertAttachmentForPreUpload(preUploadAttachments.get(i)).attachmentId; 350 attachmentCopies.get(i).add(attachmentId); 351 attachmentIds.add(attachmentId); 352 } 353 354 attachmentDatabase.updateMessageId(attachmentIds, messageId, secondaryMessage.getStoryType().isStory()); 355 if (primaryMessage.getStoryType() != StoryType.NONE) { 356 for (final AttachmentId preUploadAttachmentId : attachmentIds) { 357 attachmentDatabase.updateAttachmentCaption(preUploadAttachmentId, primaryMessage.getBody()); 358 } 359 } 360 361 messageIds.add(messageId); 362 } 363 364 for (int i = 0; i < attachmentCopies.size(); i++) { 365 Job copyJob = new AttachmentCopyJob(preUploadAttachmentIds.get(i), attachmentCopies.get(i)); 366 jobManager.add(copyJob, preUploadJobIds); 367 messageDependsOnIds.add(copyJob.getId()); 368 } 369 } 370 371 for (int i = 0; i < messageIds.size(); i++) { 372 long messageId = messageIds.get(i); 373 OutgoingMessage message = messages.get(i); 374 Recipient recipient = message.getThreadRecipient(); 375 376 if (recipient.isDistributionList()) { 377 List<RecipientId> members = SignalDatabase.distributionLists().getMembers(recipient.requireDistributionListId()); 378 DistributionId distributionId = Objects.requireNonNull(SignalDatabase.distributionLists().getDistributionId(recipient.requireDistributionListId())); 379 SignalDatabase.storySends().insert(messageId, members, message.getSentTimeMillis(), message.getStoryType().isStoryWithReplies(), distributionId); 380 } 381 } 382 383 onMessageSent(); 384 mmsDatabase.setTransactionSuccessful(); 385 } catch (MmsException e) { 386 Log.w(TAG, "Failed to send messages.", e); 387 return; 388 } finally { 389 mmsDatabase.endTransaction(); 390 } 391 392 for (int i = 0; i < messageIds.size(); i++) { 393 long messageId = messageIds.get(i); 394 Recipient recipient = messages.get(i).getThreadRecipient(); 395 396 if (isLocalSelfSend(context, recipient, SendType.SIGNAL)) { 397 sendLocalMediaSelf(context, messageId); 398 } else if (recipient.isPushGroup()) { 399 jobManager.add(new PushGroupSendJob(messageId, recipient.getId(), Collections.emptySet(), true, false), messageDependsOnIds, recipient.getId().toQueueKey()); 400 } else if (recipient.isDistributionList()) { 401 jobManager.add(new PushDistributionListSendJob(messageId, recipient.getId(), true, Collections.emptySet()), messageDependsOnIds, recipient.getId().toQueueKey()); 402 } else { 403 jobManager.add(IndividualSendJob.create(messageId, recipient, true, false), messageDependsOnIds, recipient.getId().toQueueKey()); 404 } 405 } 406 } 407 408 /** 409 * @return A result if the attachment was enqueued, or null if it failed to enqueue or shouldn't 410 * be enqueued (like in the case of a local self-send). 411 */ 412 public static @Nullable PreUploadResult preUploadPushAttachment(@NonNull Context context, @NonNull Attachment attachment, @Nullable Recipient recipient, @NonNull Media media) { 413 if (isLocalSelfSend(context, recipient, SendType.SIGNAL)) { 414 return null; 415 } 416 Log.i(TAG, "Pre-uploading attachment for " + (recipient != null ? recipient.getId() : "null")); 417 418 try { 419 AttachmentTable attachmentDatabase = SignalDatabase.attachments(); 420 DatabaseAttachment databaseAttachment = attachmentDatabase.insertAttachmentForPreUpload(attachment); 421 422 Job compressionJob = AttachmentCompressionJob.fromAttachment(databaseAttachment, false, -1); 423 Job uploadJob = new AttachmentUploadJob(databaseAttachment.attachmentId); 424 425 ApplicationDependencies.getJobManager() 426 .startChain(compressionJob) 427 .then(uploadJob) 428 .enqueue(); 429 430 return new PreUploadResult(media, databaseAttachment.attachmentId, Arrays.asList(compressionJob.getId(), uploadJob.getId())); 431 } catch (MmsException e) { 432 Log.w(TAG, "preUploadPushAttachment() - Failed to upload!", e); 433 return null; 434 } 435 } 436 437 public static void sendNewReaction(@NonNull Context context, @NonNull MessageId messageId, @NonNull String emoji) { 438 ReactionRecord reaction = new ReactionRecord(emoji, Recipient.self().getId(), System.currentTimeMillis(), System.currentTimeMillis()); 439 SignalDatabase.reactions().addReaction(messageId, reaction); 440 441 try { 442 ApplicationDependencies.getJobManager().add(ReactionSendJob.create(context, messageId, reaction, false)); 443 onMessageSent(); 444 } catch (NoSuchMessageException e) { 445 Log.w(TAG, "[sendNewReaction] Could not find message! Ignoring."); 446 } 447 } 448 449 public static void sendReactionRemoval(@NonNull Context context, @NonNull MessageId messageId, @NonNull ReactionRecord reaction) { 450 SignalDatabase.reactions().deleteReaction(messageId, reaction.getAuthor()); 451 452 try { 453 ApplicationDependencies.getJobManager().add(ReactionSendJob.create(context, messageId, reaction, true)); 454 onMessageSent(); 455 } catch (NoSuchMessageException e) { 456 Log.w(TAG, "[sendReactionRemoval] Could not find message! Ignoring."); 457 } 458 } 459 460 public static void sendRemoteDelete(long messageId) { 461 MessageTable db = SignalDatabase.messages(); 462 db.markAsRemoteDelete(messageId); 463 db.markAsSending(messageId); 464 465 try { 466 RemoteDeleteSendJob.create(messageId).enqueue(); 467 onMessageSent(); 468 } catch (NoSuchMessageException e) { 469 Log.w(TAG, "[sendRemoteDelete] Could not find message! Ignoring."); 470 } 471 } 472 473 public static void resendGroupMessage(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set<RecipientId> filterRecipientIds) { 474 if (!messageRecord.isMms()) throw new AssertionError("Not Group"); 475 sendGroupPush(context, messageRecord.getToRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList()); 476 onMessageSent(); 477 } 478 479 public static void resendDistributionList(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set<RecipientId> filterRecipientIds) { 480 if (!messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getStoryType().isStory()) { 481 throw new AssertionError("Not a story"); 482 } 483 sendDistributionList(context, messageRecord.getToRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList()); 484 onMessageSent(); 485 } 486 487 public static void resend(Context context, MessageRecord messageRecord) { 488 long messageId = messageRecord.getId(); 489 boolean forceSms = messageRecord.isForcedSms(); 490 Recipient recipient = messageRecord.getToRecipient(); 491 492 SendType sendType; 493 494 if (forceSms) { 495 Recipient threadRecipient = SignalDatabase.threads().getRecipientForThreadId(messageRecord.getThreadId()); 496 497 if ((threadRecipient != null && threadRecipient.isGroup()) || SignalDatabase.attachments().getAttachmentsForMessage(messageId).size() > 0) { 498 sendType = SendType.MMS; 499 } else { 500 sendType = SendType.SMS; 501 } 502 } else { 503 sendType = SendType.SIGNAL; 504 } 505 506 sendMessageInternal(context, recipient, sendType, messageId, Collections.emptyList(), false); 507 508 onMessageSent(); 509 } 510 511 public static void onMessageSent() { 512 EventBus.getDefault().postSticky(MessageSentEvent.INSTANCE); 513 } 514 515 private static @NonNull OutgoingMessage applyUniversalExpireTimerIfNecessary(@NonNull Context context, @NonNull Recipient recipient, @NonNull OutgoingMessage outgoingMessage, long threadId) { 516 if (!outgoingMessage.isExpirationUpdate() && outgoingMessage.getExpiresIn() == 0 && RecipientUtil.setAndSendUniversalExpireTimerIfNecessary(context, recipient, threadId)) { 517 return outgoingMessage.withExpiry(TimeUnit.SECONDS.toMillis(SignalStore.settings().getUniversalExpireTimer())); 518 } 519 return outgoingMessage; 520 } 521 522 private static void sendMessageInternal(Context context, 523 Recipient recipient, 524 SendType sendType, 525 long messageId, 526 @NonNull Collection<String> uploadJobIds, 527 boolean isScheduledSend) 528 { 529 if (isLocalSelfSend(context, recipient, sendType) && !isScheduledSend) { 530 sendLocalMediaSelf(context, messageId); 531 } else if (recipient.isPushGroup()) { 532 sendGroupPush(context, recipient, messageId, Collections.emptySet(), uploadJobIds); 533 } else if (recipient.isDistributionList()) { 534 sendDistributionList(context, recipient, messageId, Collections.emptySet(), uploadJobIds); 535 } else if (sendType == SendType.SIGNAL && isPushMediaSend(context, recipient)) { 536 sendMediaPush(context, recipient, messageId, uploadJobIds); 537 } else { 538 Log.w(TAG, "Unknown send type!"); 539 } 540 } 541 542 private static void sendMediaPush(Context context, Recipient recipient, long messageId, @NonNull Collection<String> uploadJobIds) { 543 JobManager jobManager = ApplicationDependencies.getJobManager(); 544 545 if (uploadJobIds.size() > 0) { 546 Job mediaSend = IndividualSendJob.create(messageId, recipient, true, false); 547 jobManager.add(mediaSend, uploadJobIds); 548 } else { 549 IndividualSendJob.enqueue(context, jobManager, messageId, recipient, false); 550 } 551 } 552 553 private static void sendGroupPush(@NonNull Context context, @NonNull Recipient recipient, long messageId, @NonNull Set<RecipientId> filterRecipientIds, @NonNull Collection<String> uploadJobIds) { 554 JobManager jobManager = ApplicationDependencies.getJobManager(); 555 556 if (uploadJobIds.size() > 0) { 557 Job groupSend = new PushGroupSendJob(messageId, recipient.getId(), filterRecipientIds, !uploadJobIds.isEmpty(), false); 558 jobManager.add(groupSend, uploadJobIds, uploadJobIds.isEmpty() ? null : recipient.getId().toQueueKey()); 559 } else { 560 PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientIds, false); 561 } 562 } 563 564 private static void sendDistributionList(@NonNull Context context, @NonNull Recipient recipient, long messageId, @NonNull Set<RecipientId> filterRecipientIds, @NonNull Collection<String> uploadJobIds) { 565 JobManager jobManager = ApplicationDependencies.getJobManager(); 566 567 if (uploadJobIds.size() > 0) { 568 Job groupSend = new PushDistributionListSendJob(messageId, recipient.getId(), !uploadJobIds.isEmpty(), filterRecipientIds); 569 jobManager.add(groupSend, uploadJobIds, uploadJobIds.isEmpty() ? null : recipient.getId().toQueueKey()); 570 } else { 571 PushDistributionListSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientIds); 572 } 573 } 574 575 private static boolean isPushMediaSend(Context context, Recipient recipient) { 576 if (!SignalStore.account().isRegistered()) { 577 return false; 578 } 579 580 if (recipient.isGroup()) { 581 return false; 582 } 583 584 return isPushDestination(context, recipient); 585 } 586 587 private static boolean isPushDestination(Context context, Recipient destination) { 588 if (destination.resolve().getRegistered() == RecipientTable.RegisteredState.REGISTERED) { 589 return true; 590 } else if (destination.resolve().getRegistered() == RecipientTable.RegisteredState.NOT_REGISTERED) { 591 return false; 592 } else { 593 try { 594 RecipientTable.RegisteredState state = ContactDiscovery.refresh(context, destination, false); 595 return state == RecipientTable.RegisteredState.REGISTERED; 596 } catch (IOException e1) { 597 Log.w(TAG, e1); 598 return false; 599 } 600 } 601 } 602 603 public static boolean isLocalSelfSend(@NonNull Context context, @Nullable Recipient recipient, SendType sendType) { 604 return recipient != null && 605 recipient.isSelf() && 606 sendType == SendType.SIGNAL && 607 SignalStore.account().isRegistered() && 608 !TextSecurePreferences.isMultiDevice(context); 609 } 610 611 private static void sendLocalMediaSelf(Context context, long messageId) { 612 try { 613 ExpiringMessageManager expirationManager = ApplicationDependencies.getExpiringMessageManager(); 614 MessageTable mmsDatabase = SignalDatabase.messages(); 615 OutgoingMessage message = mmsDatabase.getOutgoingMessage(messageId); 616 SyncMessageId syncId = new SyncMessageId(Recipient.self().getId(), message.getSentTimeMillis()); 617 List<Attachment> attachments = new LinkedList<>(); 618 619 620 attachments.addAll(message.getAttachments()); 621 622 attachments.addAll(Stream.of(message.getLinkPreviews()) 623 .map(LinkPreview::getThumbnail) 624 .filter(Optional::isPresent) 625 .map(Optional::get) 626 .toList()); 627 628 attachments.addAll(Stream.of(message.getSharedContacts()) 629 .map(Contact::getAvatar).withoutNulls() 630 .map(Contact.Avatar::getAttachment).withoutNulls() 631 .toList()); 632 633 List<AttachmentCompressionJob> compressionJobs = Stream.of(attachments) 634 .map(a -> AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, false, -1)) 635 .toList(); 636 637 List<AttachmentMarkUploadedJob> fakeUploadJobs = Stream.of(attachments) 638 .map(a -> new AttachmentMarkUploadedJob(messageId, ((DatabaseAttachment) a).attachmentId)) 639 .toList(); 640 641 ApplicationDependencies.getJobManager().startChain(compressionJobs) 642 .then(fakeUploadJobs) 643 .enqueue(); 644 645 mmsDatabase.markAsSent(messageId, true); 646 mmsDatabase.markUnidentified(messageId, true); 647 648 mmsDatabase.incrementDeliveryReceiptCount(message.getSentTimeMillis(), Recipient.self().getId(), System.currentTimeMillis()); 649 mmsDatabase.incrementReadReceiptCount(message.getSentTimeMillis(), Recipient.self().getId(), System.currentTimeMillis()); 650 mmsDatabase.incrementViewedReceiptCount(message.getSentTimeMillis(), Recipient.self().getId(), System.currentTimeMillis()); 651 652 if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { 653 mmsDatabase.markExpireStarted(messageId); 654 expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn()); 655 } 656 } catch (NoSuchMessageException | MmsException e) { 657 Log.w(TAG, "Failed to update self-sent message.", e); 658 } 659 } 660 661 public static class PreUploadResult implements Parcelable { 662 private final Media media; 663 private final AttachmentId attachmentId; 664 private final Collection<String> jobIds; 665 666 PreUploadResult(@NonNull Media media, @NonNull AttachmentId attachmentId, @NonNull Collection<String> jobIds) { 667 this.media = media; 668 this.attachmentId = attachmentId; 669 this.jobIds = jobIds; 670 } 671 672 private PreUploadResult(Parcel in) { 673 this.attachmentId = in.readParcelable(AttachmentId.class.getClassLoader()); 674 this.jobIds = ParcelUtil.readStringCollection(in); 675 this.media = in.readParcelable(Media.class.getClassLoader()); 676 } 677 678 public @NonNull AttachmentId getAttachmentId() { 679 return attachmentId; 680 } 681 682 public @NonNull Collection<String> getJobIds() { 683 return jobIds; 684 } 685 686 public @NonNull Media getMedia() { 687 return media; 688 } 689 690 public static final Creator<PreUploadResult> CREATOR = new Creator<PreUploadResult>() { 691 @Override 692 public PreUploadResult createFromParcel(Parcel in) { 693 return new PreUploadResult(in); 694 } 695 696 @Override 697 public PreUploadResult[] newArray(int size) { 698 return new PreUploadResult[size]; 699 } 700 }; 701 702 @Override 703 public int describeContents() { 704 return 0; 705 } 706 707 @Override 708 public void writeToParcel(Parcel dest, int flags) { 709 dest.writeParcelable(attachmentId, flags); 710 ParcelUtil.writeStringCollection(dest, jobIds); 711 dest.writeParcelable(media, flags); 712 } 713 714 @Override 715 public @NonNull String toString() { 716 return "{ID: " + attachmentId.id + ", URI: " + media.getUri() + ", Jobs: " + jobIds.stream().map(j -> "JOB::" + j).collect(Collectors.toList()) + "}"; 717 } 718 } 719 720 public enum MessageSentEvent { 721 INSTANCE 722 } 723 724 public enum SendType { 725 SIGNAL, SMS, MMS 726 } 727}