use of org.thoughtcrime.securesms.mms.MmsException in project Signal-Android by WhisperSystems.
the class AttachmentCompressionJob method transcodeVideoIfNeededToDatabase.
@NonNull
private static DatabaseAttachment transcodeVideoIfNeededToDatabase(@NonNull Context context, @NonNull AttachmentDatabase attachmentDatabase, @NonNull DatabaseAttachment attachment, @NonNull MediaConstraints constraints, @NonNull EventBus eventBus, @NonNull TranscoderCancelationSignal cancelationSignal) throws UndeliverableMessageException {
AttachmentDatabase.TransformProperties transformProperties = attachment.getTransformProperties();
boolean allowSkipOnFailure = false;
if (!MediaConstraints.isVideoTranscodeAvailable()) {
if (transformProperties.isVideoEdited()) {
throw new UndeliverableMessageException("Video edited, but transcode is not available");
}
return attachment;
}
try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {
notification.setIndeterminateProgress();
try (MediaDataSource dataSource = attachmentDatabase.mediaDataSourceFor(attachment.getAttachmentId())) {
if (dataSource == null) {
throw new UndeliverableMessageException("Cannot get media data source for attachment.");
}
allowSkipOnFailure = !transformProperties.isVideoEdited();
TranscoderOptions options = null;
if (transformProperties.isVideoTrim()) {
options = new TranscoderOptions(transformProperties.getVideoTrimStartTimeUs(), transformProperties.getVideoTrimEndTimeUs());
}
if (FeatureFlags.useStreamingVideoMuxer() || !MemoryFileDescriptor.supported()) {
StreamingTranscoder transcoder = new StreamingTranscoder(dataSource, options, constraints.getCompressedVideoMaxSize(context));
if (transcoder.isTranscodeRequired()) {
Log.i(TAG, "Compressing with streaming muxer");
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
File file = SignalDatabase.attachments().newFile();
file.deleteOnExit();
try {
try (OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, true).second) {
transcoder.transcode(percent -> {
notification.setProgress(100, percent);
eventBus.postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.COMPRESSION, 100, percent));
}, outputStream, cancelationSignal);
}
MediaStream mediaStream = new MediaStream(ModernDecryptingPartInputStream.createFor(attachmentSecret, file, 0), MimeTypes.VIDEO_MP4, 0, 0);
attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited());
} finally {
if (!file.delete()) {
Log.w(TAG, "Failed to delete temp file");
}
}
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
return Objects.requireNonNull(attachmentDatabase.getAttachment(attachment.getAttachmentId()));
} else {
Log.i(TAG, "Transcode was not required");
}
} else {
try (InMemoryTranscoder transcoder = new InMemoryTranscoder(context, dataSource, options, constraints.getCompressedVideoMaxSize(context))) {
if (transcoder.isTranscodeRequired()) {
Log.i(TAG, "Compressing with android in-memory muxer");
MediaStream mediaStream = transcoder.transcode(percent -> {
notification.setProgress(100, percent);
eventBus.postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.COMPRESSION, 100, percent));
}, cancelationSignal);
attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited());
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
return Objects.requireNonNull(attachmentDatabase.getAttachment(attachment.getAttachmentId()));
} else {
Log.i(TAG, "Transcode was not required (in-memory transcoder)");
}
}
}
}
} catch (VideoSourceException | EncodingException | MemoryFileException e) {
if (attachment.getSize() > constraints.getVideoMaxSize(context)) {
throw new UndeliverableMessageException("Duration not found, attachment too large to skip transcode", e);
} else {
if (allowSkipOnFailure) {
Log.w(TAG, "Problem with video source, but video small enough to skip transcode", e);
} else {
throw new UndeliverableMessageException("Failed to transcode and cannot skip due to editing", e);
}
}
} catch (IOException | MmsException e) {
throw new UndeliverableMessageException("Failed to transcode", e);
}
return attachment;
}
use of org.thoughtcrime.securesms.mms.MmsException in project Signal-Android by WhisperSystems.
the class MessageContentProcessor method handleExpirationUpdate.
@Nullable
private MessageId handleExpirationUpdate(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional<Long> smsMessageId, @NonNull Optional<GroupId> groupId, @NonNull Recipient senderRecipient, @NonNull Recipient threadRecipient, long receivedTime) throws StorageFailedException {
log(content.getTimestamp(), "Expiration update.");
if (groupId.isPresent() && groupId.get().isV2()) {
warn(String.valueOf(content.getTimestamp()), "Expiration update received for GV2. Ignoring.");
return null;
}
int expiresInSeconds = message.getExpiresInSeconds();
Optional<SignalServiceGroupContext> groupContext = message.getGroupContext();
if (threadRecipient.getExpiresInSeconds() == expiresInSeconds) {
log(String.valueOf(content.getTimestamp()), "No change in message expiry for group. Ignoring.");
return null;
}
try {
MessageDatabase database = SignalDatabase.mms();
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(senderRecipient.getId(), content.getTimestamp(), content.getServerReceivedTimestamp(), receivedTime, -1, expiresInSeconds * 1000L, true, false, content.isNeedsReceipt(), Optional.absent(), groupContext, Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), content.getServerUuid());
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
SignalDatabase.recipients().setExpireMessages(threadRecipient.getId(), expiresInSeconds);
if (smsMessageId.isPresent()) {
SignalDatabase.sms().deleteMessage(smsMessageId.get());
}
if (insertResult.isPresent()) {
return new MessageId(insertResult.get().getMessageId(), true);
}
} catch (MmsException e) {
throw new StorageFailedException(e, content.getSender().getIdentifier(), content.getSenderDevice());
}
return null;
}
use of org.thoughtcrime.securesms.mms.MmsException in project Signal-Android by WhisperSystems.
the class AttachmentDatabase method insertAttachmentForPreUpload.
@NonNull
public DatabaseAttachment insertAttachmentForPreUpload(@NonNull Attachment attachment) throws MmsException {
Map<Attachment, AttachmentId> result = insertAttachmentsForMessage(PREUPLOAD_MESSAGE_ID, Collections.singletonList(attachment), Collections.emptyList());
if (result.values().isEmpty()) {
throw new MmsException("Bad attachment result!");
}
DatabaseAttachment databaseAttachment = getAttachment(result.values().iterator().next());
if (databaseAttachment == null) {
throw new MmsException("Failed to retrieve attachment we just inserted!");
}
return databaseAttachment;
}
use of org.thoughtcrime.securesms.mms.MmsException in project Signal-Android by signalapp.
the class MmsDatabase method getOutgoingMessage.
@Override
public OutgoingMediaMessage getOutgoingMessage(long messageId) throws MmsException, NoSuchMessageException {
AttachmentDatabase attachmentDatabase = SignalDatabase.attachments();
MentionDatabase mentionDatabase = SignalDatabase.mentions();
Cursor cursor = null;
try {
cursor = rawQuery(RAW_ID_WHERE, new String[] { String.valueOf(messageId) });
if (cursor != null && cursor.moveToNext()) {
List<DatabaseAttachment> associatedAttachments = attachmentDatabase.getAttachmentsForMessage(messageId);
List<Mention> mentions = mentionDatabase.getMentionsForMessage(messageId);
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String body = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
boolean viewOnce = cursor.getLong(cursor.getColumnIndexOrThrow(VIEW_ONCE)) == 1;
long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
int distributionType = SignalDatabase.threads().getDistributionType(threadId);
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID));
long quoteAuthor = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR));
String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY));
boolean quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE_MISSING)) == 1;
List<Attachment> quoteAttachments = Stream.of(associatedAttachments).filter(Attachment::isQuote).map(a -> (Attachment) a).toList();
List<Mention> quoteMentions = parseQuoteMentions(context, cursor);
List<Contact> contacts = getSharedContacts(cursor, associatedAttachments);
Set<Attachment> contactAttachments = new HashSet<>(Stream.of(contacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList());
List<LinkPreview> previews = getLinkPreviews(cursor, associatedAttachments);
Set<Attachment> previewAttachments = Stream.of(previews).filter(lp -> lp.getThumbnail().isPresent()).map(lp -> lp.getThumbnail().get()).collect(Collectors.toSet());
List<Attachment> attachments = Stream.of(associatedAttachments).filterNot(Attachment::isQuote).filterNot(contactAttachments::contains).filterNot(previewAttachments::contains).sorted(new DatabaseAttachment.DisplayOrderComparator()).map(a -> (Attachment) a).toList();
Recipient recipient = Recipient.resolved(RecipientId.from(recipientId));
Set<NetworkFailure> networkFailures = new HashSet<>();
Set<IdentityKeyMismatch> mismatches = new HashSet<>();
QuoteModel quote = null;
if (quoteId > 0 && quoteAuthor > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) {
quote = new QuoteModel(quoteId, RecipientId.from(quoteAuthor), quoteText, quoteMissing, quoteAttachments, quoteMentions);
}
if (!TextUtils.isEmpty(mismatchDocument)) {
try {
mismatches = JsonUtils.fromJson(mismatchDocument, IdentityKeyMismatchSet.class).getItems();
} catch (IOException e) {
Log.w(TAG, e);
}
}
if (!TextUtils.isEmpty(networkDocument)) {
try {
networkFailures = JsonUtils.fromJson(networkDocument, NetworkFailureSet.class).getItems();
} catch (IOException e) {
Log.w(TAG, e);
}
}
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
return new OutgoingGroupUpdateMessage(recipient, new MessageGroupContext(body, Types.isGroupV2(outboxType)), attachments, timestamp, 0, false, quote, contacts, previews, mentions);
} else if (Types.isExpirationTimerUpdate(outboxType)) {
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
}
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews, mentions, networkFailures, mismatches);
if (Types.isSecureType(outboxType)) {
return new OutgoingSecureMediaMessage(message);
}
return message;
}
throw new NoSuchMessageException("No record found for id: " + messageId);
} catch (IOException e) {
throw new MmsException(e);
} finally {
if (cursor != null)
cursor.close();
}
}
use of org.thoughtcrime.securesms.mms.MmsException in project Signal-Android by signalapp.
the class MessageContentProcessor method handleSynchronizeSentMessage.
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, @NonNull SentTranscriptMessage message, @NonNull Recipient senderRecipient) throws StorageFailedException, BadGroupIdException, IOException, GroupChangeBusyException {
log(String.valueOf(content.getTimestamp()), "Processing sent transcript for message with ID " + message.getTimestamp());
try {
GroupDatabase groupDatabase = SignalDatabase.groups();
if (message.getMessage().isGroupV2Message()) {
GroupId.V2 groupId = GroupId.v2(message.getMessage().getGroupContext().get().getGroupV2().get().getMasterKey());
if (handleGv2PreProcessing(groupId, content, message.getMessage().getGroupContext().get().getGroupV2().get(), senderRecipient)) {
return;
}
}
long threadId = -1;
if (message.isRecipientUpdate()) {
handleGroupRecipientUpdate(message, content.getTimestamp());
} else if (message.getMessage().isEndSession()) {
threadId = handleSynchronizeSentEndSessionMessage(message, content.getTimestamp());
} else if (message.getMessage().isGroupV1Update()) {
Long gv1ThreadId = GroupV1MessageProcessor.process(context, content, message.getMessage(), true);
threadId = gv1ThreadId == null ? -1 : gv1ThreadId;
} else if (message.getMessage().isGroupV2Update()) {
handleSynchronizeSentGv2Update(content, message);
threadId = SignalDatabase.threads().getOrCreateThreadIdFor(getSyncMessageDestination(message));
} else if (Build.VERSION.SDK_INT > 19 && message.getMessage().getGroupCallUpdate().isPresent()) {
handleGroupCallUpdateMessage(content, message.getMessage(), GroupUtil.idFromGroupContext(message.getMessage().getGroupContext()), senderRecipient);
} else if (message.getMessage().isEmptyGroupV2Message()) {
warn(content.getTimestamp(), "Empty GV2 message! Doing nothing.");
} else if (message.getMessage().isExpirationUpdate()) {
threadId = handleSynchronizeSentExpirationUpdate(message);
} else if (message.getMessage().getReaction().isPresent()) {
handleReaction(content, message.getMessage(), senderRecipient);
threadId = SignalDatabase.threads().getOrCreateThreadIdFor(getSyncMessageDestination(message));
} else if (message.getMessage().getRemoteDelete().isPresent()) {
handleRemoteDelete(content, message.getMessage(), senderRecipient);
} else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent() || message.getMessage().getPreviews().isPresent() || message.getMessage().getSticker().isPresent() || message.getMessage().isViewOnce() || message.getMessage().getMentions().isPresent()) {
threadId = handleSynchronizeSentMediaMessage(message, content.getTimestamp());
} else {
threadId = handleSynchronizeSentTextMessage(message, content.getTimestamp());
}
if (message.getMessage().getGroupContext().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.idFromGroupContext(message.getMessage().getGroupContext().get()))) {
handleUnknownGroupMessage(content, message.getMessage().getGroupContext().get(), senderRecipient);
}
if (message.getMessage().getProfileKey().isPresent()) {
Recipient recipient = getSyncMessageDestination(message);
if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) {
SignalDatabase.recipients().setProfileSharing(recipient.getId(), true);
}
}
if (threadId != -1) {
SignalDatabase.threads().setRead(threadId, true);
ApplicationDependencies.getMessageNotifier().updateNotification(context);
}
if (SignalStore.rateLimit().needsRecaptcha()) {
log(content.getTimestamp(), "Got a sent transcript while in reCAPTCHA mode. Assuming we're good to message again.");
RateLimitUtil.retryAllRateLimitedMessages(context);
}
ApplicationDependencies.getMessageNotifier().setLastDesktopActivityTimestamp(message.getTimestamp());
} catch (MmsException e) {
throw new StorageFailedException(e, content.getSender().getIdentifier(), content.getSenderDevice());
}
}
Aggregations