Merge branch 'dev' of https://github.com/loki-project/session-android into data-extraction

This commit is contained in:
Brice-W 2021-03-23 10:42:30 +11:00
commit 8c7f23a4e3
152 changed files with 1286 additions and 5495 deletions

View File

@ -157,8 +157,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.2' testImplementation 'org.robolectric:shadows-multidex:4.2'
} }
def canonicalVersionCode = 143 def canonicalVersionCode = 147
def canonicalVersionName = "1.8.0" def canonicalVersionName = "1.9.0"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,
@ -235,7 +235,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard/proguard-dagger.pro', 'proguard/proguard-dagger.pro',

View File

@ -2,6 +2,7 @@
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; } -keep class org.whispersystems.** { *; }
-keep class org.thoughtcrime.securesms.** { *; } -keep class org.thoughtcrime.securesms.** { *; }
-keep class org.session.** { *; }
-keepclassmembers class ** { -keepclassmembers class ** {
public void onEvent*(**); public void onEvent*(**);
} }

View File

@ -32,9 +32,10 @@ import androidx.multidex.MultiDexApplication;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.MessagingConfiguration;
import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.snode.SnodeConfiguration;
import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
@ -54,7 +55,7 @@ import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.signal.aesgcmprovider.AesGcmProvider; import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.session.libsession.utilities.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
@ -175,6 +176,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
DatabaseFactory.getStorage(this), DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this), DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this)); new SessionProtocolImpl(this));
SnodeConfiguration.Companion.configure(apiDB, broadcaster);
if (userPublicKey != null) { if (userPublicKey != null) {
SwarmAPI.Companion.configureIfNeeded(apiDB); SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster); SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
@ -441,6 +443,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB); FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
org.session.libsession.messaging.fileserver.FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
return true; return true;
} }

View File

@ -37,7 +37,11 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import org.session.libsession.messaging.messages.visible.LinkPreview;
import org.session.libsession.messaging.messages.visible.Quote;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus; import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus;
import org.session.libsession.utilities.color.MaterialColor; import org.session.libsession.utilities.color.MaterialColor;
import org.thoughtcrime.securesms.conversation.ConversationItem; import org.thoughtcrime.securesms.conversation.ConversationItem;
@ -50,12 +54,12 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader; import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
@ -441,7 +445,28 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
private void onResendClicked(View v) { private void onResendClicked(View v) {
MessageSender.resend(MessageDetailsActivity.this, messageRecord); Recipient recipient = messageRecord.getRecipient();
VisibleMessage message = new VisibleMessage();
message.setId(messageRecord.getId());
message.setText(messageRecord.getBody());
message.setSentTimestamp(messageRecord.getTimestamp());
if (recipient.isGroupRecipient()) {
message.setGroupPublicKey(recipient.getAddress().toGroupString());
} else {
message.setRecipient(messageRecord.getRecipient().getAddress().serialize());
}
message.setThreadID(messageRecord.getThreadId());
if (messageRecord.isMms()) {
MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord;
if (!mmsMessageRecord.getLinkPreviews().isEmpty()) {
message.setLinkPreview(LinkPreview.Companion.from(mmsMessageRecord.getLinkPreviews().get(0)));
}
if (mmsMessageRecord.getQuote() != null) {
message.setQuote(Quote.Companion.from(mmsMessageRecord.getQuote().getQuoteModel()));
}
message.addSignalAttachments(mmsMessageRecord.getSlideDeck().asAttachments());
}
MessageSender.send(message, recipient.getAddress());
resendButton.setVisibility(View.GONE); resendButton.setVisibility(View.GONE);
} }
} }

View File

@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import org.session.libsession.messaging.threads.DistributionTypes;
import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
@ -249,7 +250,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
public void onContactSelected(String number) { public void onContactSelected(String number) {
Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true); Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true);
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient); long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
createConversation(existingThread, recipient.getAddress(), ThreadDatabase.DistributionTypes.DEFAULT); createConversation(existingThread, recipient.getAddress(), DistributionTypes.DEFAULT);
} }
@Override @Override

View File

@ -1,22 +1,29 @@
package org.thoughtcrime.securesms.attachments package org.thoughtcrime.securesms.attachments
import android.content.Context import android.content.Context
import android.text.TextUtils
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsession.utilities.Util
import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceAttachment import org.session.libsignal.service.api.messages.SignalServiceAttachment
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.events.PartProgressEvent import org.thoughtcrime.securesms.events.PartProgressEvent
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MediaUtil
import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -40,6 +47,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return databaseAttachment.toSignalAttachmentStream(context) return databaseAttachment.toSignalAttachmentStream(context)
} }
override fun getScaledSignalAttachmentStream(attachmentId: Long): SignalServiceAttachmentStream? {
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = database.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
val mediaConstraints = MediaConstraints.getPushMediaConstraints()
val scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment) ?: return null
return getAttachmentFor(scaledAttachment)
}
override fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer? { override fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
@ -51,12 +66,6 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value) attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value)
} }
@Throws(Exception::class)
override fun uploadAttachment(attachmentId: Long) {
val attachmentUploadJob = AttachmentUploadJob(AttachmentId(attachmentId, 0), null)
attachmentUploadJob.onRun()
}
override fun getMessageForQuote(timestamp: Long, author: Address): Long? { override fun getMessageForQuote(timestamp: Long, author: Address): Long? {
val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context) val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context)
return messagingDatabase.getMessageFor(timestamp, author)?.id return messagingDatabase.getMessageFor(timestamp, author)?.id
@ -72,6 +81,18 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return messagingDatabase.getMessage(messageID).body return messagingDatabase.getMessage(messageID).body
} }
override fun getAttachmentIDsFor(messageID: Long): List<Long> {
return DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageID).mapNotNull {
if (it.isQuote) return@mapNotNull null
it.attachmentId.rowId
}
}
override fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? {
val message = DatabaseFactory.getMmsDatabase(context).getOutgoingMessage(messageID)
return message.linkPreviews.firstOrNull()?.attachmentId?.rowId
}
override fun insertAttachment(messageId: Long, attachmentId: Long, stream: InputStream) { override fun insertAttachment(messageId: Long, attachmentId: Long, stream: InputStream) {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId, 0), stream) attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId, 0), stream)
@ -79,7 +100,32 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
override fun isOutgoingMessage(timestamp: Long): Boolean { override fun isOutgoingMessage(timestamp: Long): Boolean {
val smsDatabase = DatabaseFactory.getSmsDatabase(context) val smsDatabase = DatabaseFactory.getSmsDatabase(context)
return smsDatabase.isOutgoingMessage(timestamp) val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
}
override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
val attachmentPointer = SignalServiceAttachmentPointer(uploadResult.id,
attachmentStream.contentType,
attachmentKey,
Optional.of(Util.toIntExact(attachmentStream.length)),
attachmentStream.preview,
attachmentStream.width, attachmentStream.height,
Optional.fromNullable(uploadResult.digest),
attachmentStream.fileName,
attachmentStream.voiceNote,
attachmentStream.caption,
uploadResult.url);
val attachment = PointerAttachment.forPointer(Optional.of(attachmentPointer), databaseAttachment.fastPreflightId).get()
database.updateAttachmentAfterUploadSucceeded(databaseAttachment.attachmentId, attachment)
}
override fun updateAttachmentAfterUploadFailed(attachmentId: Long) {
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
database.updateAttachmentAfterUploadFailed(databaseAttachment.attachmentId)
} }
override fun getMessageID(serverID: Long): Long? { override fun getMessageID(serverID: Long): Long? {
@ -92,6 +138,52 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
messagingDatabase.deleteMessage(messageID) messagingDatabase.deleteMessage(messageID)
} }
override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
return attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0))
}
private fun scaleAndStripExif(attachmentDatabase: AttachmentDatabase, constraints: MediaConstraints, attachment: Attachment): Attachment? {
return try {
if (constraints.isSatisfied(context, attachment)) {
if (MediaUtil.isJpeg(attachment)) {
val stripped = constraints.getResizedMedia(context, attachment)
attachmentDatabase.updateAttachmentData(attachment, stripped)
} else {
attachment
}
} else if (constraints.canResize(attachment)) {
val resized = constraints.getResizedMedia(context, attachment)
attachmentDatabase.updateAttachmentData(attachment, resized)
} else {
throw Exception("Size constraints could not be met!")
}
} catch (e: Exception) {
return null
}
}
private fun getAttachmentFor(attachment: Attachment): SignalServiceAttachmentStream? {
try {
if (attachment.dataUri == null || attachment.size == 0L) throw IOException("Assertion failed, outgoing attachment has no data!")
val `is` = PartAuthority.getAttachmentStream(context, attachment.dataUri!!)
return SignalServiceAttachment.newStreamBuilder()
.withStream(`is`)
.withContentType(attachment.contentType)
.withLength(attachment.size)
.withFileName(attachment.fileName)
.withVoiceNote(attachment.isVoiceNote)
.withWidth(attachment.width)
.withHeight(attachment.height)
.withCaption(attachment.caption)
.withListener { total: Long, progress: Long -> EventBus.getDefault().postSticky(PartProgressEvent(attachment, total, progress)) }
.build()
} catch (ioe: IOException) {
Log.w("Loki", "Couldn't open attachment", ioe)
}
return null
}
} }
fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer { fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer {
@ -117,8 +209,28 @@ fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttac
return attachmentStream return attachmentStream
} }
fun DatabaseAttachment.toSignalAttachmentPointer(): SignalServiceAttachmentPointer { fun DatabaseAttachment.toSignalAttachmentPointer(): SignalServiceAttachmentPointer? {
return SignalServiceAttachmentPointer(attachmentId.rowId, contentType, key?.toByteArray(), Optional.fromNullable(size.toInt()), Optional.absent(), width, height, Optional.fromNullable(digest), Optional.fromNullable(fileName), isVoiceNote, Optional.fromNullable(caption), url) if (TextUtils.isEmpty(location)) { return null }
if (TextUtils.isEmpty(key)) { return null }
return try {
val id: Long = location!!.toLong()
val key: ByteArray = Base64.decode(key!!)
SignalServiceAttachmentPointer(id,
contentType,
key,
Optional.of(Util.toIntExact(size)),
Optional.absent(),
width,
height,
Optional.fromNullable(digest),
Optional.fromNullable(fileName),
isVoiceNote,
Optional.fromNullable(caption),
url)
} catch (e: Exception) {
null
}
} }
fun DatabaseAttachment.toSignalAttachmentStream(context: Context): SignalServiceAttachmentStream { fun DatabaseAttachment.toSignalAttachmentStream(context: Context): SignalServiceAttachmentStream {

View File

@ -4,6 +4,7 @@ package org.thoughtcrime.securesms.attachments;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
@ -30,11 +31,11 @@ public class MmsNotificationAttachment extends Attachment {
if (status == MmsDatabase.Status.DOWNLOAD_INITIALIZED || if (status == MmsDatabase.Status.DOWNLOAD_INITIALIZED ||
status == MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY) status == MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY)
{ {
return AttachmentDatabase.TRANSFER_PROGRESS_PENDING; return AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING;
} else if (status == MmsDatabase.Status.DOWNLOAD_CONNECTING) { } else if (status == MmsDatabase.Status.DOWNLOAD_CONNECTING) {
return AttachmentDatabase.TRANSFER_PROGRESS_STARTED; return AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED;
} else { } else {
return AttachmentDatabase.TRANSFER_PROGRESS_FAILED; return AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED;
} }
} }
} }

View File

@ -18,7 +18,6 @@ import org.session.libsession.utilities.Conversions
import org.thoughtcrime.securesms.backup.BackupProtos.* import org.thoughtcrime.securesms.backup.BackupProtos.*
import org.thoughtcrime.securesms.crypto.AttachmentSecret import org.thoughtcrime.securesms.crypto.AttachmentSecret
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
import org.thoughtcrime.securesms.database.* import org.thoughtcrime.securesms.database.*
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
@ -91,7 +90,7 @@ object FullBackupExporter {
} }
} }
} }
for (preference in IdentityKeyUtil.getBackupRecords(context)) { for (preference in BackupUtil.getBackupRecords(context)) {
EventBus.getDefault().post(BackupEvent.createProgress(++count)) EventBus.getDefault().post(BackupEvent.createProgress(++count))
outputStream.writePreferenceEntry(preference) outputStream.writePreferenceEntry(preference)
} }

View File

@ -20,6 +20,8 @@ import com.pnikosis.materialishprogress.ProgressWheel;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.events.PartProgressEvent; import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.mms.DocumentSlide; import org.thoughtcrime.securesms.mms.DocumentSlide;
@ -94,7 +96,7 @@ public class DocumentView extends FrameLayout {
controlToggle.displayQuick(downloadButton); controlToggle.displayQuick(downloadButton);
downloadButton.setOnClickListener(new DownloadClickedListener(documentSlide)); downloadButton.setOnClickListener(new DownloadClickedListener(documentSlide));
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning(); if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
} else if (showControls && documentSlide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) { } else if (showControls && documentSlide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) {
controlToggle.displayQuick(downloadProgress); controlToggle.displayQuick(downloadProgress);
downloadProgress.spin(); downloadProgress.spin();
} else { } else {

View File

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import android.util.AttributeSet; import android.util.AttributeSet;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -248,7 +249,7 @@ public class ThumbnailView extends FrameLayout {
} }
if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() && if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() &&
(slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE || isPreview)) (slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview))
{ {
this.playOverlay.setVisibility(View.VISIBLE); this.playOverlay.setVisibility(View.VISIBLE);
} else { } else {
@ -402,7 +403,7 @@ public class ThumbnailView extends FrameLayout {
if (thumbnailClickListener != null && if (thumbnailClickListener != null &&
slide != null && slide != null &&
slide.asAttachment().getDataUri() != null && slide.asAttachment().getDataUri() != null &&
slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE)
{ {
thumbnailClickListener.onClick(view, slide); thumbnailClickListener.onClick(view, slide);
} else if (parentClickListener != null) { } else if (parentClickListener != null) {

View File

@ -17,6 +17,7 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.events.PartProgressEvent; import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
@ -105,17 +106,17 @@ public class TransferControlView extends FrameLayout {
} }
for (Slide slide : slides) { for (Slide slide : slides) {
if (slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { if (slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) {
downloadProgress.put(slide.asAttachment(), 1f); downloadProgress.put(slide.asAttachment(), 1f);
} }
} }
switch (getTransferState(slides)) { switch (getTransferState(slides)) {
case AttachmentDatabase.TRANSFER_PROGRESS_STARTED: case AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED:
showProgressSpinner(calculateProgress(downloadProgress)); showProgressSpinner(calculateProgress(downloadProgress));
break; break;
case AttachmentDatabase.TRANSFER_PROGRESS_PENDING: case AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING:
case AttachmentDatabase.TRANSFER_PROGRESS_FAILED: case AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED:
downloadDetailsText.setText(getDownloadText(this.slides)); downloadDetailsText.setText(getDownloadText(this.slides));
display(downloadDetails); display(downloadDetails);
break; break;
@ -174,9 +175,9 @@ public class TransferControlView extends FrameLayout {
} }
private int getTransferState(@NonNull List<Slide> slides) { private int getTransferState(@NonNull List<Slide> slides) {
int transferState = AttachmentDatabase.TRANSFER_PROGRESS_DONE; int transferState = AttachmentTransferProgress.TRANSFER_PROGRESS_DONE;
for (Slide slide : slides) { for (Slide slide : slides) {
if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING && transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { if (slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING && transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) {
transferState = slide.getTransferState(); transferState = slide.getTransferState();
} else { } else {
transferState = Math.max(transferState, slide.getTransferState()); transferState = Math.max(transferState, slide.getTransferState());
@ -189,7 +190,7 @@ public class TransferControlView extends FrameLayout {
if (slides.size() == 1) { if (slides.size() == 1) {
return slides.get(0).getContentDescription(); return slides.get(0).getContentDescription();
} else { } else {
int downloadCount = Stream.of(slides).reduce(0, (count, slide) -> slide.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE ? count + 1 : count); int downloadCount = Stream.of(slides).reduce(0, (count, slide) -> slide.getTransferState() != AttachmentTransferProgress.TRANSFER_PROGRESS_DONE ? count + 1 : count);
return getContext().getResources().getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount); return getContext().getResources().getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount);
} }
} }

View File

@ -4,25 +4,21 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.ApplicationContext; import org.session.libsession.messaging.messages.control.TypingIndicator;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@SuppressLint("UseSparseArrays") @SuppressLint("UseSparseArrays")
public class TypingStatusSender { public class TypingStatusSender {
private static final String TAG = TypingStatusSender.class.getSimpleName();
private static final long REFRESH_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(10); private static final long REFRESH_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
private static final long PAUSE_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(3); private static final long PAUSE_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(3);
@ -82,12 +78,16 @@ public class TypingStatusSender {
private void sendTyping(long threadId, boolean typingStarted) { private void sendTyping(long threadId, boolean typingStarted) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId); Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
if (recipient == null) { return; }
// Loki - Check whether we want to send a typing indicator to this user // Loki - Check whether we want to send a typing indicator to this user
if (recipient != null && !SessionMetaProtocol.shouldSendTypingIndicator(recipient.getAddress())) { return; } if (recipient != null && !SessionMetaProtocol.shouldSendTypingIndicator(recipient.getAddress())) { return; }
// Loki - Take into account multi device TypingIndicator typingIndicator;
if (recipient == null) { return; } if (typingStarted) {
long threadID = threadDatabase.getOrCreateThreadIdFor(recipient); typingIndicator = new TypingIndicator(TypingIndicator.Kind.STARTED);
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadID, typingStarted)); } else {
typingIndicator = new TypingIndicator(TypingIndicator.Kind.STOPPED);
}
MessageSender.send(typingIndicator, recipient.getAddress());
} }
private class StartRunnable implements Runnable { private class StartRunnable implements Runnable {

View File

@ -85,6 +85,10 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.libsignal.InvalidMessageException; import org.session.libsignal.libsignal.InvalidMessageException;
@ -137,7 +141,7 @@ import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker; import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities; import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
@ -153,9 +157,10 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.QuoteId; import org.thoughtcrime.securesms.mms.QuoteId;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
@ -168,9 +173,8 @@ import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientFormattingException; import org.session.libsession.messaging.threads.recipients.RecipientFormattingException;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.search.model.MessageResult; import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.sms.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
@ -197,7 +201,6 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -571,16 +574,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
final Context context = ConversationActivity.this.getApplicationContext(); final Context context = ConversationActivity.this.getApplicationContext();
sendMediaMessage(false, sendMediaMessage(message,
message,
slideDeck, slideDeck,
inputPanel.getQuote().orNull(), inputPanel.getQuote().orNull(),
Collections.emptyList(), Optional.absent(),
Collections.emptyList(), initiating).addListener(new AssertedSuccessListener<Void>() {
expiresIn,
subscriptionId,
initiating,
true).addListener(new AssertedSuccessListener<Void>() {
@Override @Override
public void onSuccess(Void result) { public void onSuccess(Void result) {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
@ -648,7 +646,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (!isPushGroupConversation()) { if (!isPushGroupConversation()) {
inflater.inflate(R.menu.conversation_mms_group_options, menu); inflater.inflate(R.menu.conversation_mms_group_options, menu);
if (distributionType == ThreadDatabase.DistributionTypes.BROADCAST) { if (distributionType == DistributionTypes.BROADCAST) {
menu.findItem(R.id.menu_distribution_broadcast).setChecked(true); menu.findItem(R.id.menu_distribution_broadcast).setChecked(true);
} else { } else {
menu.findItem(R.id.menu_distribution_conversation).setChecked(true); menu.findItem(R.id.menu_distribution_conversation).setChecked(true);
@ -808,9 +806,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime);
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipient(), System.currentTimeMillis(), expirationTime * 1000L); ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime);
MessageSender.send(ConversationActivity.this, outgoingMessage, threadId, false, null); message.setSentTimestamp(System.currentTimeMillis());
OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient);
try {
message.setId(DatabaseFactory.getMmsDatabase(ConversationActivity.this).insertMessageOutbox(outgoingMessage, getAllocatedThreadId(ConversationActivity.this), false, null));
MessageSender.send(message, recipient.getAddress());
} catch (MmsException e) {
Log.w(TAG, e);
}
return null; return null;
} }
@ -839,17 +843,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}); });
} }
private void handleConversationSettings() {
/*
Intent intent = new Intent(ConversationActivity.this, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(RecipientPreferenceActivity.CAN_HAVE_SAFETY_NUMBER_EXTRA,
isSecureText && !isSelfConversation());
startActivitySceneTransition(intent, titleView.findViewById(R.id.contact_photo_image), "avatar");
*/
}
private void handleUnmuteNotifications() { private void handleUnmuteNotifications() {
recipient.setMuted(0); recipient.setMuted(0);
@ -1018,7 +1011,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
try { try {
if (isClosedGroup) { if (isClosedGroup) {
ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey); MessageSender.explicitLeave(groupPublicKey);
initializeEnabledCheck(); initializeEnabledCheck();
} else { } else {
Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
@ -1040,7 +1033,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void handleDistributionBroadcastEnabled(MenuItem item) { private void handleDistributionBroadcastEnabled(MenuItem item) {
distributionType = ThreadDatabase.DistributionTypes.BROADCAST; distributionType = DistributionTypes.BROADCAST;
item.setChecked(true); item.setChecked(true);
if (threadId != -1) { if (threadId != -1) {
@ -1048,7 +1041,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getThreadDatabase(ConversationActivity.this) DatabaseFactory.getThreadDatabase(ConversationActivity.this)
.setDistributionType(threadId, ThreadDatabase.DistributionTypes.BROADCAST); .setDistributionType(threadId, DistributionTypes.BROADCAST);
return null; return null;
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -1056,7 +1049,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void handleDistributionConversationEnabled(MenuItem item) { private void handleDistributionConversationEnabled(MenuItem item) {
distributionType = ThreadDatabase.DistributionTypes.CONVERSATION; distributionType = DistributionTypes.CONVERSATION;
item.setChecked(true); item.setChecked(true);
if (threadId != -1) { if (threadId != -1) {
@ -1064,7 +1057,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getThreadDatabase(ConversationActivity.this) DatabaseFactory.getThreadDatabase(ConversationActivity.this)
.setDistributionType(threadId, ThreadDatabase.DistributionTypes.CONVERSATION); .setDistributionType(threadId, DistributionTypes.CONVERSATION);
return null; return null;
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -1335,7 +1328,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (address == null) { finish(); return; } if (address == null) { finish(); return; }
recipient = Recipient.from(this, address, true); recipient = Recipient.from(this, address, true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT);
glideRequests = GlideApp.with(this); glideRequests = GlideApp.with(this);
recipient.addListener(this); recipient.addListener(this);
@ -1483,14 +1476,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
else if (contactData.numbers.size() > 1) selectContactInfo(contactData); else if (contactData.numbers.size() > 1) selectContactInfo(contactData);
} }
private void sendSharedContact(List<Contact> contacts) {
int subscriptionId = -1;
long expiresIn = recipient.getExpireMessages() * 1000L;
boolean initiating = threadId == -1;
sendMediaMessage(false, "", attachmentManager.buildSlideDeck(), null, contacts, Collections.emptyList(), expiresIn, subscriptionId, initiating, false);
}
private void selectContactInfo(ContactData contactData) { private void selectContactInfo(ContactData contactData) {
final CharSequence[] numbers = new CharSequence[contactData.numbers.size()]; final CharSequence[] numbers = new CharSequence[contactData.numbers.size()];
final CharSequence[] numberItems = new CharSequence[contactData.numbers.size()]; final CharSequence[] numberItems = new CharSequence[contactData.numbers.size()];
@ -1677,7 +1662,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Context context = ConversationActivity.this; Context context = ConversationActivity.this;
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false); List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false);
if (!org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.shouldSendReadReceipt(recipient.getAddress())) { if (!SessionMetaProtocol.shouldSendReadReceipt(recipient.getAddress())) {
for (MarkedMessageInfo messageInfo : messageIds) { for (MarkedMessageInfo messageInfo : messageIds) {
MarkReadReceiver.scheduleDeletion(context, messageInfo.getExpirationInfo()); MarkReadReceiver.scheduleDeletion(context, messageInfo.getExpirationInfo());
} }
@ -1735,8 +1720,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
String message = getMessage(); String message = getMessage();
int subscriptionId = -1;
long expiresIn = recipient.getExpireMessages() * 1000L;
boolean initiating = threadId == -1; boolean initiating = threadId == -1;
boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize; boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize;
boolean isMediaMessage = attachmentManager.isAttachmentPresent() || boolean isMediaMessage = attachmentManager.isAttachmentPresent() ||
@ -1747,9 +1730,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
needsSplit; needsSplit;
if (isMediaMessage) { if (isMediaMessage) {
sendMediaMessage(expiresIn, subscriptionId, initiating); sendMediaMessage(initiating);
} else { } else {
sendTextMessage(expiresIn, subscriptionId, initiating); sendTextMessage(initiating);
} }
} catch (RecipientFormattingException ex) { } catch (RecipientFormattingException ex) {
Log.w(TAG, ex); Log.w(TAG, ex);
@ -1764,23 +1747,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
} }
private void sendMediaMessage(final long expiresIn, final int subscriptionId, boolean initiating) private void sendMediaMessage(boolean initiating)
throws InvalidMessageException throws InvalidMessageException
{ {
Log.i(TAG, "Sending media message..."); Log.i(TAG, "Sending media message...");
sendMediaMessage(false, getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), Collections.emptyList(), linkPreviewViewModel.getActiveLinkPreviews(), expiresIn, subscriptionId, initiating, true); sendMediaMessage(getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), linkPreviewViewModel.getActiveLinkPreview(), initiating);
} }
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, private ListenableFuture<Void> sendMediaMessage(String body,
String body,
SlideDeck slideDeck, SlideDeck slideDeck,
QuoteModel quote, QuoteModel quote,
List<Contact> contacts, Optional<LinkPreview> linkPreview,
List<LinkPreview> previews, final boolean initiating)
final long expiresIn,
final int subscriptionId,
final boolean initiating,
final boolean clearComposeBox)
{ {
Pair<String, Optional<Slide>> splitMessage = getSplitMessage(body, characterCalculator.calculateCharacters(body).maxPrimaryMessageSize); Pair<String, Optional<Slide>> splitMessage = getSplitMessage(body, characterCalculator.calculateCharacters(body).maxPrimaryMessageSize);
@ -1790,7 +1768,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
slideDeck.addSlide(splitMessage.second.get()); slideDeck.addSlide(splitMessage.second.get());
} }
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType, quote, contacts, previews); List<Attachment> attachments = slideDeck.asAttachments();
VisibleMessage message = new VisibleMessage();
message.setSentTimestamp(System.currentTimeMillis());
message.setText(body);
OutgoingMediaMessage outgoingMessageCandidate = OutgoingMediaMessage.from(message, recipient, attachments, quote, linkPreview.orNull());
final SettableFuture<Void> future = new SettableFuture<>(); final SettableFuture<Void> future = new SettableFuture<>();
final Context context = getApplicationContext(); final Context context = getApplicationContext();
@ -1800,11 +1783,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate); outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate);
ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId);
if (clearComposeBox) {
inputPanel.clearQuote(); inputPanel.clearQuote();
attachmentManager.clear(glideRequests, false); attachmentManager.clear(glideRequests, false);
silentlySetComposeText(""); silentlySetComposeText("");
}
final long id = fragment.stageOutgoingMessage(outgoingMessage); final long id = fragment.stageOutgoingMessage(outgoingMessage);
@ -1812,43 +1793,44 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
} }
long result = MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); try {
long allocatedThreadId = getAllocatedThreadId(context);
sendComplete(result); message.setId(DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMessage, allocatedThreadId, false, ()->fragment.releaseOutgoingMessage(id)));
MessageSender.send(message, recipient.getAddress(), attachments, quote, linkPreview.orNull());
sendComplete(allocatedThreadId);
} catch (MmsException e) {
Log.w(TAG, e);
sendComplete(threadId);
}
future.set(null); future.set(null);
return future; return future;
} }
private void sendTextMessage(final long expiresIn, final int subscriptionId, final boolean initiatingConversation) private void sendTextMessage(final boolean initiating)
throws InvalidMessageException throws InvalidMessageException
{ {
final Context context = getApplicationContext(); final Context context = getApplicationContext();
final String messageBody = getMessage(); final String messageBody = getMessage();
OutgoingTextMessage message; VisibleMessage message = new VisibleMessage();
message.setSentTimestamp(System.currentTimeMillis());
message = new OutgoingEncryptedMessage(recipient, messageBody, expiresIn); message.setText(messageBody);
OutgoingTextMessage outgoingTextMessage = OutgoingTextMessage.from(message, recipient);
ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId);
silentlySetComposeText(""); silentlySetComposeText("");
final long id = fragment.stageOutgoingMessage(message); final long id = fragment.stageOutgoingMessage(outgoingTextMessage);
if (initiatingConversation) { if (initiating) {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
} }
long result = MessageSender.send(context, message, threadId, false, () -> fragment.releaseOutgoingMessage(id)); long allocatedThreadId = getAllocatedThreadId(context);
message.setId(DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(allocatedThreadId, outgoingTextMessage, false, message.getSentTimestamp(), ()->fragment.releaseOutgoingMessage(id)));
MessageSender.send(message, recipient.getAddress());
sendComplete(result); sendComplete(allocatedThreadId);
}
private void showDefaultSmsPrompt() {
new AlertDialog.Builder(this)
.setMessage(R.string.ConversationActivity_signal_cannot_sent_sms_mms_messages_because_it_is_not_your_default_sms_app)
.setNegativeButton(R.string.ConversationActivity_no, (dialog, which) -> dialog.dismiss())
.setPositiveButton(R.string.ConversationActivity_yes, (dialog, which) -> handleMakeDefaultSms())
.show();
} }
private void updateToggleButtonState() { private void updateToggleButtonState() {
@ -1934,7 +1916,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
SlideDeck slideDeck = new SlideDeck(); SlideDeck slideDeck = new SlideDeck();
slideDeck.addSlide(audioSlide); slideDeck.addSlide(audioSlide);
sendMediaMessage(false, "", slideDeck, inputPanel.getQuote().orNull(), Collections.emptyList(), Collections.emptyList(), expiresIn, subscriptionId, initiating, true).addListener(new AssertedSuccessListener<Void>() { sendMediaMessage("", slideDeck, inputPanel.getQuote().orNull(), Optional.absent(), initiating).addListener(new AssertedSuccessListener<Void>() {
@Override @Override
public void onSuccess(Void nothing) { public void onSuccess(Void nothing) {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@ -2319,6 +2301,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
// region Loki // region Loki
private long getAllocatedThreadId(Context context) {
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
} else {
allocatedThreadId = threadId;
}
return allocatedThreadId;
}
private void updateTitleTextView(Recipient recipient) { private void updateTitleTextView(Recipient recipient) {
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (recipient == null) { if (recipient == null) {

View File

@ -57,6 +57,9 @@ import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.session.libsession.messaging.messages.visible.Quote;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.MessageDetailsActivity; import org.thoughtcrime.securesms.MessageDetailsActivity;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
@ -77,22 +80,21 @@ import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.longmessage.LongMessageActivity; import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.session.libsession.utilities.task.ProgressDialogAsyncTask; import org.session.libsession.utilities.task.ProgressDialogAsyncTask;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat; import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.ViewUtil; import org.session.libsession.utilities.ViewUtil;
@ -405,7 +407,7 @@ public class ConversationFragment extends Fragment
menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser); menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser);
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1); menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
boolean userCanModerate = isPublicChat && PublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer()); boolean userCanModerate = isPublicChat && OpenGroupAPI.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate); boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate);
// allow banning if moderating a public chat and only one user's messages are selected // allow banning if moderating a public chat and only one user's messages are selected
boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1; boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1;
@ -521,7 +523,6 @@ public class ConversationFragment extends Fragment
ArrayList<Long> ignoredMessages = new ArrayList<>(); ArrayList<Long> ignoredMessages = new ArrayList<>();
ArrayList<Long> failedMessages = new ArrayList<>(); ArrayList<Long> failedMessages = new ArrayList<>();
boolean isSentByUser = true; boolean isSentByUser = true;
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getPublicChatAPI();
for (MessageRecord messageRecord : messageRecords) { for (MessageRecord messageRecord : messageRecords) {
isSentByUser = isSentByUser && messageRecord.isOutgoing(); isSentByUser = isSentByUser && messageRecord.isOutgoing();
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
@ -531,8 +532,8 @@ public class ConversationFragment extends Fragment
ignoredMessages.add(messageRecord.getId()); ignoredMessages.add(messageRecord.getId());
} }
} }
if (publicChat != null && publicChatAPI != null) { if (publicChat != null) {
publicChatAPI OpenGroupAPI
.deleteMessages(serverIDs, publicChat.getChannel(), publicChat.getServer(), isSentByUser) .deleteMessages(serverIDs, publicChat.getChannel(), publicChat.getServer(), isSentByUser)
.success(l -> { .success(l -> {
for (MessageRecord messageRecord : messageRecords) { for (MessageRecord messageRecord : messageRecords) {
@ -602,9 +603,7 @@ public class ConversationFragment extends Fragment
protected Void doInBackground(String... userPublicKeyParam) { protected Void doInBackground(String... userPublicKeyParam) {
String userPublicKey = userPublicKeyParam[0]; String userPublicKey = userPublicKeyParam[0];
if (publicChat != null) { if (publicChat != null) {
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getPublicChatAPI(); OpenGroupAPI
if (publicChat != null && publicChatAPI != null) {
publicChatAPI
.ban(userPublicKey, publicChat.getServer()) .ban(userPublicKey, publicChat.getServer())
.success(l -> { .success(l -> {
Log.d("Loki", "User banned"); Log.d("Loki", "User banned");
@ -613,7 +612,6 @@ public class ConversationFragment extends Fragment
Log.d("Loki", "Couldn't ban user due to error: " + e.toString() + "."); Log.d("Loki", "Couldn't ban user due to error: " + e.toString() + ".");
return null; return null;
}); });
}
} else { } else {
Log.d("Loki", "Tried to ban user from a non-public chat"); Log.d("Loki", "Tried to ban user from a non-public chat");
} }
@ -696,11 +694,32 @@ public class ConversationFragment extends Fragment
} }
private void handleResendMessage(final MessageRecord message) { private void handleResendMessage(final MessageRecord message) {
final Context context = getActivity().getApplicationContext();
new AsyncTask<MessageRecord, Void, Void>() { new AsyncTask<MessageRecord, Void, Void>() {
@Override @Override
protected Void doInBackground(MessageRecord... messageRecords) { protected Void doInBackground(MessageRecord... messageRecords) {
MessageSender.resend(context, messageRecords[0]); MessageRecord messageRecord = messageRecords[0];
Recipient recipient = messageRecord.getRecipient();
VisibleMessage message = new VisibleMessage();
message.setId(messageRecord.getId());
message.setText(messageRecord.getBody());
message.setSentTimestamp(messageRecord.getTimestamp());
if (recipient.isGroupRecipient()) {
message.setGroupPublicKey(recipient.getAddress().toGroupString());
} else {
message.setRecipient(messageRecord.getRecipient().getAddress().serialize());
}
message.setThreadID(messageRecord.getThreadId());
if (messageRecord.isMms()) {
MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord;
if (!mmsMessageRecord.getLinkPreviews().isEmpty()) {
message.setLinkPreview(org.session.libsession.messaging.messages.visible.LinkPreview.Companion.from(mmsMessageRecord.getLinkPreviews().get(0)));
}
if (mmsMessageRecord.getQuote() != null) {
message.setQuote(Quote.Companion.from(mmsMessageRecord.getQuote().getQuoteModel()));
}
message.addSignalAttachments(mmsMessageRecord.getSlideDeck().asAttachments());
}
MessageSender.send(message, recipient.getAddress());
return null; return null;
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);

View File

@ -53,6 +53,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat; import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
@ -1003,10 +1004,10 @@ public class ConversationItem extends LinearLayout
if (messageRecord.isMms()) { if (messageRecord.isMms()) {
TextSlide slide = ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide(); TextSlide slide = ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide();
if (slide != null && slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { if (slide != null && slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) {
message = getResources().getString(R.string.ConversationItem_read_more); message = getResources().getString(R.string.ConversationItem_read_more);
action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms());
} else if (slide != null && slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) { } else if (slide != null && slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) {
message = getResources().getString(R.string.ConversationItem_pending); message = getResources().getString(R.string.ConversationItem_pending);
action = () -> {}; action = () -> {};
} else if (slide != null) { } else if (slide != null) {

View File

@ -1,88 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsignal.metadata.SignalProtos;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.push.SignalServiceAddress;
public class UnidentifiedAccessUtil {
private static final String TAG = UnidentifiedAccessUtil.class.getSimpleName();
@WorkerThread
public static Optional<UnidentifiedAccess> getAccessFor(@NonNull Context context,
@NonNull Recipient recipient)
{
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null) +
" | Our access key present? " + (ourUnidentifiedAccessKey != null) +
" | Our certificate present? " + (ourUnidentifiedAccessCertificate != null));
if (theirUnidentifiedAccessKey != null &&
ourUnidentifiedAccessKey != null &&
ourUnidentifiedAccessCertificate != null)
{
return Optional.of(new UnidentifiedAccess(theirUnidentifiedAccessKey));
}
return Optional.absent();
}
public static Optional<UnidentifiedAccess> getAccessForSync(@NonNull Context context) {
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
if (ourUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
return Optional.of(new UnidentifiedAccess(ourUnidentifiedAccessKey));
}
return Optional.absent();
}
public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull Context context) {
return UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getProfileKey(context));
}
private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) {
byte[] theirProfileKey = recipient.resolve().getProfileKey();
if (theirProfileKey == null) return Util.getSecretBytes(16);
else return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
}
private static @Nullable byte[] getUnidentifiedAccessCertificate(Context context) {
String ourNumber = TextSecurePreferences.getLocalNumber(context);
if (ourNumber != null) {
SignalProtos.SenderCertificate certificate = SignalProtos.SenderCertificate.newBuilder()
.setSender(ourNumber)
.setSenderDevice(SignalServiceAddress.DEFAULT_DEVICE_ID)
.build();
return certificate.toByteArray();
}
return null;
}
}

View File

@ -1,21 +0,0 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import org.session.libsignal.libsignal.IdentityKeyPair;
import org.session.libsignal.libsignal.state.IdentityKeyStore;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
public class SignalProtocolStoreImpl implements IdentityKeyStore {
private final Context context;
public SignalProtocolStoreImpl(Context context) {
this.context = context;
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return IdentityKeyUtil.getIdentityKeyPair(context);
}
}

View File

@ -38,6 +38,7 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
@ -119,12 +120,8 @@ public class AttachmentDatabase extends Database {
static final String AUDIO_VISUAL_SAMPLES = "audio_visual_samples"; // Small amount of audio byte samples to visualise the content (e.g. draw waveform). static final String AUDIO_VISUAL_SAMPLES = "audio_visual_samples"; // Small amount of audio byte samples to visualise the content (e.g. draw waveform).
static final String AUDIO_DURATION = "audio_duration"; // Duration of the audio track in milliseconds. static final String AUDIO_DURATION = "audio_duration"; // Duration of the audio track in milliseconds.
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
public static final int TRANSFER_PROGRESS_PENDING = 2;
public static final int TRANSFER_PROGRESS_FAILED = 3;
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?"; private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
private static final String ROW_ID_WHERE = ROW_ID + " = ?";
private static final String PART_AUDIO_ONLY_WHERE = CONTENT_TYPE + " LIKE \"audio/%\""; private static final String PART_AUDIO_ONLY_WHERE = CONTENT_TYPE + " LIKE \"audio/%\"";
private static final String[] PROJECTION = new String[] {ROW_ID, private static final String[] PROJECTION = new String[] {ROW_ID,
@ -199,21 +196,12 @@ public class AttachmentDatabase extends Database {
} }
} }
public boolean containsStickerPackId(@NonNull String stickerPackId) {
String selection = STICKER_PACK_ID + " = ?";
String[] args = new String[] { stickerPackId };
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) {
return cursor != null && cursor.moveToFirst();
}
}
public void setTransferProgressFailed(AttachmentId attachmentId, long mmsId) public void setTransferProgressFailed(AttachmentId attachmentId, long mmsId)
throws MmsException throws MmsException
{ {
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_FAILED); values.put(TRANSFER_STATE, AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()); database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId)); notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
@ -225,7 +213,7 @@ public class AttachmentDatabase extends Database {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null); cursor = database.query(TABLE_NAME, PROJECTION, ROW_ID_WHERE, new String[]{String.valueOf(attachmentId.getRowId())}, null, null, null);
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
List<DatabaseAttachment> list = getAttachment(cursor); List<DatabaseAttachment> list = getAttachment(cursor);
@ -268,7 +256,7 @@ public class AttachmentDatabase extends Database {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null); cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED)}, null, null, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
attachments.addAll(getAttachment(cursor)); attachments.addAll(getAttachment(cursor));
} }
@ -372,7 +360,7 @@ public class AttachmentDatabase extends Database {
values.put(DATA_RANDOM, dataInfo.random); values.put(DATA_RANDOM, dataInfo.random);
} }
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); values.put(TRANSFER_STATE, AttachmentTransferProgress.TRANSFER_PROGRESS_DONE);
values.put(CONTENT_LOCATION, (String)null); values.put(CONTENT_LOCATION, (String)null);
values.put(CONTENT_DISPOSITION, (String)null); values.put(CONTENT_DISPOSITION, (String)null);
values.put(DIGEST, (byte[])null); values.put(DIGEST, (byte[])null);
@ -395,7 +383,7 @@ public class AttachmentDatabase extends Database {
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); values.put(TRANSFER_STATE, AttachmentTransferProgress.TRANSFER_PROGRESS_DONE);
values.put(CONTENT_LOCATION, attachment.getLocation()); values.put(CONTENT_LOCATION, attachment.getLocation());
values.put(DIGEST, attachment.getDigest()); values.put(DIGEST, attachment.getDigest());
values.put(CONTENT_DISPOSITION, attachment.getKey()); values.put(CONTENT_DISPOSITION, attachment.getKey());
@ -411,7 +399,7 @@ public class AttachmentDatabase extends Database {
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_FAILED); values.put(TRANSFER_STATE, AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED);
database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings()); database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings());
} }
@ -506,37 +494,17 @@ public class AttachmentDatabase extends Database {
databaseAttachment.getUrl()); databaseAttachment.getUrl());
} }
public void updateAttachmentFileName(@NonNull AttachmentId attachmentId,
@Nullable String fileName)
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(FILE_NAME, ExternalStorageUtil.getCleanFileName(fileName));
database.update(TABLE_NAME, contentValues, PART_ID_WHERE, attachmentId.toStrings());
}
public void markAttachmentUploaded(long messageId, Attachment attachment) { public void markAttachmentUploaded(long messageId, Attachment attachment) {
ContentValues values = new ContentValues(1); ContentValues values = new ContentValues(1);
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); values.put(TRANSFER_STATE, AttachmentTransferProgress.TRANSFER_PROGRESS_DONE);
database.update(TABLE_NAME, values, PART_ID_WHERE, ((DatabaseAttachment)attachment).getAttachmentId().toStrings()); database.update(TABLE_NAME, values, PART_ID_WHERE, ((DatabaseAttachment)attachment).getAttachmentId().toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
((DatabaseAttachment) attachment).setUploaded(true); ((DatabaseAttachment) attachment).setUploaded(true);
} }
public void setTransferState(long messageId, @NonNull Attachment attachment, int transferState) {
if (!(attachment instanceof DatabaseAttachment)) {
throw new AssertionError("Attempt to update attachment that doesn't belong to DB!");
}
setTransferState(messageId, ((DatabaseAttachment) attachment).getAttachmentId(), transferState);
}
public void setTransferState(long messageId, @NonNull AttachmentId attachmentId, int transferState) { public void setTransferState(long messageId, @NonNull AttachmentId attachmentId, int transferState) {
final ContentValues values = new ContentValues(1); final ContentValues values = new ContentValues(1);
final SQLiteDatabase database = databaseHelper.getWritableDatabase(); final SQLiteDatabase database = databaseHelper.getWritableDatabase();
@ -546,14 +514,6 @@ public class AttachmentDatabase extends Database {
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
} }
public boolean hasStickerAttachments() {
String selection = STICKER_PACK_ID + " NOT NULL";
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, null, null, null, null, "1")) {
return cursor != null && cursor.moveToFirst();
}
}
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@VisibleForTesting @VisibleForTesting
protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType, long offset) protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType, long offset)

View File

@ -1,59 +0,0 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import org.session.libsession.utilities.Util;
public class ContentValuesBuilder {
private final ContentValues contentValues;
public ContentValuesBuilder(ContentValues contentValues) {
this.contentValues = contentValues;
}
public void add(String key, String charsetKey, EncodedStringValue value) {
if (value != null) {
contentValues.put(key, Util.toIsoString(value.getTextString()));
contentValues.put(charsetKey, value.getCharacterSet());
}
}
public void add(String contentKey, byte[] value) {
if (value != null) {
contentValues.put(contentKey, Util.toIsoString(value));
}
}
public void add(String contentKey, int b) {
if (b != 0)
contentValues.put(contentKey, b);
}
public void add(String contentKey, long value) {
if (value != -1L)
contentValues.put(contentKey, value);
}
public ContentValues getContentValues() {
return contentValues;
}
}

View File

@ -7,9 +7,9 @@ import android.text.TextUtils;
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.documents.Document; import org.session.libsession.database.documents.Document;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList; import org.session.libsession.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.libsignal.IdentityKey; import org.session.libsignal.libsignal.IdentityKey;
@ -38,27 +38,6 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markAsSent(long messageId, boolean secure); public abstract void markAsSent(long messageId, boolean secure);
public abstract void markUnidentified(long messageId, boolean unidentified); public abstract void markUnidentified(long messageId, boolean unidentified);
public void setMismatchedIdentity(long messageId, final Address address, final IdentityKey identityKey) {
List<IdentityKeyMismatch> items = new ArrayList<IdentityKeyMismatch>() {{
add(new IdentityKeyMismatch(address, identityKey));
}};
IdentityKeyMismatchList document = new IdentityKeyMismatchList(items);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.beginTransaction();
try {
setDocument(database, messageId, MISMATCHED_IDENTITIES, document);
database.setTransactionSuccessful();
} catch (IOException ioe) {
Log.w(TAG, ioe);
} finally {
database.endTransaction();
}
}
public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) { public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) {
try { try {
addToDocument(messageId, MISMATCHED_IDENTITIES, addToDocument(messageId, MISMATCHED_IDENTITIES,

View File

@ -19,9 +19,7 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -38,22 +36,22 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment; import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList; import org.session.libsession.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.NetworkFailureList; import org.session.libsession.database.documents.NetworkFailureList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;

View File

@ -86,15 +86,6 @@ public class MmsSmsDatabase extends Database {
} }
} }
public @Nullable MessageRecord getMessageFor(long messageId) {
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.ID + " = " + messageId, null, null)) {
MmsSmsDatabase.Reader reader = db.readerFor(cursor);
return reader.getNext();
}
}
public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) { public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) {
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);

View File

@ -23,8 +23,6 @@ import android.database.Cursor;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabase;
@ -32,15 +30,15 @@ import net.sqlcipher.database.SQLiteStatement;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList; import org.session.libsession.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
@ -51,7 +49,6 @@ import org.session.libsignal.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;

View File

@ -13,7 +13,6 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.opengroups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachment
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
@ -30,20 +29,22 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.get
import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.loki.utilities.getString
import org.thoughtcrime.securesms.mms.IncomingMediaMessage import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.sms.IncomingGroupMessage import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage
import org.thoughtcrime.securesms.sms.OutgoingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.service.loki.utilities.prettifiedDescription
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
override fun getUserPublicKey(): String? { override fun getUserPublicKey(): String? {
@ -65,8 +66,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
override fun getUserProfileKey(): ByteArray? { override fun getUserProfileKey(): ByteArray? {
val profileKey = TextSecurePreferences.getProfileKey(context) ?: return null return ProfileKeyUtil.getProfileKey(context)
return profileKey.toByteArray()
} }
override fun getUserProfilePictureURL(): String? { override fun getUserProfilePictureURL(): String? {
@ -81,7 +81,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun getOrGenerateRegistrationID(): Int { override fun getOrGenerateRegistrationID(): Int {
var registrationID = TextSecurePreferences.getLocalRegistrationId(context) var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
if (registrationID == null) { if (registrationID == 0) {
registrationID = KeyHelper.generateRegistrationId(false) registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(context, registrationID) TextSecurePreferences.setLocalRegistrationId(context, registrationID)
} }
@ -90,7 +90,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun persistAttachments(messageId: Long, attachments: List<Attachment>): List<Long> { override fun persistAttachments(messageId: Long, attachments: List<Attachment>): List<Long> {
val database = DatabaseFactory.getAttachmentDatabase(context) val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachments = attachments.map { it.toDatabaseAttachment() } val databaseAttachments = attachments.mapNotNull { it.toSignalAttachment() }
return database.insertAttachments(messageId, databaseAttachments) return database.insertAttachments(messageId, databaseAttachments)
} }
@ -125,7 +125,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}.mapNotNull { }.mapNotNull {
PointerAttachment.forPointer(Optional.of(it)).orNull() PointerAttachment.forPointer(Optional.of(it)).orNull()
} }
val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), attachments, quote.orNull(), linkPreviews.orNull()) val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), attachments, quote.orNull(), linkPreviews.orNull().firstOrNull())
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!)
} else { } else {
// It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment
@ -331,9 +331,13 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, serverID) DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, serverID)
} }
override fun markAsSent(messageID: Long) { override fun getQuoteServerID(quoteID: Long, publicKey: String): Long? {
return DatabaseFactory.getLokiMessageDatabase(context).getQuoteServerID(quoteID, publicKey)
}
override fun markAsSent(timestamp: Long, author: String) {
val database = DatabaseFactory.getMmsSmsDatabase(context) val database = DatabaseFactory.getMmsSmsDatabase(context)
val messageRecord = database.getMessageFor(messageID)!! val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) { if (messageRecord.isMms) {
val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
mmsDatabase.markAsSent(messageRecord.getId(), true) mmsDatabase.markAsSent(messageRecord.getId(), true)
@ -343,9 +347,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun markUnidentified(messageID: Long) { override fun markUnidentified(timestamp: Long, author: String) {
val database = DatabaseFactory.getMmsSmsDatabase(context) val database = DatabaseFactory.getMmsSmsDatabase(context)
val messageRecord = database.getMessageFor(messageID)!! val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) { if (messageRecord.isMms) {
val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
mmsDatabase.markUnidentified(messageRecord.getId(), true) mmsDatabase.markUnidentified(messageRecord.getId(), true)
@ -355,15 +359,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun setErrorMessage(messageID: Long, error: Exception) { override fun setErrorMessage(timestamp: Long, author: String, error: Exception) {
val database = DatabaseFactory.getMmsSmsDatabase(context) val database = DatabaseFactory.getMmsSmsDatabase(context)
val messageRecord = database.getMessageFor(messageID) ?: return val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) { if (messageRecord.isMms) {
val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
mmsDatabase.markAsSentFailed(messageID) mmsDatabase.markAsSentFailed(messageRecord.getId())
} else { } else {
val smsDatabase = DatabaseFactory.getSmsDatabase(context) val smsDatabase = DatabaseFactory.getSmsDatabase(context)
smsDatabase.markAsSentFailed(messageID) smsDatabase.markAsSentFailed(messageRecord.getId())
}
if (error.localizedMessage != null) {
DatabaseFactory.getLokiMessageDatabase(context).setErrorMessage(messageRecord.getId(), error.localizedMessage!!)
} else {
DatabaseFactory.getLokiMessageDatabase(context).setErrorMessage(messageRecord.getId(), error.javaClass.simpleName)
} }
} }
@ -388,7 +397,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members) DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members)
} }
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>) { override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder() val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
.setType(type0) .setType(type0)
@ -396,13 +405,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
.addAllMembers(members) .addAllMembers(members)
.addAllAdmins(admins) .addAllAdmins(admins)
val group = SignalServiceGroup(type1, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) val group = SignalServiceGroup(type1, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, System.currentTimeMillis(), "", Optional.of(group), 0, true) val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "") val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "")
val smsDB = DatabaseFactory.getSmsDatabase(context) val smsDB = DatabaseFactory.getSmsDatabase(context)
smsDB.insertMessageInbox(infoMessage) smsDB.insertMessageInbox(infoMessage)
} }
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long) { override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
val userPublicKey = getUserPublicKey()
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder() val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
@ -410,8 +420,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
.setName(name) .setName(name)
.addAllMembers(members) .addAllMembers(members)
.addAllAdmins(admins) .addAllAdmins(admins)
val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, 0, null, listOf(), listOf()) val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTimestamp, 0, null, listOf(), listOf())
val mmsDB = DatabaseFactory.getMmsDatabase(context) val mmsDB = DatabaseFactory.getMmsDatabase(context)
val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context)
if (mmsSmsDB.getMessageFor(sentTimestamp,userPublicKey) != null) return
val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null)
mmsDB.markAsSent(infoMessageID, true) mmsDB.markAsSent(infoMessageID, true)
} }

View File

@ -30,6 +30,7 @@ import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabase;
import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.GroupRecord; import org.session.libsession.messaging.threads.GroupRecord;
@ -598,14 +599,6 @@ public class ThreadDatabase extends Database {
return new Reader(cursor); return new Reader(cursor);
} }
public static class DistributionTypes {
public static final int DEFAULT = 2;
public static final int BROADCAST = 1;
public static final int CONVERSATION = 2;
public static final int ARCHIVE = 3;
public static final int INBOX_ZERO = 4;
}
public class Reader implements Closeable { public class Reader implements Closeable {
private final Cursor cursor; private final Cursor cursor;

View File

@ -1,245 +0,0 @@
package org.thoughtcrime.securesms.database;
import android.text.TextUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class XmlBackup {
private static final String PROTOCOL = "protocol";
private static final String ADDRESS = "address";
private static final String CONTACT_NAME = "contact_name";
private static final String DATE = "date";
private static final String READABLE_DATE = "readable_date";
private static final String TYPE = "type";
private static final String SUBJECT = "subject";
private static final String BODY = "body";
private static final String SERVICE_CENTER = "service_center";
private static final String READ = "read";
private static final String STATUS = "status";
private static final String TOA = "toa";
private static final String SC_TOA = "sc_toa";
private static final String LOCKED = "locked";
private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
private final XmlPullParser parser;
public XmlBackup(String path) throws XmlPullParserException, FileNotFoundException {
this.parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(new FileInputStream(path), null);
}
public XmlBackupItem getNext() throws IOException, XmlPullParserException {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (!name.equalsIgnoreCase("sms")) {
continue;
}
int attributeCount = parser.getAttributeCount();
if (attributeCount <= 0) {
continue;
}
XmlBackupItem item = new XmlBackupItem();
for (int i=0;i<attributeCount;i++) {
String attributeName = parser.getAttributeName(i);
if (attributeName.equals(PROTOCOL )) item.protocol = Integer.parseInt(parser.getAttributeValue(i));
else if (attributeName.equals(ADDRESS )) item.address = parser.getAttributeValue(i);
else if (attributeName.equals(CONTACT_NAME )) item.contactName = parser.getAttributeValue(i);
else if (attributeName.equals(DATE )) item.date = Long.parseLong(parser.getAttributeValue(i));
else if (attributeName.equals(READABLE_DATE )) item.readableDate = parser.getAttributeValue(i);
else if (attributeName.equals(TYPE )) item.type = Integer.parseInt(parser.getAttributeValue(i));
else if (attributeName.equals(SUBJECT )) item.subject = parser.getAttributeValue(i);
else if (attributeName.equals(BODY )) item.body = parser.getAttributeValue(i);
else if (attributeName.equals(SERVICE_CENTER)) item.serviceCenter = parser.getAttributeValue(i);
else if (attributeName.equals(READ )) item.read = Integer.parseInt(parser.getAttributeValue(i));
else if (attributeName.equals(STATUS )) item.status = Integer.parseInt(parser.getAttributeValue(i));
}
return item;
}
return null;
}
public static class XmlBackupItem {
private int protocol;
private String address;
private String contactName;
private long date;
private String readableDate;
private int type;
private String subject;
private String body;
private String serviceCenter;
private int read;
private int status;
public XmlBackupItem() {}
public XmlBackupItem(int protocol, String address, String contactName, long date, int type,
String subject, String body, String serviceCenter, int read, int status)
{
this.protocol = protocol;
this.address = address;
this.contactName = contactName;
this.date = date;
this.readableDate = dateFormatter.format(date);
this.type = type;
this.subject = subject;
this.body = body;
this.serviceCenter = serviceCenter;
this.read = read;
this.status = status;
}
public int getProtocol() {
return protocol;
}
public String getAddress() {
return address;
}
public String getContactName() {
return contactName;
}
public long getDate() {
return date;
}
public String getReadableDate() {
return readableDate;
}
public int getType() {
return type;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
public String getServiceCenter() {
return serviceCenter;
}
public int getRead() {
return read;
}
public int getStatus() {
return status;
}
}
public static class Writer {
private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>";
private static final String CREATED_BY = "<!-- File Created By Signal -->";
private static final String OPEN_TAG_SMSES = "<smses count=\"%d\">";
private static final String CLOSE_TAG_SMSES = "</smses>";
private static final String OPEN_TAG_SMS = " <sms ";
private static final String CLOSE_EMPTYTAG = "/>";
private static final String OPEN_ATTRIBUTE = "=\"";
private static final String CLOSE_ATTRIBUTE = "\" ";
private static final Pattern PATTERN = Pattern.compile("[^\u0020-\uD7FF]");
private final BufferedWriter bufferedWriter;
public Writer(String path, int count) throws IOException {
bufferedWriter = new BufferedWriter(new FileWriter(path, false));
bufferedWriter.write(XML_HEADER);
bufferedWriter.newLine();
bufferedWriter.write(CREATED_BY);
bufferedWriter.newLine();
bufferedWriter.write(String.format(Locale.ROOT, OPEN_TAG_SMSES, count));
}
public void writeItem(XmlBackupItem item) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(OPEN_TAG_SMS);
appendAttribute(stringBuilder, PROTOCOL, item.getProtocol());
appendAttribute(stringBuilder, ADDRESS, escapeXML(item.getAddress()));
appendAttribute(stringBuilder, CONTACT_NAME, escapeXML(item.getContactName()));
appendAttribute(stringBuilder, DATE, item.getDate());
appendAttribute(stringBuilder, READABLE_DATE, item.getReadableDate());
appendAttribute(stringBuilder, TYPE, item.getType());
appendAttribute(stringBuilder, SUBJECT, escapeXML(item.getSubject()));
appendAttribute(stringBuilder, BODY, escapeXML(item.getBody()));
appendAttribute(stringBuilder, TOA, "null");
appendAttribute(stringBuilder, SC_TOA, "null");
appendAttribute(stringBuilder, SERVICE_CENTER, item.getServiceCenter());
appendAttribute(stringBuilder, READ, item.getRead());
appendAttribute(stringBuilder, STATUS, item.getStatus());
appendAttribute(stringBuilder, LOCKED, 0);
stringBuilder.append(CLOSE_EMPTYTAG);
bufferedWriter.newLine();
bufferedWriter.write(stringBuilder.toString());
}
private <T> void appendAttribute(StringBuilder stringBuilder, String name, T value) {
stringBuilder.append(name).append(OPEN_ATTRIBUTE).append(value).append(CLOSE_ATTRIBUTE);
}
public void close() throws IOException {
bufferedWriter.newLine();
bufferedWriter.write(CLOSE_TAG_SMSES);
bufferedWriter.close();
}
private String escapeXML(String s) {
if (TextUtils.isEmpty(s)) return s;
Matcher matcher = PATTERN.matcher( s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;"));
StringBuffer st = new StringBuffer();
while (matcher.find()) {
String escaped="";
for (char ch: matcher.group(0).toCharArray()) {
escaped += ("&#" + ((int) ch) + ";");
}
matcher.appendReplacement(st, escaped);
}
matcher.appendTail(st);
return st.toString();
}
}
}

View File

@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.database.SessionJobDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration;
public class SQLCipherOpenHelper extends SQLiteOpenHelper { public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@ -52,9 +53,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV19 = 40; private static final int lokiV19 = 40;
private static final int lokiV20 = 41; private static final int lokiV20 = 41;
private static final int lokiV21 = 42; private static final int lokiV21 = 42;
private static final int lokiV22 = 43;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV21; private static final int DATABASE_VERSION = lokiV22;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -121,6 +123,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand()); db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand()); db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand());
executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS);
@ -255,6 +258,20 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
"ResetThreadSessionJob"); "ResetThreadSessionJob");
} }
if (oldVersion < lokiV22) {
db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand());
deleteJobRecords(db,
"PushGroupSendJob",
"PushMediaSendJob",
"PushTextSendJob",
"SendReadReceiptJob",
"TypingSendJob",
"AttachmentUploadJob",
"RequestGroupInfoJob",
"ClosedGroupUpdateMessageSendJobV2",
"SendDeliveryReceiptJob");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -45,6 +45,7 @@ public class PagingMediaLoader extends AsyncLoader<Pair<Cursor, Integer>> {
return new Pair<>(cursor, leftIsRecent ? cursor.getPosition() : cursor.getCount() - 1 - cursor.getPosition()); return new Pair<>(cursor, leftIsRecent ? cursor.getPosition() : cursor.getCount() - 1 - cursor.getPosition());
} }
} }
cursor.close();
return null; return null;
} }

View File

@ -26,8 +26,8 @@ import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;

View File

@ -26,8 +26,8 @@ import android.text.style.StyleSpan;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
@ -158,18 +158,10 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isIdentityDefault(type); return SmsDatabase.Types.isIdentityDefault(type);
} }
public boolean isIdentityMismatchFailure() {
return mismatches != null && !mismatches.isEmpty();
}
public boolean isBundleKeyExchange() { public boolean isBundleKeyExchange() {
return SmsDatabase.Types.isBundleKeyExchange(type); return SmsDatabase.Types.isBundleKeyExchange(type);
} }
public boolean isContentBundleKeyExchange() {
return SmsDatabase.Types.isContentBundleKeyExchange(type);
}
public boolean isIdentityUpdate() { public boolean isIdentityUpdate() {
return SmsDatabase.Types.isIdentityUpdate(type); return SmsDatabase.Types.isIdentityUpdate(type);
} }
@ -195,10 +187,6 @@ public abstract class MessageRecord extends DisplayRecord {
return individualRecipient; return individualRecipient;
} }
public int getRecipientDeviceId() {
return recipientDeviceId;
}
public long getType() { public long getType() {
return type; return type;
} }
@ -211,10 +199,6 @@ public abstract class MessageRecord extends DisplayRecord {
return networkFailures; return networkFailures;
} }
public boolean hasNetworkFailures() {
return networkFailures != null && !networkFailures.isEmpty();
}
protected SpannableString emphasisAdded(String sequence) { protected SpannableString emphasisAdded(String sequence) {
SpannableString spannable = new SpannableString(sequence); SpannableString spannable = new SpannableString(sequence);
spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
@ -234,10 +218,6 @@ public abstract class MessageRecord extends DisplayRecord {
return (int)getId(); return (int)getId();
} }
public int getSubscriptionId() {
return subscriptionId;
}
public long getExpiresIn() { public long getExpiresIn() {
return expiresIn; return expiresIn;
} }

View File

@ -8,8 +8,8 @@ import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;

View File

@ -23,8 +23,8 @@ import android.text.SpannableString;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;

View File

@ -4,6 +4,7 @@ package org.thoughtcrime.securesms.database.model;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
@ -42,4 +43,8 @@ public class Quote {
public @NonNull SlideDeck getAttachment() { public @NonNull SlideDeck getAttachment() {
return attachment; return attachment;
} }
public QuoteModel getQuoteModel() {
return new QuoteModel(id, author, text, missing, attachment.asAttachments());
}
} }

View File

@ -24,7 +24,7 @@ import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import java.util.LinkedList; import java.util.LinkedList;

View File

@ -3,42 +3,20 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context; import android.content.Context;
import org.session.libsignal.service.api.SignalServiceMessageReceiver; import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob; import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.session.libsession.utilities.TextSecurePreferences;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@Module(complete = false, injects = {PushGroupSendJob.class, @Module(complete = false, injects = {AttachmentDownloadJob.class,
PushTextSendJob.class,
PushMediaSendJob.class,
AttachmentDownloadJob.class,
RequestGroupInfoJob.class,
AvatarDownloadJob.class, AvatarDownloadJob.class,
RetrieveProfileAvatarJob.class, RetrieveProfileAvatarJob.class,
SendReadReceiptJob.class,
AppProtectionPreferenceFragment.class, AppProtectionPreferenceFragment.class,
SendDeliveryReceiptJob.class,
TypingSendJob.class,
AttachmentUploadJob.class,
PushDecryptJob.class, PushDecryptJob.class,
LinkPreviewRepository.class}) LinkPreviewRepository.class})
@ -46,30 +24,12 @@ public class SignalCommunicationModule {
private final Context context; private final Context context;
private SignalServiceMessageSender messageSender;
private SignalServiceMessageReceiver messageReceiver; private SignalServiceMessageReceiver messageReceiver;
public SignalCommunicationModule(Context context) { public SignalCommunicationModule(Context context) {
this.context = context; this.context = context;
} }
@Provides
public synchronized SignalServiceMessageSender provideSignalMessageSender() {
if (this.messageSender == null) {
this.messageSender = new SignalServiceMessageSender(new SignalProtocolStoreImpl(context),
TextSecurePreferences.getLocalNumber(context),
DatabaseFactory.getLokiAPIDatabase(context),
DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context),
new SessionProtocolImpl(context),
DatabaseFactory.getLokiUserDatabase(context),
DatabaseFactory.getGroupDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster);
}
return this.messageSender;
}
@Provides @Provides
synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() { synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
if (this.messageReceiver == null) { if (this.messageReceiver == null) {

View File

@ -2,36 +2,23 @@ package org.thoughtcrime.securesms.groups;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.protobuf.ByteString;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -72,7 +59,7 @@ public class GroupManager {
groupDatabase.updateProfilePicture(groupId, avatarBytes); groupDatabase.updateProfilePicture(groupId, avatarBytes);
long threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor( long threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(
groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); groupRecipient, DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadID); return new GroupActionResult(groupRecipient, threadID);
} }
@ -95,82 +82,6 @@ public class GroupManager {
return groupDatabase.delete(groupId); return groupDatabase.delete(groupId);
} }
public static GroupActionResult updateGroup(@NonNull Context context,
@NonNull String groupId,
@NonNull Set<Recipient> members,
@Nullable Bitmap avatar,
@Nullable String name,
@NonNull Set<Recipient> admins)
{
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final Set<Address> memberAddresses = getMemberAddresses(members);
final Set<Address> adminAddresses = getMemberAddresses(admins);
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses));
groupDatabase.updateAdmins(groupId, new LinkedList<>(adminAddresses));
groupDatabase.updateTitle(groupId, name);
groupDatabase.updateProfilePicture(groupId, avatarBytes);
if (!GroupUtil.isMmsGroup(groupId)) {
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
} else {
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
return new GroupActionResult(groupRecipient, threadId);
}
}
private static GroupActionResult sendGroupUpdate(@NonNull Context context,
@NonNull String groupId,
@NonNull Set<Address> members,
@Nullable String groupName,
@Nullable byte[] avatar,
@NonNull Set<Address> admins)
{
Attachment avatarAttachment = null;
Address groupAddress = Address.fromSerialized(groupId);
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
List<String> numbers = new LinkedList<>();
for (Address member : members) {
numbers.add(member.serialize());
}
List<String> adminNumbers = new LinkedList<>();
for (Address admin : admins) {
adminNumbers.add(admin.serialize());
}
GroupContext.Builder groupContextBuilder = GroupContext.newBuilder()
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupId)))
.setType(GroupContext.Type.UPDATE)
.addAllMembers(numbers)
.addAllAdmins(adminNumbers);
if (groupName != null) groupContextBuilder.setName(groupName);
GroupContext groupContext = groupContextBuilder.build();
if (avatar != null) {
Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory();
avatarAttachment = new UriAttachment(avatarUri, MediaTypes.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null);
}
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, 0, null, Collections.emptyList(), Collections.emptyList());
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
return new GroupActionResult(groupRecipient, threadId);
}
private static Set<Address> getMemberAddresses(Collection<Recipient> recipients) {
final Set<Address> results = new HashSet<>();
for (Recipient recipient : recipients) {
results.add(recipient.getAddress());
}
return results;
}
public static class GroupActionResult { public static class GroupActionResult {
private Recipient groupRecipient; private Recipient groupRecipient;
private long threadId; private long threadId;

View File

@ -7,6 +7,7 @@ import androidx.annotation.VisibleForTesting;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.session.libsession.messaging.jobs.Data; import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsignal.libsignal.InvalidMessageException; import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream; import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream;
@ -96,11 +97,11 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId); final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final DatabaseAttachment attachment = database.getAttachment(attachmentId); final DatabaseAttachment attachment = database.getAttachment(attachmentId);
final boolean pending = attachment != null && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE; final boolean pending = attachment != null && attachment.getTransferState() != AttachmentTransferProgress.TRANSFER_PROGRESS_DONE;
if (pending && (manual || AttachmentUtil.isAutoDownloadPermitted(context, attachment))) { if (pending && (manual || AttachmentUtil.isAutoDownloadPermitted(context, attachment))) {
Log.i(TAG, "onAdded() Marking attachment progress as 'started'"); Log.i(TAG, "onAdded() Marking attachment progress as 'started'");
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED); database.setTransferState(messageId, attachmentId, AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED);
} }
} }
@ -129,12 +130,12 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
if (!manual && !AttachmentUtil.isAutoDownloadPermitted(context, attachment)) { if (!manual && !AttachmentUtil.isAutoDownloadPermitted(context, attachment)) {
Log.w(TAG, "Attachment can't be auto downloaded..."); Log.w(TAG, "Attachment can't be auto downloaded...");
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_PENDING); database.setTransferState(messageId, attachmentId, AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING);
return; return;
} }
Log.i(TAG, "Downloading push part " + attachmentId); Log.i(TAG, "Downloading push part " + attachmentId);
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED); database.setTransferState(messageId, attachmentId, AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED);
retrieveAttachment(messageId, attachmentId, attachment); retrieveAttachment(messageId, attachmentId, attachment);
} }

View File

@ -1,169 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.greenrobot.eventbus.EventBus;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.loki.api.utilities.HTTP;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class AttachmentUploadJob extends BaseJob implements InjectableType {
public static final String KEY = "AttachmentUploadJob";
private static final String TAG = AttachmentUploadJob.class.getSimpleName();
private static final String KEY_ROW_ID = "row_id";
private static final String KEY_UNIQUE_ID = "unique_id";
private static final String KEY_DESTINATION = "destination";
private AttachmentId attachmentId;
private Address destination;
@Inject SignalServiceMessageSender messageSender;
public AttachmentUploadJob(AttachmentId attachmentId, Address destination) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(10)
.build(),
attachmentId, destination);
}
private AttachmentUploadJob(@NonNull Job.Parameters parameters, @NonNull AttachmentId attachmentId, Address destination) {
super(parameters);
this.attachmentId = attachmentId;
this.destination = destination;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putLong(KEY_ROW_ID, attachmentId.getRowId())
.putLong(KEY_UNIQUE_ID, attachmentId.getUniqueId())
.putString(KEY_DESTINATION, destination.serialize())
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws Exception {
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId);
if (databaseAttachment == null) {
throw new IllegalStateException("Cannot find the specified attachment.");
}
// Only upload attachment if necessary
if (databaseAttachment.getUrl().isEmpty()) {
final Attachment attachment;
try {
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment);
SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment);
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), false, new SignalServiceAddress(destination.serialize()));
attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), databaseAttachment.getFastPreflightId()).get();
} catch (Exception e) {
// On any error make sure we mark the related DB record's transfer state as failed.
database.updateAttachmentAfterUploadFailed(databaseAttachment.getAttachmentId());
throw e;
}
database.updateAttachmentAfterUploadSucceeded(databaseAttachment.getAttachmentId(), attachment);
}
}
@Override
public void onCanceled() { }
@Override
protected boolean onShouldRetry(@NonNull Exception exception) {
return exception instanceof IOException ||
exception instanceof HTTP.HTTPRequestFailedException;
}
private SignalServiceAttachment getAttachmentFor(Attachment attachment) {
try {
if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!");
InputStream is = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
return SignalServiceAttachment.newStreamBuilder()
.withStream(is)
.withContentType(attachment.getContentType())
.withLength(attachment.getSize())
.withFileName(attachment.getFileName())
.withVoiceNote(attachment.isVoiceNote())
.withWidth(attachment.getWidth())
.withHeight(attachment.getHeight())
.withCaption(attachment.getCaption())
.withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)))
.build();
} catch (IOException ioe) {
Log.w(TAG, "Couldn't open attachment", ioe);
}
return null;
}
private Attachment scaleAndStripExif(@NonNull AttachmentDatabase attachmentDatabase,
@NonNull MediaConstraints constraints,
@NonNull Attachment attachment)
throws UndeliverableMessageException
{
try {
if (constraints.isSatisfied(context, attachment)) {
if (MediaUtil.isJpeg(attachment)) {
MediaStream stripped = constraints.getResizedMedia(context, attachment);
return attachmentDatabase.updateAttachmentData(attachment, stripped);
} else {
return attachment;
}
} else if (constraints.canResize(attachment)) {
MediaStream resized = constraints.getResizedMedia(context, attachment);
return attachmentDatabase.updateAttachmentData(attachment, resized);
} else {
throw new UndeliverableMessageException("Size constraints could not be met!");
}
} catch (IOException | MmsException e) {
throw new UndeliverableMessageException(e);
}
}
public static final class Factory implements Job.Factory<AttachmentUploadJob> {
@Override
public @NonNull AttachmentUploadJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), Address.fromSerialized(data.getString(KEY_DESTINATION)));
}
}
}

View File

@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob; import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJobV2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -31,21 +30,12 @@ public final class JobManagerFactories {
public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) { public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) {
HashMap<String, Job.Factory> factoryHashMap = new HashMap<String, Job.Factory>() {{ HashMap<String, Job.Factory> factoryHashMap = new HashMap<String, Job.Factory>() {{
put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory()); put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory());
put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory());
put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory());
put(ClosedGroupUpdateMessageSendJobV2.KEY, new ClosedGroupUpdateMessageSendJobV2.Factory());
put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
put(PushContentReceiveJob.KEY, new PushContentReceiveJob.Factory()); put(PushContentReceiveJob.KEY, new PushContentReceiveJob.Factory());
put(PushDecryptJob.KEY, new PushDecryptJob.Factory()); put(PushDecryptJob.KEY, new PushDecryptJob.Factory());
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application)); put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory());
put(TrimThreadJob.KEY, new TrimThreadJob.Factory()); put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
put(TypingSendJob.KEY, new TypingSendJob.Factory());
put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory()); put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory());
}}; }};

View File

@ -15,6 +15,7 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data; import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsignal.metadata.InvalidMetadataMessageException; import org.session.libsignal.metadata.InvalidMetadataMessageException;
import org.session.libsignal.metadata.ProtocolInvalidMessageException; import org.session.libsignal.metadata.ProtocolInvalidMessageException;
import org.session.libsignal.service.api.crypto.SignalServiceCipher; import org.session.libsignal.service.api.crypto.SignalServiceCipher;
@ -33,8 +34,7 @@ import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.session.libsession.utilities.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
@ -59,15 +59,14 @@ import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol; import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceContent; import org.session.libsignal.service.api.messages.SignalServiceContent;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage; import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview; import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
@ -84,8 +83,6 @@ import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import network.loki.messenger.R; import network.loki.messenger.R;
public class PushDecryptJob extends BaseJob implements InjectableType { public class PushDecryptJob extends BaseJob implements InjectableType {
@ -102,8 +99,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private MessageNotifier messageNotifier; private MessageNotifier messageNotifier;
@Inject SignalServiceMessageSender messageSender;
public PushDecryptJob(Context context) { public PushDecryptJob(Context context) {
this(context, -1); this(context, -1);
} }
@ -232,17 +227,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleTextMessage(content, message, smsMessageId, Optional.absent()); handleTextMessage(content, message, smsMessageId, Optional.absent());
} }
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
handleUnknownGroupMessage(content, message.getGroupInfo().get());
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
SessionMetaProtocol.handleProfileKeyUpdate(context, content); SessionMetaProtocol.handleProfileKeyUpdate(context, content);
} }
if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.fromSerialized(content.getSender()))) {
handleNeedsDeliveryReceipt(content, message);
}
} else if (content.getReceiptMessage().isPresent()) { } else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get(); SignalServiceReceiptMessage message = content.getReceiptMessage().get();
@ -268,16 +255,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private void handleUnknownGroupMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group)
{
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RequestGroupInfoJob(content.getSender(), group.getGroupId()));
}
}
private void handleExpirationUpdate(@NonNull SignalServiceContent content, private void handleExpirationUpdate(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
@ -346,7 +323,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
attachments, attachments,
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000, message.getExpiresInSeconds() * 1000,
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(), DistributionTypes.DEFAULT, quote.orNull(),
sharedContacts.or(Collections.emptyList()), sharedContacts.or(Collections.emptyList()),
linkPreviews.or(Collections.emptyList()), linkPreviews.or(Collections.emptyList()),
Collections.emptyList(), Collections.emptyList()); Collections.emptyList(), Collections.emptyList());
@ -476,10 +453,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Recipient masterRecipient = getMessageMasterDestination(content.getSender()); Recipient masterRecipient = getMessageMasterDestination(content.getSender());
String syncTarget = message.getSyncTarget().orNull(); String syncTarget = message.getSyncTarget().orNull();
if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
}
Long threadId = null; Long threadId = null;
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
@ -608,14 +581,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message)
{
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SendDeliveryReceiptJob(Address.fromSerialized(content.getSender()), message.getTimestamp()));
}
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private void handleDeliveryReceipt(@NonNull SignalServiceContent content, private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message) @NonNull SignalServiceReceiptMessage message)

View File

@ -1,298 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SendMessageResult;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Quote;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class PushGroupSendJob extends PushSendJob implements InjectableType {
public static final String KEY = "PushGroupSendJob";
private static final String TAG = PushGroupSendJob.class.getSimpleName();
@Inject SignalServiceMessageSender messageSender;
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_FILTER_ADDRESS = "filter_address";
private long messageId;
private String filterAddress;
public PushGroupSendJob(long messageId, @NonNull Address destination, @Nullable Address filterAddress) {
this(new Job.Parameters.Builder()
.setQueue(destination.toGroupString())
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
messageId, filterAddress);
}
private PushGroupSendJob(@NonNull Job.Parameters parameters, long messageId, @Nullable Address filterAddress) {
super(parameters);
this.messageId = messageId;
this.filterAddress = filterAddress == null ? null :filterAddress.toString();
}
@WorkerThread
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination, @Nullable Address filterAddress) {
try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
List<Attachment> attachments = new LinkedList<>();
attachments.addAll(message.getAttachments());
attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList();
if (attachmentJobs.isEmpty()) {
jobManager.add(new PushGroupSendJob(messageId, destination, filterAddress));
} else {
jobManager.startChain(attachmentJobs)
.then(new PushGroupSendJob(messageId, destination, filterAddress))
.enqueue();
}
} catch (NoSuchMessageException | MmsException e) {
Log.w(TAG, "Failed to enqueue message.", e);
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putString(KEY_FILTER_ADDRESS, filterAddress)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
DatabaseFactory.getMmsDatabase(context).markAsSending(messageId);
}
@Override
public void onPushSend()
throws MmsException, NoSuchMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
List<NetworkFailure> existingNetworkFailures = message.getNetworkFailures();
List<IdentityKeyMismatch> existingIdentityMismatches = message.getIdentityKeyMismatches();
if (database.isSent(messageId)) {
log(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
}
try {
log(TAG, "Sending message: " + messageId);
List<Address> targets;
if (filterAddress != null) targets = Collections.singletonList(Address.fromSerialized(filterAddress));
else if (!existingNetworkFailures.isEmpty()) targets = Stream.of(existingNetworkFailures).map(NetworkFailure::getAddress).toList();
else targets = ClosedGroupsProtocolV2.getMessageDestinations(context, message.getRecipient().getAddress().toGroupString());
List<SendMessageResult> results = deliver(message, targets);
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.fromSerialized(result.getAddress().getNumber()))).toList();
List<IdentityKeyMismatch> identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList();
Set<Address> successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet());
List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<SendMessageResult> successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList();
for (NetworkFailure resolvedFailure : resolvedNetworkFailures) {
database.removeFailure(messageId, resolvedFailure);
existingNetworkFailures.remove(resolvedFailure);
}
for (IdentityKeyMismatch resolvedIdentity : resolvedIdentityFailures) {
database.removeMismatchedIdentity(messageId, resolvedIdentity.getAddress(), resolvedIdentity.getIdentityKey());
existingIdentityMismatches.remove(resolvedIdentity);
}
if (!networkFailures.isEmpty()) {
database.addFailures(messageId, networkFailures);
}
for (IdentityKeyMismatch mismatch : identityMismatches) {
database.addMismatchedIdentity(messageId, mismatch.getAddress(), mismatch.getIdentityKey());
}
for (SendMessageResult success : successes) {
DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Address.fromSerialized(success.getAddress().getNumber()),
messageId,
success.getSuccess().isUnidentified());
}
if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) {
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments());
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId);
ApplicationContext.getInstance(context)
.getExpiringMessageManager()
.scheduleDeletion(messageId, true, message.getExpiresIn());
}
} else if (!networkFailures.isEmpty()) {
throw new RetryLaterException();
} else if (!identityMismatches.isEmpty()) {
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
} catch (Exception e) {
warn(TAG, e);
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
if (exception instanceof IOException) return true;
// Loki - Disable since we have our own retrying
return false;
}
@Override
public void onCanceled() {
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
}
private List<SendMessageResult> deliver(OutgoingMediaMessage message, @NonNull List<Address> destinations)
throws IOException {
Address address = message.getRecipient().getAddress();
List<SignalServiceAddress> addresses = Stream.of(destinations).map(this::getPushAddress).toList();
List<Optional<UnidentifiedAccess>> unidentifiedAccess = Stream.of(addresses)
.map(a -> Address.fromSerialized(a.getNumber()))
.map(a -> Recipient.from(context, a, false))
.map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient))
.toList();
if (message.isGroup() && address.isClosedGroup()) {
SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL;
String groupId = address.toGroupString();
List<Attachment> attachments = Stream.of(message.getAttachments()).toList();
List<SignalServiceAttachment> attachmentPointers = getAttachmentPointersFor(attachments);
// Loki - Only send GroupUpdate or GroupQuit messages to closed groups
OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message;
GroupContext groupContext = groupMessage.getGroupContext();
SignalServiceAttachment avatar = attachmentPointers.isEmpty() ? null : attachmentPointers.get(0);
SignalServiceGroup.Type type = groupMessage.isGroupQuit() ? SignalServiceGroup.Type.QUIT : SignalServiceGroup.Type.UPDATE;
SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupId), groupType, groupContext.getName(), groupContext.getMembersList(), avatar, groupContext.getAdminsList());
SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getSentTimeMillis())
.withExpiration(message.getRecipient().getExpireMessages())
.asGroupMessage(group)
.build();
return messageSender.sendMessage(messageId, addresses, unidentifiedAccess, groupDataMessage);
} else {
SignalServiceDataMessage groupMessage = getDataMessage(address, message).build();
return messageSender.sendMessage(messageId, addresses, unidentifiedAccess, groupMessage);
}
}
public SignalServiceDataMessage.Builder getDataMessage(Address address, OutgoingMediaMessage message) {
SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL;
String groupId = address.toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
Optional<Quote> quote = getQuoteFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<Preview> previews = getPreviewsFor(message);
List<Attachment> attachments = Stream.of(message.getAttachments()).toList();
List<SignalServiceAttachment> attachmentPointers = getAttachmentPointersFor(attachments);
SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupId), groupType);
return SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getSentTimeMillis())
.asGroupMessage(group)
.withAttachments(attachmentPointers)
.withBody(message.getBody())
.withExpiration((int)(message.getExpiresIn() / 1000))
.asExpirationUpdate(message.isExpirationUpdate())
.withProfileKey(profileKey.orNull())
.withQuote(quote.orNull())
.withSharedContacts(sharedContacts)
.withPreviews(previews);
}
public static class Factory implements Job.Factory<PushGroupSendJob> {
@Override
public @NonNull PushGroupSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
String address = data.getString(KEY_FILTER_ADDRESS);
Address filter = address != null ? Address.fromSerialized(data.getString(KEY_FILTER_ADDRESS)) : null;
return new PushGroupSendJob(parameters, data.getLong(KEY_MESSAGE_ID), filter);
}
}
}

View File

@ -1,308 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.threads.recipients.Recipient.UnidentifiedAccessMode;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SendMessageResult;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.loki.api.SnodeAPI;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.inject.Inject;
public class PushMediaSendJob extends PushSendJob implements InjectableType {
public static final String KEY = "PushMediaSendJob";
private static final String TAG = PushMediaSendJob.class.getSimpleName();
private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id";
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_DESTINATION = "destination";
@Inject SignalServiceMessageSender messageSender;
private long messageId;
private long templateMessageId;
private Address destination;
public PushMediaSendJob(long templateMessageId, long messageId, Address destination) {
this(constructParameters(destination), templateMessageId, messageId, destination);
}
private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination) {
super(parameters);
this.templateMessageId = templateMessageId;
this.messageId = messageId;
this.destination = destination;
}
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination) {
enqueue(context, jobManager, messageId, messageId, destination);
}
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination) {
enqueue(context, jobManager, Collections.singletonList(new PushMediaSendJob(templateMessageId, messageId, destination)));
}
@WorkerThread
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, List<PushMediaSendJob> jobs) {
if (jobs.size() == 0) { return; }
PushMediaSendJob first = jobs.get(0);
long messageId = first.templateMessageId;
try {
List<AttachmentUploadJob> attachmentJobs = getAttachmentUploadJobs(context, messageId, first.destination);
if (attachmentJobs.isEmpty()) {
for (PushMediaSendJob job : jobs) { jobManager.add(job); }
} else {
jobManager.startChain(attachmentJobs)
.then((List<Job>)(List)jobs)
.enqueue();
}
} catch (NoSuchMessageException | MmsException e) {
Log.w(TAG, "Failed to enqueue message.", e);
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
public static List<AttachmentUploadJob> getAttachmentUploadJobs(@NonNull Context context, long messageId, @NonNull Address destination)
throws NoSuchMessageException, MmsException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
List<Attachment> attachments = new LinkedList<>();
attachments.addAll(message.getAttachments());
attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
return Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList();
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder()
.putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
.putLong(KEY_MESSAGE_ID, messageId)
.putString(KEY_DESTINATION, destination.serialize()).build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
DatabaseFactory.getMmsDatabase(context).markAsSending(messageId);
}
@Override
public void onPushSend()
throws RetryLaterException, MmsException, NoSuchMessageException,
UndeliverableMessageException
{
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(templateMessageId);
if (messageId >= 0 && database.isSent(messageId)) {
warn(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
}
try {
log(TAG, "Sending message: " + messageId);
Recipient recipient = Recipient.from(context, destination, false);
byte[] profileKey = recipient.getProfileKey();
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
boolean unidentified = deliver(message);
if (messageId >= 0) {
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments());
database.markUnidentified(messageId, unidentified);
}
if (recipient.isLocalNumber()) {
SyncMessageId id = new SyncMessageId(recipient.getAddress(), message.getSentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
log(TAG, "Marking recipient as UD-unrestricted following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
log(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
log(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
if (messageId > 0 && message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId);
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
}
log(TAG, "Sent message: " + messageId);
} catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
lokiMessageDatabase.setErrorMessage(messageId, e.getDescription());
database.markAsSentFailed(messageId);
}
}
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
// Loki - Disable since we have our own retrying
return false;
}
@Override
public void onCanceled() {
if (messageId >= 0) {
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
private boolean deliver(OutgoingMediaMessage message)
throws RetryLaterException, UndeliverableMessageException, SnodeAPI.Error
{
try {
Recipient recipient = Recipient.from(context, destination, false);
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
SignalServiceAddress address = getPushAddress(recipient.getAddress());
SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey);
List<Attachment> attachments = Stream.of(message.getAttachments()).toList();
List<SignalServiceAttachment> serviceAttachments = getAttachmentPointersFor(attachments);
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
Optional<SignalServiceDataMessage.Quote> quote = getQuoteFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<Preview> previews = getPreviewsFor(message);
Optional<UnidentifiedAccess> unidentifiedAccessPair = UnidentifiedAccessUtil.getAccessFor(context, recipient);
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
.withBody(message.getBody())
.withAttachments(serviceAttachments)
.withTimestamp(message.getSentTimeMillis())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.withQuote(quote.orNull())
.withSharedContacts(sharedContacts)
.withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate())
.build();
SignalServiceDataMessage mediaSelfSendMessage = SignalServiceDataMessage.newBuilder()
.withBody(message.getBody())
.withAttachments(serviceAttachments)
.withTimestamp(message.getSentTimeMillis())
.withSyncTarget(destination.serialize())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.withQuote(quote.orNull())
.withSharedContacts(sharedContacts)
.withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate())
.build();
if (userPublicKey == address.getNumber()) {
// Loki - Device link messages don't go through here
SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccessPair, mediaMessage, true);
if (result.getLokiAPIError() != null) {
throw result.getLokiAPIError();
} else {
return result.getSuccess().isUnidentified();
}
} else {
SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccessPair, mediaMessage, false);
if (result.getLokiAPIError() != null) {
throw result.getLokiAPIError();
} else {
boolean isUnidentified = result.getSuccess().isUnidentified();
try {
// send to ourselves to sync multi-device
Optional<UnidentifiedAccess> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, mediaSelfSendMessage, true);
if (selfSendResult.getLokiAPIError() != null) {
throw selfSendResult.getLokiAPIError();
}
} catch (Exception e) {
Log.e("Loki", "Error sending message to ourselves", e);
}
return isUnidentified;
}
}
} catch (FileNotFoundException e) {
warn(TAG, e);
throw new UndeliverableMessageException(e);
} catch (IOException e) {
warn(TAG, e);
throw new RetryLaterException(e);
}
}
public static final class Factory implements Job.Factory<PushMediaSendJob> {
@Override
public @NonNull PushMediaSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
long messageID = data.getLong(KEY_MESSAGE_ID);
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
return new PushMediaSendJob(parameters, templateMessageID, messageID, destination);
}
}
}

View File

@ -1,230 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.Util;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.session.libsignal.utilities.Hex;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public abstract class PushSendJob extends SendJob {
private static final String TAG = PushSendJob.class.getSimpleName();
protected PushSendJob(Job.Parameters parameters) {
super(parameters);
}
protected static Job.Parameters constructParameters(Address destination) {
return new Parameters.Builder()
.setQueue(destination.serialize())
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(1)
.build();
}
@Override
protected final void onSend() throws Exception {
onPushSend();
}
@Override
public void onRetry() {
super.onRetry();
Log.i(TAG, "onRetry()");
}
protected Optional<byte[]> getProfileKey(@NonNull Recipient recipient) {
if (!recipient.resolve().isSystemContact() && !recipient.resolve().isProfileSharing()) {
return Optional.absent();
}
return Optional.of(ProfileKeyUtil.getProfileKey(context));
}
protected SignalServiceAddress getPushAddress(Address address) {
String relay = null;
return new SignalServiceAddress(address.toString(), Optional.fromNullable(relay));
}
protected SignalServiceAttachment getAttachmentFor(Attachment attachment) {
try {
if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!");
InputStream is = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
return SignalServiceAttachment.newStreamBuilder()
.withStream(is)
.withContentType(attachment.getContentType())
.withLength(attachment.getSize())
.withFileName(attachment.getFileName())
.withVoiceNote(attachment.isVoiceNote())
.withWidth(attachment.getWidth())
.withHeight(attachment.getHeight())
.withCaption(attachment.getCaption())
.withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)))
.build();
} catch (IOException ioe) {
Log.w(TAG, "Couldn't open attachment", ioe);
}
return null;
}
protected @NonNull List<SignalServiceAttachment> getAttachmentPointersFor(List<Attachment> attachments) {
return Stream.of(attachments).map(this::getAttachmentPointerFor).filter(a -> a != null).toList();
}
protected @Nullable SignalServiceAttachment getAttachmentPointerFor(Attachment attachment) {
if (TextUtils.isEmpty(attachment.getLocation())) {
Log.w(TAG, "empty content id");
return null;
}
if (TextUtils.isEmpty(attachment.getKey())) {
Log.w(TAG, "empty encrypted key");
return null;
}
try {
long id = Long.parseLong(attachment.getLocation());
byte[] key = Base64.decode(attachment.getKey());
return new SignalServiceAttachmentPointer(id,
attachment.getContentType(),
key,
Optional.of(Util.toIntExact(attachment.getSize())),
Optional.absent(),
attachment.getWidth(),
attachment.getHeight(),
Optional.fromNullable(attachment.getDigest()),
Optional.fromNullable(attachment.getFileName()),
attachment.isVoiceNote(),
Optional.fromNullable(attachment.getCaption()), attachment.getUrl());
} catch (IOException | ArithmeticException e) {
Log.w(TAG, e);
return null;
}
}
protected static void notifyMediaMessageDeliveryFailed(Context context, long messageId) {
long threadId = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId);
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (threadId != -1 && recipient != null) {
ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
protected Optional<SignalServiceDataMessage.Quote> getQuoteFor(OutgoingMediaMessage message) {
if (message.getOutgoingQuote() == null) return Optional.absent();
long quoteId = message.getOutgoingQuote().getId();
String quoteBody = message.getOutgoingQuote().getText();
Address quoteAuthor = message.getOutgoingQuote().getAuthor();
List<SignalServiceDataMessage.Quote.QuotedAttachment> quoteAttachments = new LinkedList<>();
for (Attachment attachment : message.getOutgoingQuote().getAttachments()) {
BitmapUtil.ScaleResult thumbnailData = null;
SignalServiceAttachment thumbnail = null;
String thumbnailType = MediaTypes.IMAGE_JPEG;
try {
if (MediaUtil.isImageType(attachment.getContentType()) && attachment.getDataUri() != null) {
Bitmap.CompressFormat format = BitmapUtil.getCompressFormatForContentType(attachment.getContentType());
thumbnailData = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getDataUri()), 100, 100, 500 * 1024, format);
thumbnailType = attachment.getContentType();
} else if (MediaUtil.isVideoType(attachment.getContentType()) && attachment.getThumbnailUri() != null) {
thumbnailData = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getThumbnailUri()), 100, 100, 500 * 1024);
}
if (thumbnailData != null) {
thumbnail = SignalServiceAttachment.newStreamBuilder()
.withContentType(thumbnailType)
.withWidth(thumbnailData.getWidth())
.withHeight(thumbnailData.getHeight())
.withLength(thumbnailData.getBitmap().length)
.withStream(new ByteArrayInputStream(thumbnailData.getBitmap()))
.build();
}
quoteAttachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.getContentType(),
attachment.getFileName(),
thumbnail));
} catch (BitmapDecodingException e) {
Log.w(TAG, e);
}
}
return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments));
}
List<SharedContact> getSharedContactsFor(OutgoingMediaMessage mediaMessage) {
List<SharedContact> sharedContacts = new LinkedList<>();
for (Contact contact : mediaMessage.getSharedContacts()) {
SharedContact.Builder builder = ContactModelMapper.localToRemoteBuilder(contact);
SharedContact.Avatar avatar = null;
if (contact.getAvatar() != null && contact.getAvatar().getAttachment() != null) {
avatar = SharedContact.Avatar.newBuilder().withAttachment(getAttachmentFor(contact.getAvatarAttachment()))
.withProfileFlag(contact.getAvatar().isProfile())
.build();
}
builder.setAvatar(avatar);
sharedContacts.add(builder.build());
}
return sharedContacts;
}
List<Preview> getPreviewsFor(OutgoingMediaMessage mediaMessage) {
return Stream.of(mediaMessage.getLinkPreviews()).map(lp -> {
SignalServiceAttachment attachment = lp.getThumbnail().isPresent() ? getAttachmentPointerFor(lp.getThumbnail().get()) : null;
return new Preview(lp.getUrl(), lp.getTitle(), Optional.fromNullable(attachment));
}).toList();
}
protected abstract void onPushSend() throws Exception;
}

View File

@ -1,241 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.Recipient.UnidentifiedAccessMode;
import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SendMessageResult;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.loki.api.SnodeAPI;
import java.io.IOException;
import javax.inject.Inject;
public class PushTextSendJob extends PushSendJob implements InjectableType {
public static final String KEY = "PushTextSendJob";
private static final String TAG = PushTextSendJob.class.getSimpleName();
private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id";
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_DESTINATION = "destination";
@Inject SignalServiceMessageSender messageSender;
private long messageId;
private long templateMessageId;
private Address destination;
public PushTextSendJob(long messageId, Address destination) {
this(messageId, messageId, destination);
}
public PushTextSendJob(long templateMessageId, long messageId, Address destination) {
this(constructParameters(destination), templateMessageId, messageId, destination);
}
private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination) {
super(parameters);
this.templateMessageId = templateMessageId;
this.messageId = messageId;
this.destination = destination;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder()
.putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
.putLong(KEY_MESSAGE_ID, messageId)
.putString(KEY_DESTINATION, destination.serialize()).build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
if (messageId >= 0) {
DatabaseFactory.getSmsDatabase(context).markAsSending(messageId);
}
}
@Override
public void onPushSend() throws NoSuchMessageException, RetryLaterException {
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
SmsMessageRecord record = database.getMessage(templateMessageId);
Recipient recordRecipient = record.getRecipient().resolve();
boolean hasSameDestination = destination.equals(recordRecipient.getAddress());
if (hasSameDestination && !record.isPending() && !record.isFailed()) {
Log.d("Loki", "Message with ID: " + templateMessageId + " was already sent; ignoring.");
return;
}
try {
log(TAG, "Sending message: " + templateMessageId + (hasSameDestination ? "" : "to a linked device."));
Recipient recipient = Recipient.from(context, destination, false);
byte[] profileKey = recipient.getProfileKey();
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
boolean unidentified = deliver(record);
if (messageId >= 0) {
database.markAsSent(messageId, true);
database.markUnidentified(messageId, unidentified);
}
if (recipient.isLocalNumber()) {
SyncMessageId id = new SyncMessageId(recipient.getAddress(), record.getDateSent());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
log(TAG, "Marking recipient as UD-unrestricted following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
log(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
log(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
if (record.getExpiresIn() > 0 && messageId >= 0) {
database.markExpireStarted(messageId);
expirationManager.scheduleDeletion(record.getId(), record.isMms(), record.getExpiresIn());
}
log(TAG, "Sent message: " + templateMessageId + (hasSameDestination ? "" : "to a linked device."));
} catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
lokiMessageDatabase.setErrorMessage(record.getId(), e.getDescription());
database.markAsSentFailed(record.getId());
}
}
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
// Loki - Disable since we have our own retrying
return false;
}
@Override
public void onCanceled() {
if (messageId >= 0) {
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
long threadId = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageId);
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (threadId != -1 && recipient != null) {
ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
}
private boolean deliver(SmsMessageRecord message) throws RetryLaterException, SnodeAPI.Error
{
try {
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
Recipient recipient = Recipient.from(context, destination, false);
SignalServiceAddress address = getPushAddress(recipient.getAddress());
SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey);
Optional<byte[]> profileKey = getProfileKey(recipient);
Optional<UnidentifiedAccess> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
log(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent())
.withBody(message.getBody())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.build();
SignalServiceDataMessage textSecureSelfSendMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent())
.withBody(message.getBody())
.withSyncTarget(destination.serialize())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.build();
if (userPublicKey.equals(address.getNumber())) {
// Loki - Device link messages don't go through here
SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, true);
if (result.getLokiAPIError() != null) {
throw result.getLokiAPIError();
} else {
return result.getSuccess().isUnidentified();
}
} else {
SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, false);
if (result.getLokiAPIError() != null) {
throw result.getLokiAPIError();
} else {
boolean isUnidentified = result.getSuccess().isUnidentified();
try {
// send to ourselves to sync multi-device
Optional<UnidentifiedAccess> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, textSecureSelfSendMessage, true);
if (selfSendResult.getLokiAPIError() != null) {
throw selfSendResult.getLokiAPIError();
}
} catch (Exception e) {
Log.e("Loki", "Error sending message to ourselves", e);
}
return isUnidentified;
}
}
} catch (IOException e) {
warn(TAG, "Failure", e);
throw new RetryLaterException(e);
}
}
public static class Factory implements Job.Factory<PushTextSendJob> {
@Override
public @NonNull PushTextSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
long messageID = data.getLong(KEY_MESSAGE_ID);
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
return new PushTextSendJob(parameters, templateMessageID, messageID, destination);
}
}
}

View File

@ -1,107 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.utilities.GroupUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceGroup.Type;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class RequestGroupInfoJob extends BaseJob implements InjectableType {
public static final String KEY = "RequestGroupInfoJob";
@SuppressWarnings("unused")
private static final String TAG = RequestGroupInfoJob.class.getSimpleName();
private static final String KEY_SOURCE = "source";
private static final String KEY_GROUP_ID = "group_id";
@Inject SignalServiceMessageSender messageSender;
private String source;
private byte[] groupId;
public RequestGroupInfoJob(@NonNull String source, @NonNull byte[] groupId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
source,
groupId);
}
private RequestGroupInfoJob(@NonNull Job.Parameters parameters, @NonNull String source, @NonNull byte[] groupId) {
super(parameters);
this.source = source;
this.groupId = groupId;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putString(KEY_SOURCE, source)
.putString(KEY_GROUP_ID, GroupUtil.getEncodedClosedGroupID(groupId))
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException {
SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO)
.withId(groupId, SignalServiceGroup.GroupType.SIGNAL)
.build();
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()
.asGroupMessage(group)
.withTimestamp(System.currentTimeMillis())
.build();
messageSender.sendMessage(0, new SignalServiceAddress(source),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromExternal(context, source), false)),
message, false);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
}
public static final class Factory implements Job.Factory<RequestGroupInfoJob> {
@Override
public @NonNull RequestGroupInfoJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RequestGroupInfoJob(parameters,
data.getString(KEY_SOURCE),
GroupUtil.getDecodedGroupIDAsData(data.getString(KEY_GROUP_ID)));
}
}
}

View File

@ -1,112 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class SendDeliveryReceiptJob extends BaseJob implements InjectableType {
public static final String KEY = "SendDeliveryReceiptJob";
private static final String KEY_ADDRESS = "address";
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_TIMESTAMP = "timestamp";
private static final String TAG = SendReadReceiptJob.class.getSimpleName();
@Inject
transient SignalServiceMessageSender messageSender;
private String address;
private long messageId;
private long timestamp;
public SendDeliveryReceiptJob(@NonNull Address address, long messageId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
address,
messageId,
System.currentTimeMillis());
}
private SendDeliveryReceiptJob(@NonNull Job.Parameters parameters,
@NonNull Address address,
long messageId,
long timestamp)
{
super(parameters);
this.address = address.serialize();
this.messageId = messageId;
this.timestamp = timestamp;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putString(KEY_ADDRESS, address)
.putLong(KEY_MESSAGE_ID, messageId)
.putLong(KEY_TIMESTAMP, timestamp)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException {
Log.d("Loki", "Sending delivery receipt.");
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
Collections.singletonList(messageId),
timestamp);
messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
receiptMessage);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
if (e instanceof PushNetworkException) return true;
return false;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to send delivery receipt to: " + address);
}
public static final class Factory implements Job.Factory<SendDeliveryReceiptJob> {
@Override
public @NonNull SendDeliveryReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new SendDeliveryReceiptJob(parameters,
Address.fromSerialized(data.getString(KEY_ADDRESS)),
data.getLong(KEY_MESSAGE_ID),
data.getLong(KEY_TIMESTAMP));
}
}
}

View File

@ -1,63 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobLogger;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public abstract class SendJob extends BaseJob {
@SuppressWarnings("unused")
private final static String TAG = SendJob.class.getSimpleName();
public SendJob(Job.Parameters parameters) {
super(parameters);
}
@Override
public final void onRun() throws Exception {
Log.i(TAG, "Starting message send attempt");
onSend();
Log.i(TAG, "Message send completed");
}
protected abstract void onSend() throws Exception;
protected void markAttachmentsUploaded(long messageId, @NonNull List<Attachment> attachments) {
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
for (Attachment attachment : attachments) {
database.markAttachmentUploaded(messageId, attachment);
}
}
protected void log(@NonNull String tag, @NonNull String message) {
Log.i(tag, JobLogger.format(this, message));
}
protected void warn(@NonNull String tag, @NonNull String message) {
warn(tag, message, null);
}
protected void warn(@NonNull String tag, @Nullable Throwable t) {
warn(tag, "", t);
}
protected void warn(@NonNull String tag, @NonNull String message, @Nullable Throwable t) {
Log.w(tag, JobLogger.format(this, message), t);
}
}

View File

@ -1,123 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class SendReadReceiptJob extends BaseJob implements InjectableType {
public static final String KEY = "SendReadReceiptJob";
private static final String TAG = SendReadReceiptJob.class.getSimpleName();
private static final String KEY_ADDRESS = "address";
private static final String KEY_MESSAGE_IDS = "message_ids";
private static final String KEY_TIMESTAMP = "timestamp";
@Inject SignalServiceMessageSender messageSender;
private String address;
private List<Long> messageIds;
private long timestamp;
public SendReadReceiptJob(Address address, List<Long> messageIds) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
address,
messageIds,
System.currentTimeMillis());
}
private SendReadReceiptJob(@NonNull Job.Parameters parameters,
@NonNull Address address,
@NonNull List<Long> messageIds,
long timestamp)
{
super(parameters);
this.address = address.serialize();
this.messageIds = messageIds;
this.timestamp = timestamp;
}
@Override
public @NonNull
Data serialize() {
long[] ids = new long[messageIds.size()];
for (int i = 0; i < ids.length; i++) {
ids[i] = messageIds.get(i);
}
return new Data.Builder().putString(KEY_ADDRESS, address)
.putLongArray(KEY_MESSAGE_IDS, ids)
.putLong(KEY_TIMESTAMP, timestamp)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException {
if (!TextSecurePreferences.isReadReceiptsEnabled(context) || messageIds.isEmpty()) return;
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp);
messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
receiptMessage);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
if (e instanceof PushNetworkException) return true;
return false;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to send read receipts to: " + address);
}
public static final class Factory implements Job.Factory<SendReadReceiptJob> {
@Override
public @NonNull SendReadReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) {
Address address = Address.fromSerialized(data.getString(KEY_ADDRESS));
long timestamp = data.getLong(KEY_TIMESTAMP);
long[] ids = data.hasLongArray(KEY_MESSAGE_IDS) ? data.getLongArray(KEY_MESSAGE_IDS) : new long[0];
List<Long> messageIds = new ArrayList<>(ids.length);
for (long id : ids) {
messageIds.add(id);
}
return new SendReadReceiptJob(parameters, address, messageIds, timestamp);
}
}
}

View File

@ -1,116 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage.Action;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class TypingSendJob extends BaseJob implements InjectableType {
public static final String KEY = "TypingSendJob";
private static final String TAG = TypingSendJob.class.getSimpleName();
private static final String KEY_THREAD_ID = "thread_id";
private static final String KEY_TYPING = "typing";
private long threadId;
private boolean typing;
@Inject SignalServiceMessageSender messageSender;
public TypingSendJob(long threadId, boolean typing) {
this(new Job.Parameters.Builder()
.setQueue("TYPING_" + threadId)
.setMaxAttempts(1)
.setLifespan(TimeUnit.SECONDS.toMillis(5))
.build(),
threadId,
typing);
}
private TypingSendJob(@NonNull Job.Parameters parameters, long threadId, boolean typing) {
super(parameters);
this.threadId = threadId;
this.typing = typing;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putLong(KEY_THREAD_ID, threadId)
.putBoolean(KEY_TYPING, typing)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws Exception {
if (!TextSecurePreferences.isTypingIndicatorsEnabled(context)) {
return;
}
Log.d(TAG, "Sending typing " + (typing ? "started" : "stopped") + " for thread " + threadId);
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (recipient == null) {
throw new IllegalStateException("Tried to send a typing indicator to a non-existent thread.");
}
List<Recipient> recipients = Collections.singletonList(recipient);
if (recipient.isGroupRecipient()) {
recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false);
}
List<SignalServiceAddress> addresses = Stream.of(recipients).map(r -> new SignalServiceAddress(r.getAddress().serialize())).toList();
List<Optional<UnidentifiedAccess>> unidentifiedAccess = Stream.of(recipients).map(r -> UnidentifiedAccessUtil.getAccessFor(context, r)).toList();
SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis());
messageSender.sendTyping(addresses, unidentifiedAccess, typingMessage);
}
@Override
public void onCanceled() {
}
@Override
protected boolean onShouldRetry(@NonNull Exception exception) {
return false;
}
public static final class Factory implements Job.Factory<TypingSendJob> {
@Override
public @NonNull TypingSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new TypingSendJob(parameters, data.getLong(KEY_THREAD_ID), data.getBoolean(KEY_TYPING));
}
}
}

View File

@ -9,9 +9,9 @@ import androidx.annotation.Nullable;
import com.google.android.gms.common.util.IOUtils; import com.google.android.gms.common.util.IOUtils;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.MediaTypes;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.net.CallRequestController; import org.thoughtcrime.securesms.net.CallRequestController;
@ -192,7 +192,7 @@ public class LinkPreviewRepository implements InjectableType {
return Optional.of(new UriAttachment(uri, return Optional.of(new UriAttachment(uri,
uri, uri,
contentType, contentType,
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED,
bytes.length, bytes.length,
bitmap.getWidth(), bitmap.getWidth(),
bitmap.getHeight(), bitmap.getHeight(),

View File

@ -43,13 +43,13 @@ public class LinkPreviewViewModel extends ViewModel {
return linkPreviewState.getValue() != null && linkPreviewState.getValue().getLinkPreview().isPresent(); return linkPreviewState.getValue() != null && linkPreviewState.getValue().getLinkPreview().isPresent();
} }
public @NonNull List<LinkPreview> getActiveLinkPreviews() { public Optional<LinkPreview> getActiveLinkPreview() {
final LinkPreviewState state = linkPreviewState.getValue(); final LinkPreviewState state = linkPreviewState.getValue();
if (state == null || !state.getLinkPreview().isPresent()) { if (state == null || !state.getLinkPreview().isPresent()) {
return Collections.emptyList(); return Optional.absent();
} else { } else {
return Collections.singletonList(state.getLinkPreview().get()); return state.getLinkPreview();
} }
} }

View File

@ -13,18 +13,18 @@ import android.widget.Toast
import kotlinx.android.synthetic.main.activity_create_closed_group.* import kotlinx.android.synthetic.main.activity_create_closed_group.*
import network.loki.messenger.R import network.loki.messenger.R
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.DistributionTypes
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.loki.utilities.fadeIn import org.thoughtcrime.securesms.loki.utilities.fadeIn
import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.loki.utilities.fadeOut
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
//TODO Refactor to avoid using kotlinx.android.synthetic //TODO Refactor to avoid using kotlinx.android.synthetic
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> { class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
@ -104,13 +104,13 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
if (selectedMembers.count() < 1) { if (selectedMembers.count() < 1) {
return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
} }
if (selectedMembers.count() >= ClosedGroupsProtocolV2.groupSizeLimit) { // Minus one because we're going to include self later if (selectedMembers.count() >= groupSizeLimit) { // Minus one because we're going to include self later
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
} }
val userPublicKey = TextSecurePreferences.getLocalNumber(this)!! val userPublicKey = TextSecurePreferences.getLocalNumber(this)!!
isLoading = true isLoading = true
loaderContainer.fadeIn() loaderContainer.fadeIn()
ClosedGroupsProtocolV2.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> MessageSender.createClosedGroup(name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
loaderContainer.fadeOut() loaderContainer.fadeOut()
isLoading = false isLoading = false
val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false)) val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
@ -128,7 +128,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
val intent = Intent(context, ConversationActivity::class.java) val intent = Intent(context, ConversationActivity::class.java)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId) intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT) intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -18,6 +18,7 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.DistributionTypes
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
@ -69,7 +70,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC
intent.setDataAndType(getIntent().data, getIntent().type) intent.setDataAndType(getIntent().data, getIntent().type)
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient) val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread) intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT) intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
startActivity(intent) startActivity(intent)
finish() finish()
} }

View File

@ -19,12 +19,12 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.utilities.fadeIn import org.thoughtcrime.securesms.loki.utilities.fadeIn
import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.loki.utilities.fadeOut
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
@ -260,7 +260,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
} }
val maxGroupMembers = if (isClosedGroup) ClosedGroupsProtocolV2.groupSizeLimit else legacyGroupSizeLimit val maxGroupMembers = if (isClosedGroup) groupSizeLimit else legacyGroupSizeLimit
if (members.size >= maxGroupMembers) { if (members.size >= maxGroupMembers) {
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
} }
@ -277,17 +277,17 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
isLoading = true isLoading = true
loaderContainer.fadeIn() loaderContainer.fadeIn()
val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) { val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey!!) MessageSender.explicitLeave(groupPublicKey!!)
} else { } else {
task { task {
if (hasNameChanged) { if (hasNameChanged) {
ClosedGroupsProtocolV2.explicitNameChange(this@EditClosedGroupActivity, groupPublicKey!!, name) MessageSender.explicitNameChange(groupPublicKey!!, name)
} }
members.filterNot { it in originalMembers }.let { adds -> members.filterNot { it in originalMembers }.let { adds ->
if (adds.isNotEmpty()) ClosedGroupsProtocolV2.explicitAddMembers(this@EditClosedGroupActivity, groupPublicKey!!, adds.map { it.address.serialize() }) if (adds.isNotEmpty()) MessageSender.explicitAddMembers(groupPublicKey!!, adds.map { it.address.serialize() })
} }
originalMembers.filterNot { it in members }.let { removes -> originalMembers.filterNot { it in members }.let { removes ->
if (removes.isNotEmpty()) ClosedGroupsProtocolV2.explicitRemoveMembers(this@EditClosedGroupActivity, groupPublicKey!!, removes.map { it.address.serialize() }) if (removes.isNotEmpty()) MessageSender.explicitRemoveMembers(groupPublicKey!!, removes.map { it.address.serialize() })
} }
} }
} }
@ -296,13 +296,11 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
isLoading = false isLoading = false
finish() finish()
}.failUi { exception -> }.failUi { exception ->
val message = if (exception is ClosedGroupsProtocolV2.Error) exception.description else "An error occurred" val message = if (exception is MessageSender.Error) exception.description else "An error occurred"
Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show() Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
loaderContainer.fadeOut() loaderContainer.fadeOut()
isLoading = false isLoading = false
} }
} else {
GroupManager.updateGroup(this, groupID, members, null, name, admins)
} }
} }
} }

View File

@ -26,13 +26,12 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.utilities.GroupUtil import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.utilities.ProfilePictureModifiedEvent import org.session.libsession.utilities.*
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.Util
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
@ -42,7 +41,6 @@ import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.dialogs.* import org.thoughtcrime.securesms.loki.dialogs.*
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.loki.views.ConversationView import org.thoughtcrime.securesms.loki.views.ConversationView
@ -142,6 +140,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
if (userPublicKey != null) { if (userPublicKey != null) {
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB) MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
application.publicChatManager.startPollersIfNeeded() application.publicChatManager.startPollersIfNeeded()
JobQueue.shared.resumePendingJobs()
} }
IP2Country.configureIfNeeded(this) IP2Country.configureIfNeeded(this)
application.registerForFCMIfNeeded(false) application.registerForFCMIfNeeded(false)
@ -344,7 +343,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
isClosedGroup = false isClosedGroup = false
} }
if (isClosedGroup) { if (isClosedGroup) {
ClosedGroupsProtocolV2.explicitLeave(context, groupPublicKey!!) MessageSender.explicitLeave(groupPublicKey!!)
} else { } else {
Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
return@launch return@launch

View File

@ -3,11 +3,9 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.views.FakeChatView import org.thoughtcrime.securesms.loki.views.FakeChatView

View File

@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities import org.session.libsession.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo

View File

@ -17,6 +17,7 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.DistributionTypes
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
@ -60,7 +61,7 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperF
intent.setDataAndType(getIntent().data, getIntent().type) intent.setDataAndType(getIntent().data, getIntent().type)
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient) val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread) intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT) intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
startActivity(intent) startActivity(intent)
finish() finish()
} }

View File

@ -19,7 +19,7 @@ import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities import org.session.libsession.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo

View File

@ -23,7 +23,7 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities import org.session.libsession.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import java.util.* import java.util.*

View File

@ -12,7 +12,7 @@ import android.widget.Toast
import kotlinx.android.synthetic.main.activity_seed.* import kotlinx.android.synthetic.main.activity_seed.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.getColorWithID import org.thoughtcrime.securesms.loki.utilities.getColorWithID
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences

View File

@ -7,12 +7,11 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.PushDecryptJob import org.thoughtcrime.securesms.jobs.PushDecryptJob
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.libsignal.util.guava.Optional

View File

@ -14,36 +14,12 @@ import org.session.libsignal.service.loki.api.crypto.SessionProtocol
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities import org.session.libsession.utilities.KeyPairUtilities
class SessionProtocolImpl(private val context: Context) : SessionProtocol { class SessionProtocolImpl(private val context: Context) : SessionProtocol {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
override fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray {
val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw SessionProtocol.Exception.NoUserED25519KeyPair
val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey
val signature = ByteArray(Sign.BYTES)
try {
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
} catch (exception: Exception) {
Log.d("Loki", "Couldn't sign message due to error: $exception.")
throw SessionProtocol.Exception.SigningFailed
}
val plaintextWithMetadata = plaintext + userED25519KeyPair.publicKey.asBytes + signature
val ciphertext = ByteArray(plaintextWithMetadata.size + Box.SEALBYTES)
try {
sodium.cryptoBoxSeal(ciphertext, plaintextWithMetadata, plaintextWithMetadata.size.toLong(), recipientX25519PublicKey)
} catch (exception: Exception) {
Log.d("Loki", "Couldn't encrypt message due to error: $exception.")
throw SessionProtocol.Exception.EncryptionFailed
}
return ciphertext
}
override fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String> { override fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String> {
val recipientX25519PrivateKey = x25519KeyPair.privateKey.serialize() val recipientX25519PrivateKey = x25519KeyPair.privateKey.serialize()
val recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded()) val recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded())

View File

@ -13,7 +13,7 @@ import org.session.libsignal.service.loki.api.Snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.utilities.PublicKeyValidation import org.session.libsignal.service.loki.utilities.PublicKeyValidation

View File

@ -12,22 +12,22 @@ import org.thoughtcrime.securesms.loki.utilities.*
class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
companion object { companion object {
private val sessionJobTable = "loki_thread_session_reset_database" private const val sessionJobTable = "session_job_database"
val jobID = "job_id" const val jobID = "job_id"
val jobType = "job_type" const val jobType = "job_type"
val failureCount = "failure_count" const val failureCount = "failure_count"
val serializedData = "serialized_data" const val serializedData = "serialized_data"
@JvmStatic val createSessionJobTableCommand = "CREATE TABLE $sessionJobTable ($jobID INTEGER PRIMARY KEY, $jobType STRING, $failureCount INTEGER DEFAULT 0, $serializedData TEXT);" @JvmStatic val createSessionJobTableCommand = "CREATE TABLE $sessionJobTable ($jobID INTEGER PRIMARY KEY, $jobType STRING, $failureCount INTEGER DEFAULT 0, $serializedData TEXT);"
} }
fun persistJob(job: Job) { fun persistJob(job: Job) {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2) val contentValues = ContentValues(4)
contentValues.put(jobID, job.id) contentValues.put(jobID, job.id)
contentValues.put(jobType, job.getFactoryKey()) contentValues.put(jobType, job.getFactoryKey())
contentValues.put(failureCount, job.failureCount) contentValues.put(failureCount, job.failureCount)
contentValues.put(serializedData, SessionJobHelper.dataSerializer.serialize(job.serialize())) contentValues.put(serializedData, SessionJobHelper.dataSerializer.serialize(job.serialize()))
database.insertOrUpdate(sessionJobTable, contentValues, "$jobID = ?", arrayOf(jobID.toString())) database.insertOrUpdate(sessionJobTable, contentValues, "$jobID = ?", arrayOf(jobID))
} }
fun markJobAsSucceeded(job: Job) { fun markJobAsSucceeded(job: Job) {
@ -51,7 +51,7 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
database.getAll(sessionJobTable, "$jobType = ?", arrayOf(AttachmentUploadJob.KEY)) { cursor -> database.getAll(sessionJobTable, "$jobType = ?", arrayOf(AttachmentUploadJob.KEY)) { cursor ->
result.add(jobFromCursor(cursor) as AttachmentUploadJob) result.add(jobFromCursor(cursor) as AttachmentUploadJob)
} }
return result.first { job -> job.attachmentID == attachmentID } return result.firstOrNull { job -> job.attachmentID == attachmentID }
} }
fun getMessageSendJob(messageSendJobID: String): MessageSendJob? { fun getMessageSendJob(messageSendJobID: String): MessageSendJob? {
@ -85,10 +85,7 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
} }
} }
class SessionJobHelper() { object SessionJobHelper {
companion object {
val dataSerializer: Data.Serializer = JsonDataSerializer() val dataSerializer: Data.Serializer = JsonDataSerializer()
val sessionJobInstantiator: SessionJobInstantiator = SessionJobInstantiator(SessionJobManagerFactories.getSessionJobFactories()) val sessionJobInstantiator: SessionJobInstantiator = SessionJobInstantiator(SessionJobManagerFactories.getSessionJobFactories())
} }
}

View File

@ -11,7 +11,7 @@ import kotlinx.android.synthetic.main.dialog_clear_all_data.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities import org.session.libsession.utilities.KeyPairUtilities
class ClearAllDataDialog : DialogFragment() { class ClearAllDataDialog : DialogFragment() {

View File

@ -13,7 +13,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import kotlinx.android.synthetic.main.dialog_seed.view.* import kotlinx.android.synthetic.main.dialog_seed.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.session.libsignal.service.loki.crypto.MnemonicCodec import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey

View File

@ -1,253 +0,0 @@
package org.thoughtcrime.securesms.loki.protocol
import com.google.protobuf.ByteString
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.session.libsession.messaging.jobs.Data
import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.loki.utilities.TTLUtilities
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.jobs.BaseJob
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Hex
import java.util.concurrent.TimeUnit
class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Parameters,
private val destination: String,
private val kind: Kind,
private val sentTime: Long) : BaseJob(parameters) {
sealed class Kind {
class New(val publicKey: ByteArray, val name: String, val encryptionKeyPair: ECKeyPair, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
class Update(val name: String, val members: Collection<ByteArray>) : Kind()
object Leave : Kind()
class RemoveMembers(val members: Collection<ByteArray>) : Kind()
class AddMembers(val members: Collection<ByteArray>) : Kind()
class NameChange(val name: String) : Kind()
class EncryptionKeyPair(val wrappers: Collection<KeyPairWrapper>, val targetUser: String?) : Kind() // The new encryption key pair encrypted for each member individually
}
companion object {
const val KEY = "ClosedGroupUpdateMessageSendJobV2"
}
@Serializable
data class KeyPairWrapper(val publicKey: String, val encryptedKeyPair: ByteArray) {
companion object {
fun fromProto(proto: DataMessage.ClosedGroupControlMessage.KeyPairWrapper): KeyPairWrapper {
return KeyPairWrapper(proto.publicKey.toString(), proto.encryptedKeyPair.toByteArray())
}
}
fun toProto(): DataMessage.ClosedGroupControlMessage.KeyPairWrapper {
val result = DataMessage.ClosedGroupControlMessage.KeyPairWrapper.newBuilder()
result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey))
result.encryptedKeyPair = ByteString.copyFrom(encryptedKeyPair)
return result.build()
}
}
constructor(destination: String, kind: Kind, sentTime: Long) : this(Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue(KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(20)
.build(),
destination,
kind,
sentTime)
override fun getFactoryKey(): String { return KEY }
override fun serialize(): Data {
val builder = Data.Builder()
builder.putString("destination", destination)
builder.putLong("sentTime", sentTime)
when (kind) {
is Kind.New -> {
builder.putString("kind", "New")
builder.putByteArray("publicKey", kind.publicKey)
builder.putString("name", kind.name)
builder.putByteArray("encryptionKeyPairPublicKey", kind.encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded())
builder.putByteArray("encryptionKeyPairPrivateKey", kind.encryptionKeyPair.privateKey.serialize())
val members = kind.members.joinToString(" - ") { it.toHexString() }
builder.putString("members", members)
val admins = kind.admins.joinToString(" - ") { it.toHexString() }
builder.putString("admins", admins)
}
is Kind.Update -> {
builder.putString("kind", "Update")
builder.putString("name", kind.name)
val members = kind.members.joinToString(" - ") { it.toHexString() }
builder.putString("members", members)
}
is Kind.RemoveMembers -> {
builder.putString("kind", "RemoveMembers")
val members = kind.members.joinToString(" - ") { it.toHexString() }
builder.putString("members", members)
}
Kind.Leave -> {
builder.putString("kind", "Leave")
}
is Kind.AddMembers -> {
builder.putString("kind", "AddMembers")
val members = kind.members.joinToString(" - ") { it.toHexString() }
builder.putString("members", members)
}
is Kind.NameChange -> {
builder.putString("kind", "NameChange")
builder.putString("name", kind.name)
}
is Kind.EncryptionKeyPair -> {
builder.putString("kind", "EncryptionKeyPair")
val wrappers = kind.wrappers.joinToString(" - ") { Json.encodeToString(it) }
builder.putString("wrappers", wrappers)
builder.putString("targetUser", kind.targetUser)
}
}
return builder.build()
}
class Factory : Job.Factory<ClosedGroupUpdateMessageSendJobV2> {
override fun create(parameters: Parameters, data: Data): ClosedGroupUpdateMessageSendJobV2 {
val destination = data.getString("destination")
val rawKind = data.getString("kind")
val sentTime = data.getLong("sentTime")
val kind: Kind
when (rawKind) {
"New" -> {
val publicKey = data.getByteArray("publicKey")
val name = data.getString("name")
val encryptionKeyPairPublicKey = data.getByteArray("encryptionKeyPairPublicKey")
val encryptionKeyPairPrivateKey = data.getByteArray("encryptionKeyPairPrivateKey")
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairPublicKey), DjbECPrivateKey(encryptionKeyPairPrivateKey))
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
val admins = data.getString("admins").split(" - ").map { Hex.fromStringCondensed(it) }
kind = Kind.New(publicKey, name, encryptionKeyPair, members, admins)
}
"Update" -> {
val name = data.getString("name")
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
kind = Kind.Update(name, members)
}
"EncryptionKeyPair" -> {
val wrappers: Collection<KeyPairWrapper> = data.getString("wrappers").split(" - ").map { Json.decodeFromString(it) }
val targetUser = data.getString("targetUser")
kind = Kind.EncryptionKeyPair(wrappers, targetUser)
}
"RemoveMembers" -> {
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
kind = Kind.RemoveMembers(members)
}
"AddMembers" -> {
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
kind = Kind.AddMembers(members)
}
"NameChange" -> {
val name = data.getString("name")
kind = Kind.NameChange(name)
}
"Leave" -> {
kind = Kind.Leave
}
else -> throw Exception("Invalid closed group update message kind: $rawKind.")
}
return ClosedGroupUpdateMessageSendJobV2(parameters, destination, kind, sentTime)
}
}
public override fun onRun() {
val sendDestination = if (kind is Kind.EncryptionKeyPair && kind.targetUser != null) {
kind.targetUser
} else {
destination
}
val contentMessage = SignalServiceProtos.Content.newBuilder()
val dataMessage = DataMessage.newBuilder()
val closedGroupUpdate = DataMessage.ClosedGroupControlMessage.newBuilder()
when (kind) {
is Kind.New -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.NEW
closedGroupUpdate.publicKey = ByteString.copyFrom(kind.publicKey)
closedGroupUpdate.name = kind.name
val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder()
encryptionKeyPair.publicKey = ByteString.copyFrom(kind.encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded())
encryptionKeyPair.privateKey = ByteString.copyFrom(kind.encryptionKeyPair.privateKey.serialize())
closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build()
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
closedGroupUpdate.addAllAdmins(kind.admins.map { ByteString.copyFrom(it) })
}
is Kind.Update -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.UPDATE
closedGroupUpdate.name = kind.name
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
}
is Kind.EncryptionKeyPair -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
closedGroupUpdate.addAllWrappers(kind.wrappers.map { it.toProto() })
if (kind.targetUser != null) {
closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(destination))
}
}
Kind.Leave -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT
}
is Kind.RemoveMembers -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
}
is Kind.AddMembers -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
}
is Kind.NameChange -> {
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE
closedGroupUpdate.name = kind.name
}
}
dataMessage.closedGroupControlMessage = closedGroupUpdate.build()
contentMessage.dataMessage = dataMessage.build()
val serializedContentMessage = contentMessage.build().toByteArray()
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(sendDestination)
val recipient = Recipient.from(context, Address.fromSerialized(sendDestination), false)
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
val ttl = when (kind) {
is Kind.EncryptionKeyPair -> 4 * 24 * 60 * 60 * 1000
else -> TTLUtilities.getTTL(TTLUtilities.MessageType.ClosedGroupUpdate)
}
try {
// isClosedGroup can always be false as it's only used in the context of legacy closed groups
messageSender.sendMessage(0, address, udAccess,
sentTime, serializedContentMessage, false, ttl,
true, false, false, Optional.absent())
} catch (e: Exception) {
Log.d("Loki", "Failed to send closed group update message to: $sendDestination due to error: $e.")
}
}
public override fun onShouldRetry(e: Exception): Boolean {
return true
}
override fun onCanceled() { }
}

View File

@ -3,32 +3,25 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import org.session.libsignal.libsignal.ecc.Curve
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext
import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage import org.session.libsession.messaging.sending_receiving.MessageSender
import org.thoughtcrime.securesms.sms.IncomingGroupMessage import org.session.libsession.messaging.sending_receiving.generateAndSendNewEncryptionKeyPair
import org.thoughtcrime.securesms.sms.IncomingTextMessage import org.session.libsession.messaging.sending_receiving.pendingKeyPair
import org.session.libsignal.utilities.Hex import org.session.libsession.messaging.sending_receiving.sendEncryptionKeyPair
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.GroupRecord
@ -37,258 +30,8 @@ import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
object ClosedGroupsProtocolV2 { object ClosedGroupsProtocolV2 {
const val groupSizeLimit = 100
private val pendingKeyPair = ConcurrentHashMap<String,Optional<ECKeyPair>>()
sealed class Error(val description: String) : Exception() {
object NoThread : Error("Couldn't find a thread associated with the given group public key")
object NoKeyPair : Error("Couldn't find an encryption key pair associated with the given group public key.")
object InvalidUpdate : Error("Invalid group update.")
}
fun createClosedGroup(context: Context, name: String, members: Collection<String>): Promise<String, Exception> {
val deferred = deferred<String, Exception>()
ThreadUtils.queue {
// Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val membersAsData = members.map { Hex.fromStringCondensed(it) }
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
// Generate the group's public key
val groupPublicKey = Curve.generateKeyPair().hexEncodedPublicKey // Includes the "05" prefix
val sentTime = System.currentTimeMillis()
// Generate the key pair that'll be used for encryption and decryption
val encryptionKeyPair = Curve.generateKeyPair()
// Create the group
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val admins = setOf( userPublicKey )
val adminsAsData = admins.map { Hex.fromStringCondensed(it) }
DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
null, null, LinkedList(admins.map { Address.fromSerialized(it!!) }), System.currentTimeMillis())
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true)
// Send a closed group update message to all members individually
// Add the group to the user's set of public keys to poll for
apiDB.addClosedGroupPublicKey(groupPublicKey)
// Store the encryption key pair
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
// Notify the user
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTime)
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData)
for (member in members) {
val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind, sentTime)
job.setContext(context)
job.onRun() // Run the job immediately to make all of this sync
}
// Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
// Fulfill the promise
deferred.resolve(groupID)
}
// Return
return deferred.promise
}
/**
* @param notifyUser Inserts an outgoing info message for the user's leave message, useful to set `false` if
* you are exiting asynchronously and deleting the thread from [HomeActivity][org.thoughtcrime.securesms.loki.activities.HomeActivity.deleteConversation]
*/
@JvmStatic @JvmOverloads
fun explicitLeave(context: Context, groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
ThreadUtils.queue {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val group = groupDB.getGroup(groupID).orNull()
val updatedMembers = group.members.map { it.serialize() }.toSet() - userPublicKey
val admins = group.admins.map { it.serialize() }
val name = group.title
val sentTime = System.currentTimeMillis()
if (group == null) {
Log.d("Loki", "Can't leave nonexistent closed group.")
return@queue deferred.reject(Error.NoThread)
}
// Send the update to the group
@Suppress("NAME_SHADOWING")
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.Leave, sentTime)
job.setContext(context)
job.onRun() // Run the job immediately
// Notify the user
val infoType = GroupContext.Type.QUIT
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
if (notifyUser) {
insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
}
// Remove the group private key and unsubscribe from PNs
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
deferred.resolve(Unit)
}
return deferred.promise
}
@JvmStatic
fun explicitAddMembers(context: Context, groupPublicKey: String, membersToAdd: List<String>) {
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val group = groupDB.getGroup(groupID).orNull()
if (group == null) {
Log.d("Loki", "Can't leave nonexistent closed group.")
throw Error.NoThread
}
val updatedMembers = group.members.map { it.serialize() }.toSet() + membersToAdd
val membersAsData = updatedMembers.map { Hex.fromStringCondensed(it) }
val newMembersAsData = membersToAdd.map { Hex.fromStringCondensed(it) }
val admins = group.admins.map { it.serialize() }
val adminsAsData = admins.map { Hex.fromStringCondensed(it) }
val sentTime = System.currentTimeMillis()
val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull() ?: Optional.fromNullable(apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)).orNull()
if (encryptionKeyPair == null) {
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
throw Error.NoKeyPair
}
val name = group.title
// Send the update to the group
val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.AddMembers(newMembersAsData)
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime)
job.setContext(context)
job.onRun() // Run the job immediately
// Save the new group members
groupDB.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
// Notify the user
val infoType = GroupContext.Type.UPDATE
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
// Send closed group update messages to any new members individually
for (member in membersToAdd) {
@Suppress("NAME_SHADOWING")
val closedGroupNewKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData)
@Suppress("NAME_SHADOWING")
val newMemberJob = ClosedGroupUpdateMessageSendJobV2(member, closedGroupNewKind, sentTime)
ApplicationContext.getInstance(context).jobManager.add(newMemberJob)
}
}
@JvmStatic
fun explicitRemoveMembers(context: Context, groupPublicKey: String, membersToRemove: List<String>) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val group = groupDB.getGroup(groupID).orNull()
if (group == null) {
Log.d("Loki", "Can't leave nonexistent closed group.")
throw Error.NoThread
}
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
val removeMembersAsData = membersToRemove.map { Hex.fromStringCondensed(it) }
val admins = group.admins.map { it.serialize() }
val sentTime = System.currentTimeMillis()
val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
if (encryptionKeyPair == null) {
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
throw Error.NoKeyPair
}
if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) {
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
throw Error.InvalidUpdate
}
val name = group.title
// Send the update to the group
val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.RemoveMembers(removeMembersAsData)
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime)
job.setContext(context)
job.onRun() // Run the job immediately
// Save the new group members
groupDB.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
// Notify the user
val infoType = GroupContext.Type.UPDATE
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
val isCurrentUserAdmin = admins.contains(userPublicKey)
if (isCurrentUserAdmin) {
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMembers)
}
}
@JvmStatic
fun explicitNameChange(context: Context, groupPublicKey: String, newName: String) {
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val group = groupDB.getGroup(groupID).orNull()
val members = group.members.map { it.serialize() }.toSet()
val admins = group.admins.map { it.serialize() }
val sentTime = System.currentTimeMillis()
if (group == null) {
Log.d("Loki", "Can't leave nonexistent closed group.")
throw Error.NoThread
}
// Send the update to the group
val kind = ClosedGroupUpdateMessageSendJobV2.Kind.NameChange(newName)
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, kind, sentTime)
job.setContext(context)
job.onRun() // Run the job immediately
// Notify the user
val infoType = GroupContext.Type.UPDATE
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime)
// Update the group
groupDB.updateTitle(groupID, newName)
}
private fun generateAndSendNewEncryptionKeyPair(context: Context, groupPublicKey: String, targetMembers: Collection<String>) {
// Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val group = groupDB.getGroup(groupID).orNull()
if (group == null) {
Log.d("Loki", "Can't update nonexistent closed group.")
return
}
if (!group.admins.map { it.toString() }.contains(userPublicKey)) {
Log.d("Loki", "Can't distribute new encryption key pair as non-admin.")
return
}
// Generate the new encryption key pair
val newKeyPair = Curve.generateKeyPair()
// replace call will not succeed if no value already set
pendingKeyPair.putIfAbsent(groupPublicKey,Optional.absent())
do {
// make sure we set the pendingKeyPair or wait until it is not null
} while (!pendingKeyPair.replace(groupPublicKey,Optional.absent(),Optional.fromNullable(newKeyPair)))
// Distribute it
sendEncryptionKeyPair(context, groupPublicKey, newKeyPair, targetMembers)
// Store it * after * having sent out the message to the group
apiDB.addClosedGroupEncryptionKeyPair(newKeyPair, groupPublicKey)
pendingKeyPair[groupPublicKey] = Optional.absent()
}
private fun sendEncryptionKeyPair(context: Context, groupPublicKey: String, newKeyPair: ECKeyPair, targetMembers: Collection<String>, targetUser: String? = null, force: Boolean = true) {
val proto = SignalServiceProtos.KeyPair.newBuilder()
proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded())
proto.privateKey = ByteString.copyFrom(newKeyPair.privateKey.serialize())
val plaintext = proto.build().toByteArray()
val wrappers = targetMembers.mapNotNull { publicKey ->
val ciphertext = SessionProtocolImpl(context).encrypt(plaintext, publicKey)
ClosedGroupUpdateMessageSendJobV2.KeyPairWrapper(publicKey, ciphertext)
}
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.EncryptionKeyPair(wrappers, targetUser), System.currentTimeMillis())
if (force) {
job.setContext(context)
job.onRun() // Run the job immediately
} else {
ApplicationContext.getInstance(context).jobManager.add(job)
}
}
@JvmStatic @JvmStatic
fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return } if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return }
@ -361,11 +104,11 @@ object ClosedGroupsProtocolV2 {
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
// Notify the user (if we didn't make the group) // Notify the user (if we didn't make the group)
if (userPublicKey != senderPublicKey) { if (userPublicKey != senderPublicKey) {
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
} else if (prevGroup == null) { } else if (prevGroup == null) {
// only notify if we created this group // only notify if we created this group
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
} }
// Notify the PN server // Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
@ -410,7 +153,7 @@ object ClosedGroupsProtocolV2 {
val isCurrentUserAdmin = admins.contains(userPublicKey) val isCurrentUserAdmin = admins.contains(userPublicKey)
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
if (isCurrentUserAdmin) { if (isCurrentUserAdmin) {
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, newMembers) MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
} }
} }
val (contextType, signalType) = val (contextType, signalType) =
@ -418,9 +161,9 @@ object ClosedGroupsProtocolV2 {
else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, sentTimestamp) DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, sentTimestamp)
} else { } else {
insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, sentTimestamp) DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, sentTimestamp)
} }
} }
@ -448,9 +191,9 @@ object ClosedGroupsProtocolV2 {
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
} else { } else {
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
} }
if (userPublicKey in admins) { if (userPublicKey in admins) {
// send current encryption key to the latest added members // send current encryption key to the latest added members
@ -460,7 +203,7 @@ object ClosedGroupsProtocolV2 {
Log.d("Loki", "Couldn't get encryption key pair for closed group.") Log.d("Loki", "Couldn't get encryption key pair for closed group.")
} else { } else {
for (user in updateMembers) { for (user in updateMembers) {
sendEncryptionKeyPair(context, groupPublicKey, encryptionKeyPair, setOf(user), targetUser = user, force = false) MessageSender.sendEncryptionKeyPair(groupPublicKey, encryptionKeyPair, setOf(user), targetUser = user, force = false)
} }
} }
} }
@ -487,9 +230,9 @@ object ClosedGroupsProtocolV2 {
// Notify the user // Notify the user
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
} else { } else {
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
} }
} }
@ -523,15 +266,15 @@ object ClosedGroupsProtocolV2 {
val isCurrentUserAdmin = admins.contains(userPublicKey) val isCurrentUserAdmin = admins.contains(userPublicKey)
groupDB.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) }) groupDB.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
if (isCurrentUserAdmin) { if (isCurrentUserAdmin) {
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMemberList) MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList)
} }
} }
// Notify user // Notify user
if (userLeft) { if (userLeft) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID, sentTimestamp) DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID, sentTimestamp)
} else { } else {
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp) DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp)
} }
} }
@ -598,59 +341,6 @@ object ClosedGroupsProtocolV2 {
Log.d("Loki", "Received a new closed group encryption key pair") Log.d("Loki", "Received a new closed group encryption key pair")
} }
private fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: GroupContext.Type, type1: SignalServiceGroup.Type,
name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
val groupContextBuilder = GroupContext.newBuilder()
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
.setType(type0)
.setName(name)
.addAllMembers(members)
.addAllAdmins(admins)
val group = SignalServiceGroup(type1, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "")
val smsDB = DatabaseFactory.getSmsDatabase(context)
smsDB.insertMessageInbox(infoMessage)
}
private fun insertOutgoingInfoMessage(context: Context, groupID: String, type: GroupContext.Type, name: String,
members: Collection<String>, admins: Collection<String>, threadID: Long,
sentTimestamp: Long) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
val groupContextBuilder = GroupContext.newBuilder()
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
.setType(type)
.setName(name)
.addAllMembers(members)
.addAllAdmins(admins)
val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTimestamp, 0, null, listOf(), listOf())
val mmsDB = DatabaseFactory.getMmsDatabase(context)
val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context)
if (mmsSmsDB.getMessageFor(sentTimestamp,userPublicKey) != null) return
val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, sentTimestamp)
mmsDB.markAsSent(infoMessageID, true)
}
@JvmStatic
fun getMessageDestinations(context: Context, groupID: String): List<Address> {
return if (GroupUtil.isOpenGroup(groupID)) {
listOf(Address.fromSerialized(groupID))
} else {
var groupPublicKey: String? = null
try {
groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString() // TODO: The toHexString() here might be unnecessary
} catch (exception: Exception) {
// Do nothing
}
if (groupPublicKey != null && DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(groupPublicKey)) {
listOf(Address.fromSerialized(groupPublicKey))
} else {
DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false).map { it.address }
}
}
}
// region Deprecated // region Deprecated
private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
// Prepare // Prepare
@ -685,7 +375,7 @@ object ClosedGroupsProtocolV2 {
val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet()) val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet())
val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey) val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey)
if (wasAnyUserRemoved && isCurrentUserAdmin) { if (wasAnyUserRemoved && isCurrentUserAdmin) {
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, members) MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, members)
} }
// Update the group // Update the group
groupDB.updateTitle(groupID, name) groupDB.updateTitle(groupID, name)
@ -700,9 +390,9 @@ object ClosedGroupsProtocolV2 {
val admins = group.admins.map { it.toString() } val admins = group.admins.map { it.toString() }
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
insertOutgoingInfoMessage(context, groupID, type0, name, members, admins, threadID, sentTimestamp) DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type0, name, members, admins, threadID, sentTimestamp)
} else { } else {
insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins, sentTimestamp) DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins, sentTimestamp)
} }
} }
// endregion // endregion

View File

@ -3,30 +3,26 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import java.util.*
object MultiDeviceProtocol { object MultiDeviceProtocol {
// TODO: refactor this to use new message sending job
@JvmStatic @JvmStatic
fun syncConfigurationIfNeeded(context: Context) { fun syncConfigurationIfNeeded(context: Context) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
@ -39,22 +35,10 @@ object MultiDeviceProtocol {
ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey) ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey)
} }
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
val serializedMessage = configurationMessage.toProto()!!.toByteArray() MessageSender.send(configurationMessage, Address.fromSerialized(userPublicKey))
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(userPublicKey)
val recipient = Recipient.from(context, Address.fromSerialized(userPublicKey), false)
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
try {
messageSender.sendMessage(0, address, udAccess,
Date().time, serializedMessage, false, configurationMessage.ttl.toInt(),
true, false, false, Optional.absent())
TextSecurePreferences.setLastConfigurationSyncTime(context, now) TextSecurePreferences.setLastConfigurationSyncTime(context, now)
} catch (e: Exception) {
Log.d("Loki", "Failed to send configuration message due to error: $e.")
}
} }
// TODO: refactor this to use new message sending job
fun forceSyncConfigurationNowIfNeeded(context: Context) { fun forceSyncConfigurationNowIfNeeded(context: Context) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
val contacts = ContactUtilities.getAllContacts(context).filter { recipient -> val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
@ -63,19 +47,8 @@ object MultiDeviceProtocol {
ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey) ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey)
} }
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
val serializedMessage = configurationMessage.toProto()!!.toByteArray() MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(userPublicKey)
val recipient = Recipient.from(context, Address.fromSerialized(userPublicKey), false)
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
try {
messageSender.sendMessage(0, address, udAccess,
Date().time, serializedMessage, false, configurationMessage.ttl.toInt(),
true, false, false, Optional.absent())
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis()) TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
} catch (e: Exception) {
Log.d("Loki", "Failed to send configuration message due to error: $e.")
}
} }
// TODO: remove this after we migrate to new message receiving pipeline // TODO: remove this after we migrate to new message receiving pipeline
@ -106,8 +79,8 @@ object MultiDeviceProtocol {
closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(closedGroup.publicKey)) closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(closedGroup.publicKey))
closedGroupUpdate.name = closedGroup.name closedGroupUpdate.name = closedGroup.name
val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder() val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder()
encryptionKeyPair.publicKey = ByteString.copyFrom(closedGroup.encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) encryptionKeyPair.publicKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.publicKey.serialize().removing05PrefixIfNeeded())
encryptionKeyPair.privateKey = ByteString.copyFrom(closedGroup.encryptionKeyPair.privateKey.serialize()) encryptionKeyPair.privateKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.privateKey.serialize())
closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build() closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build()
closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })

View File

@ -20,6 +20,7 @@ import network.loki.messenger.R
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.audio.AudioSlidePlayer
@ -152,7 +153,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
downloadProgress.progress = 0 downloadProgress.progress = 0
} }
} }
(showControls && audio.transferState == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) -> { (showControls && audio.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) -> {
controlToggle.displayQuick(downloadProgress) controlToggle.displayQuick(downloadProgress)
seekBar.isEnabled = false seekBar.isEnabled = false
downloadProgress.isIndeterminate = true downloadProgress.isIndeterminate = true

View File

@ -145,14 +145,14 @@ class ProfilePictureView : RelativeLayout {
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) { private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
if (publicKey.isNotEmpty()) { if (publicKey.isNotEmpty()) {
if (imagesCached.contains(publicKey)) return
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
if (imagesCached.contains(recipient.profileAvatar.orEmpty())) return
val signalProfilePicture = recipient.contactPhoto val signalProfilePicture = recipient.contactPhoto
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0" if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") { && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
glide.clear(imageView) glide.clear(imageView)
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView) glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView)
imagesCached.add(publicKey) imagesCached.add(recipient.profileAvatar.orEmpty())
} else { } else {
val sizeInPX = resources.getDimensionPixelSize(sizeResId) val sizeInPX = resources.getDimensionPixelSize(sizeResId)
glide.clear(imageView) glide.clear(imageView)
@ -162,7 +162,7 @@ class ProfilePictureView : RelativeLayout {
publicKey, publicKey,
displayName displayName
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) )).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
imagesCached.add(publicKey) imagesCached.add(recipient.profileAvatar.orEmpty())
} }
} else { } else {
imageView.setImageDrawable(null) imageView.setImageDrawable(null)

View File

@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment; import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment;
import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.MediaTypes;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
@ -38,7 +39,7 @@ public class AudioSlide extends Slide {
} }
public AudioSlide(Context context, Uri uri, long dataSize, String contentType, boolean voiceNote) { public AudioSlide(Context context, Uri uri, long dataSize, String contentType, boolean voiceNote) {
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false, null)); super(context, new UriAttachment(uri, null, contentType, AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false, null));
} }
public AudioSlide(Context context, Attachment attachment) { public AudioSlide(Context context, Attachment attachment) {

View File

@ -23,7 +23,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
@ -135,8 +135,8 @@ public abstract class Slide {
} }
public boolean isPendingDownload() { public boolean isPendingDownload() {
return getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_FAILED || return getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED ||
getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING; getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING;
} }
public int getTransferState() { public int getTransferState() {
@ -172,7 +172,7 @@ public abstract class Slide {
return new UriAttachment(uri, return new UriAttachment(uri,
hasThumbnail ? uri : null, hasThumbnail ? uri : null,
resolvedType, resolvedType,
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED,
size, size,
width, width,
height, height,

View File

@ -25,18 +25,19 @@ import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.app.RemoteInput; import androidx.core.app.RemoteInput;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException;
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
/** /**
@ -72,17 +73,29 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
long replyThreadId; long replyThreadId;
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); if (threadId == -1) {
long expiresIn = recipient.getExpireMessages() * 1000L; replyThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
} else {
replyThreadId = threadId;
}
VisibleMessage message = new VisibleMessage();
message.setText(responseText.toString());
message.setSentTimestamp(System.currentTimeMillis());
MessageSender.send(message, recipient.getAddress());
if (recipient.isGroupRecipient()) { if (recipient.isGroupRecipient()) {
Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message");
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null);
replyThreadId = MessageSender.send(context, reply, threadId, false, null); try {
DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(reply, replyThreadId, false, null);
} catch (MmsException e) {
Log.w(TAG, e);
}
} else { } else {
Log.w("AndroidAutoReplyReceiver", "Sending regular message "); Log.w("AndroidAutoReplyReceiver", "Sending regular message ");
OutgoingTextMessage reply = new OutgoingTextMessage(recipient, responseText.toString(), expiresIn, subscriptionId); OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient);
replyThreadId = MessageSender.send(context, reply, threadId, false, null); DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(replyThreadId, reply, false, System.currentTimeMillis(), null);
} }
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true); List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true);

View File

@ -11,13 +11,15 @@ import androidx.core.app.NotificationManagerCompat;
import com.annimon.stream.Collectors; import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.session.libsession.messaging.messages.control.ReadReceipt;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
@ -25,7 +27,6 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class MarkReadReceiver extends BroadcastReceiver { public class MarkReadReceiver extends BroadcastReceiver {
@ -68,8 +69,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
public static void process(@NonNull Context context, @NonNull List<MarkedMessageInfo> markedReadMessages) { public static void process(@NonNull Context context, @NonNull List<MarkedMessageInfo> markedReadMessages) {
if (markedReadMessages.isEmpty()) return; if (markedReadMessages.isEmpty()) return;
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) return;
List<SyncMessageId> syncMessageIds = new LinkedList<>();
for (MarkedMessageInfo messageInfo : markedReadMessages) { for (MarkedMessageInfo messageInfo : markedReadMessages) {
scheduleDeletion(context, messageInfo.getExpirationInfo()); scheduleDeletion(context, messageInfo.getExpirationInfo());
@ -83,10 +83,9 @@ public class MarkReadReceiver extends BroadcastReceiver {
List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList();
// Loki - Check whether we want to send a read receipt to this user // Loki - Check whether we want to send a read receipt to this user
if (!SessionMetaProtocol.shouldSendReadReceipt(address)) { continue; } if (!SessionMetaProtocol.shouldSendReadReceipt(address)) { continue; }
// Loki - Take into account multi device ReadReceipt readReceipt = new ReadReceipt(timestamps);
ApplicationContext.getInstance(context) readReceipt.setSentTimestamp(System.currentTimeMillis());
.getJobManager() MessageSender.send(readReceipt, address);
.add(new SendReadReceiptJob(address, timestamps));
} }
} }

View File

@ -25,17 +25,19 @@ import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.app.RemoteInput; import androidx.core.app.RemoteInput;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException;
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
/** /**
@ -68,21 +70,27 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
long threadId;
Recipient recipient = Recipient.from(context, address, false); Recipient recipient = Recipient.from(context, address, false);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
long expiresIn = recipient.getExpireMessages() * 1000L; VisibleMessage message = new VisibleMessage();
message.setSentTimestamp(System.currentTimeMillis());
message.setText(responseText.toString());
switch (replyMethod) { switch (replyMethod) {
case GroupMessage: { case GroupMessage: {
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null);
threadId = MessageSender.send(context, reply, -1, false, null); try {
DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(reply, threadId, false, null);
MessageSender.send(message, address);
} catch (MmsException e) {
Log.w(TAG, e);
}
break; break;
} }
case SecureMessage: { case SecureMessage: {
OutgoingEncryptedMessage reply = new OutgoingEncryptedMessage(recipient, responseText.toString(), expiresIn); OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient);
threadId = MessageSender.send(context, reply, -1, false, null); DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, reply, false, System.currentTimeMillis(), null);
MessageSender.send(message, address);
break; break;
} }
default: default:

View File

@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import java.util.Comparator; import java.util.Comparator;
@ -70,6 +70,9 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Address address = Address.fromSerialized(senderPublicKey); Address address = Address.fromSerialized(senderPublicKey);
Recipient recipient = Recipient.from(context, address, false); Recipient recipient = Recipient.from(context, address, false);
if (recipient.isBlocked()) return;
Optional<SignalServiceGroup> groupInfo = Optional.absent(); Optional<SignalServiceGroup> groupInfo = Optional.absent();
if (content.getDataMessage().hasGroup()) { if (content.getDataMessage().hasGroup()) {
GroupContext groupContext = content.getDataMessage().getGroup(); GroupContext groupContext = content.getDataMessage().getGroup();
@ -103,17 +106,18 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
} }
@Override @Override
public void startAnyExpiration(long messageID) { public void startAnyExpiration(long timestamp, @NotNull String author) {
MessageRecord messageRecord = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(messageID); MessageRecord messageRecord = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(timestamp, author);
if (messageRecord != null) { if (messageRecord != null) {
boolean mms = messageRecord.isMms(); boolean mms = messageRecord.isMms();
Recipient recipient = messageRecord.getRecipient(); Recipient recipient = messageRecord.getRecipient();
if (recipient.getExpireMessages() <= 0) return;
if (mms) { if (mms) {
mmsDatabase.markExpireStarted(messageID); mmsDatabase.markExpireStarted(messageRecord.getId());
} else { } else {
smsDatabase.markExpireStarted(messageID); smsDatabase.markExpireStarted(messageRecord.getId());
} }
scheduleDeletion(messageID, mms, recipient.getExpireMessages()); scheduleDeletion(messageRecord.getId(), mms, recipient.getExpireMessages() * 1000);
} }
} }

View File

@ -7,11 +7,13 @@ import android.text.TextUtils;
import android.widget.Toast; import android.widget.Toast;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.Rfc5724Uri; import org.thoughtcrime.securesms.util.Rfc5724Uri;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -47,13 +49,11 @@ public class QuickResponseService extends IntentService {
number = URLDecoder.decode(number); number = URLDecoder.decode(number);
} }
Address address = Address.fromExternal(this, number);
Recipient recipient = Recipient.from(this, address, false);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
long expiresIn = recipient.getExpireMessages() * 1000L;
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
MessageSender.send(this, new OutgoingTextMessage(recipient, content, expiresIn, subscriptionId), -1, false, null); VisibleMessage message = new VisibleMessage();
message.setText(content);
message.setSentTimestamp(System.currentTimeMillis());
MessageSender.send(message, Address.fromExternal(this, number));
} }
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show();

View File

@ -1,213 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.sms;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.session.libsession.utilities.TextSecurePreferences;
public class MessageSender {
private static final String TAG = MessageSender.class.getSimpleName();
public static long send(final Context context,
final OutgoingTextMessage message,
final long threadId,
final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
Recipient recipient = message.getRecipient();
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
} else {
allocatedThreadId = threadId;
}
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
sendTextMessage(context, recipient, forceSms, messageId);
return allocatedThreadId;
}
public static long send(final Context context,
final OutgoingMediaMessage message,
final long threadId,
final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{
try {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = threadDatabase.getOrCreateThreadIdFor(message.getRecipient(), message.getDistributionType());
} else {
allocatedThreadId = threadId;
}
Recipient recipient = message.getRecipient();
long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn());
return allocatedThreadId;
} catch (MmsException e) {
Log.w(TAG, e);
return threadId;
}
}
public static void resend(Context context, MessageRecord messageRecord) {
long messageId = messageRecord.getId();
boolean forceSms = messageRecord.isForcedSms();
long expiresIn = messageRecord.getExpiresIn();
Recipient recipient = messageRecord.getRecipient();
if (messageRecord.isMms()) {
sendMediaMessage(context, recipient, forceSms, messageId, expiresIn);
} else {
sendTextMessage(context, recipient, forceSms, messageId);
}
}
private static void sendMediaMessage(Context context, Recipient recipient, boolean forceSms, long messageId, long expiresIn)
{
if (isLocalSelfSend(context, recipient, forceSms)) {
sendLocalMediaSelf(context, messageId);
} else if (isGroupPushSend(recipient)) {
sendGroupPush(context, recipient, messageId, null);
} else {
sendMediaPush(context, recipient, messageId);
}
}
private static void sendTextMessage(Context context, Recipient recipient,
boolean forceSms, long messageId)
{
if (isLocalSelfSend(context, recipient, forceSms)) {
sendLocalTextSelf(context, messageId);
} else {
sendTextPush(context, recipient, messageId);
}
}
private static void sendTextPush(Context context, Recipient recipient, long messageId) {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
}
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress());
}
private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), filterAddress);
}
private static boolean isGroupPushSend(Recipient recipient) {
return recipient.getAddress().isGroup() &&
!recipient.getAddress().isMmsGroup();
}
private static boolean isLocalSelfSend(@NonNull Context context, @NonNull Recipient recipient, boolean forceSms) {
return recipient.isLocalNumber() &&
!forceSms &&
TextSecurePreferences.isPushRegistered(context);
}
private static void sendLocalMediaSelf(Context context, long messageId) {
try {
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
OutgoingMediaMessage message = mmsDatabase.getOutgoingMessage(messageId);
SyncMessageId syncId = new SyncMessageId(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), message.getSentTimeMillis());
for (Attachment attachment : message.getAttachments()) {
attachmentDatabase.markAttachmentUploaded(messageId, attachment);
}
mmsDatabase.markAsSent(messageId, true);
mmsDatabase.markUnidentified(messageId, true);
mmsSmsDatabase.incrementDeliveryReceiptCount(syncId, System.currentTimeMillis());
mmsSmsDatabase.incrementReadReceiptCount(syncId, System.currentTimeMillis());
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
mmsDatabase.markExpireStarted(messageId);
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
}
} catch (NoSuchMessageException | MmsException e) {
Log.w("Failed to update self-sent message.", e);
}
}
private static void sendLocalTextSelf(Context context, long messageId) {
try {
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
SmsMessageRecord message = smsDatabase.getMessage(messageId);
SyncMessageId syncId = new SyncMessageId(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), message.getDateSent());
smsDatabase.markAsSent(messageId, true);
smsDatabase.markUnidentified(messageId, true);
mmsSmsDatabase.incrementDeliveryReceiptCount(syncId, System.currentTimeMillis());
mmsSmsDatabase.incrementReadReceiptCount(syncId, System.currentTimeMillis());
if (message.getExpiresIn() > 0) {
smsDatabase.markExpireStarted(messageId);
expirationManager.scheduleDeletion(message.getId(), message.isMms(), message.getExpiresIn());
}
} catch (NoSuchMessageException e) {
Log.w("Failed to update self-sent message.", e);
}
}
}

View File

@ -1,15 +0,0 @@
package org.thoughtcrime.securesms.sms;
import org.session.libsession.messaging.threads.recipients.Recipient;
public class OutgoingEncryptedMessage extends OutgoingTextMessage {
public OutgoingEncryptedMessage(Recipient recipient, String body, long expiresIn) {
super(recipient, body, expiresIn, -1);
}
@Override
public boolean isSecureMessage() {
return true;
}
}

View File

@ -51,6 +51,10 @@ public class TypingStatusRepository implements SSKEnvironment.TypingIndicatorsPr
return; return;
} }
if (Recipient.from(context, author, false).isBlocked()) {
return;
}
Set<Typist> typists = Util.getOrDefault(typistMap, threadId, new LinkedHashSet<>()); Set<Typist> typists = Util.getOrDefault(typistMap, threadId, new LinkedHashSet<>());
Typist typist = new Typist(Recipient.from(context, author, false), device, threadId); Typist typist = new Typist(Recipient.from(context, author, false), device, threadId);
@ -76,6 +80,10 @@ public class TypingStatusRepository implements SSKEnvironment.TypingIndicatorsPr
return; return;
} }
if (Recipient.from(context, author, false).isBlocked()) {
return;
}
Set<Typist> typists = Util.getOrDefault(typistMap, threadId, new LinkedHashSet<>()); Set<Typist> typists = Util.getOrDefault(typistMap, threadId, new LinkedHashSet<>());
Typist typist = new Typist(Recipient.from(context, author, false), device, threadId); Typist typist = new Typist(Recipient.from(context, author, false), device, threadId);

View File

@ -1,9 +0,0 @@
package org.thoughtcrime.securesms.transport;
public class RetryLaterException extends Exception {
public RetryLaterException() {}
public RetryLaterException(Exception e) {
super(e);
}
}

View File

@ -1,12 +0,0 @@
package org.thoughtcrime.securesms.transport;
public class UndeliverableMessageException extends Exception {
public UndeliverableMessageException(String detailMessage) {
super(detailMessage);
}
public UndeliverableMessageException(Throwable throwable) {
super(throwable);
}
}

View File

@ -7,35 +7,73 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.DocumentsContract import android.provider.DocumentsContract
import org.session.libsignal.utilities.logging.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import network.loki.messenger.R import network.loki.messenger.R
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.ByteUtil
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.backup.BackupEvent import org.thoughtcrime.securesms.backup.BackupEvent
import org.thoughtcrime.securesms.backup.BackupPassphrase import org.thoughtcrime.securesms.backup.BackupPassphrase
import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference
import org.thoughtcrime.securesms.backup.FullBackupExporter import org.thoughtcrime.securesms.backup.FullBackupExporter
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.database.BackupFileRecord import org.thoughtcrime.securesms.loki.database.BackupFileRecord
import org.thoughtcrime.securesms.service.LocalBackupListener import org.thoughtcrime.securesms.service.LocalBackupListener
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.ByteUtil
import java.io.IOException import java.io.IOException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.SecureRandom import java.security.SecureRandom
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.jvm.Throws
object BackupUtil { object BackupUtil {
private const val MASTER_SECRET_UTIL_PREFERENCES_NAME = "SecureSMS-Preferences"
private const val TAG = "BackupUtil" private const val TAG = "BackupUtil"
const val BACKUP_FILE_MIME_TYPE = "application/session-backup" const val BACKUP_FILE_MIME_TYPE = "application/session-backup"
const val BACKUP_PASSPHRASE_LENGTH = 30 const val BACKUP_PASSPHRASE_LENGTH = 30
fun getBackupRecords(context: Context): List<SharedPreference> {
val prefName = MASTER_SECRET_UTIL_PREFERENCES_NAME
val preferences = context.getSharedPreferences(prefName, 0)
val prefList = LinkedList<SharedPreference>()
prefList.add(SharedPreference.newBuilder()
.setFile(prefName)
.setKey(IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF)
.setValue(preferences.getString(IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF, null))
.build())
prefList.add(SharedPreference.newBuilder()
.setFile(prefName)
.setKey(IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF)
.setValue(preferences.getString(IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF, null))
.build())
if (preferences.contains(IdentityKeyUtil.ED25519_PUBLIC_KEY)) {
prefList.add(SharedPreference.newBuilder()
.setFile(prefName)
.setKey(IdentityKeyUtil.ED25519_PUBLIC_KEY)
.setValue(preferences.getString(IdentityKeyUtil.ED25519_PUBLIC_KEY, null))
.build())
}
if (preferences.contains(IdentityKeyUtil.ED25519_SECRET_KEY)) {
prefList.add(SharedPreference.newBuilder()
.setFile(prefName)
.setKey(IdentityKeyUtil.ED25519_SECRET_KEY)
.setValue(preferences.getString(IdentityKeyUtil.ED25519_SECRET_KEY, null))
.build())
}
prefList.add(SharedPreference.newBuilder()
.setFile(prefName)
.setKey(IdentityKeyUtil.LOKI_SEED)
.setValue(preferences.getString(IdentityKeyUtil.LOKI_SEED, null))
.build())
return prefList
}
/** /**
* Set app-wide configuration to enable the backups and schedule them. * Set app-wide configuration to enable the backups and schedule them.
* *

View File

@ -13,13 +13,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"> android:layout_gravity="center">
<org.thoughtcrime.securesms.components.location.SignalMapView
android:id="@+id/attachment_location"
android:layout_width="210dp"
android:layout_height="210dp"
android:layout_gravity="center_horizontal"
android:visibility="gone"/>
<org.thoughtcrime.securesms.components.ThumbnailView <org.thoughtcrime.securesms.components.ThumbnailView
android:id="@+id/attachment_thumbnail" android:id="@+id/attachment_thumbnail"
android:layout_width="230dp" android:layout_width="230dp"

View File

@ -1,10 +1,8 @@
package org.session.libsession.database package org.session.libsession.database
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream
import java.io.InputStream import java.io.InputStream
@ -14,10 +12,13 @@ interface MessageDataProvider {
fun getMessageID(serverID: Long): Long? fun getMessageID(serverID: Long): Long?
fun deleteMessage(messageID: Long) fun deleteMessage(messageID: Long)
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?
fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer?
fun getSignalAttachmentStream(attachmentId: Long): SignalServiceAttachmentStream? fun getSignalAttachmentStream(attachmentId: Long): SignalServiceAttachmentStream?
fun getScaledSignalAttachmentStream(attachmentId: Long): SignalServiceAttachmentStream?
fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer? fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer?
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
@ -26,12 +27,15 @@ interface MessageDataProvider {
fun isOutgoingMessage(timestamp: Long): Boolean fun isOutgoingMessage(timestamp: Long): Boolean
@Throws(Exception::class) fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult)
fun uploadAttachment(attachmentId: Long) fun updateAttachmentAfterUploadFailed(attachmentId: Long)
// Quotes // Quotes
fun getMessageForQuote(timestamp: Long, author: Address): Long? fun getMessageForQuote(timestamp: Long, author: Address): Long?
fun getAttachmentsAndLinkPreviewFor(messageID: Long): List<Attachment> fun getAttachmentsAndLinkPreviewFor(messageID: Long): List<Attachment>
fun getMessageBodyFor(messageID: Long): String fun getMessageBodyFor(messageID: Long): String
fun getAttachmentIDsFor(messageID: Long): List<Long>
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
} }

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.database.documents; package org.session.libsession.database.documents;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.database.documents; package org.session.libsession.database.documents;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.database.documents; package org.session.libsession.database.documents;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.database.documents; package org.session.libsession.database.documents;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.database.documents; package org.session.libsession.database.documents;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View File

@ -59,6 +59,8 @@ interface StorageProtocol {
fun getThreadID(openGroupID: String): String? fun getThreadID(openGroupID: String): String?
fun getAllOpenGroups(): Map<Long, PublicChat> fun getAllOpenGroups(): Map<Long, PublicChat>
fun addOpenGroup(server: String, channel: Long) fun addOpenGroup(server: String, channel: Long)
fun setOpenGroupServerMessageID(messageID: Long, serverID: Long)
fun getQuoteServerID(quoteID: Long, publicKey: String): Long?
// Open Group Public Keys // Open Group Public Keys
fun getOpenGroupPublicKey(server: String): String? fun getOpenGroupPublicKey(server: String): String?
@ -94,10 +96,9 @@ interface StorageProtocol {
fun persistAttachments(messageId: Long, attachments: List<Attachment>): List<Long> fun persistAttachments(messageId: Long, attachments: List<Attachment>): List<Long>
fun getMessageIdInDatabase(timestamp: Long, author: String): Long? fun getMessageIdInDatabase(timestamp: Long, author: String): Long?
fun setOpenGroupServerMessageID(messageID: Long, serverID: Long) fun markAsSent(timestamp: Long, author: String)
fun markAsSent(messageID: Long) fun markUnidentified(timestamp: Long, author: String)
fun markUnidentified(messageID: Long) fun setErrorMessage(timestamp: Long, author: String, error: Exception)
fun setErrorMessage(messageID: Long, error: Exception)
// Closed Groups // Closed Groups
fun getGroup(groupID: String): GroupRecord? fun getGroup(groupID: String): GroupRecord?
@ -112,9 +113,9 @@ interface StorageProtocol {
fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String)
fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String)
fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type, fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type,
name: String, members: Collection<String>, admins: Collection<String>) name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long)
fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String,
members: Collection<String>, admins: Collection<String>, threadID: Long) members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long)
fun isClosedGroup(publicKey: String): Boolean fun isClosedGroup(publicKey: String): Boolean
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair> fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair>
fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair?

View File

@ -1,40 +0,0 @@
package org.session.libsession.messaging.avatars;
import androidx.annotation.NonNull;
import org.session.libsession.utilities.color.MaterialColor;
/**
* Used for migrating legacy colors to modern colors. For normal color generation, use
* {@link ContactColors}.
*/
public class ContactColorsLegacy {
private static final String[] LEGACY_PALETTE = new String[] {
"red",
"pink",
"purple",
"deep_purple",
"indigo",
"blue",
"light_blue",
"cyan",
"teal",
"green",
"light_green",
"orange",
"deep_orange",
"amber",
"blue_grey"
};
public static MaterialColor generateFor(@NonNull String name) {
String serialized = LEGACY_PALETTE[Math.abs(name.hashCode()) % LEGACY_PALETTE.length];
try {
return MaterialColor.fromSerialized(serialized);
} catch (MaterialColor.UnknownColorException e) {
return ContactColors.generateFor(name);
}
}
}

View File

@ -29,7 +29,6 @@ class FileServerAPI(public val server: String, userPublicKey: String, userPrivat
* possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. * possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
*/ */
public val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5? public val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5?
val server = "https://file.getsession.org"
public val fileStorageBucketURL = "https://file-static.lokinet.org" public val fileStorageBucketURL = "https://file-static.lokinet.org"
// endregion // endregion

View File

@ -17,7 +17,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024 private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
// Error // Error
internal sealed class Error(val description: String) : Exception() { internal sealed class Error(val description: String) : Exception(description) {
object NoAttachment : Error("No such attachment.") object NoAttachment : Error("No such attachment.")
} }

View File

@ -8,11 +8,15 @@ import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream
import org.session.libsignal.service.internal.crypto.PaddingInputStream
import org.session.libsignal.service.internal.push.PushAttachmentData import org.session.libsignal.service.internal.push.PushAttachmentData
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory
import org.session.libsignal.service.internal.util.Util import org.session.libsignal.service.internal.util.Util
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory
import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.utilities.logging.Log
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job { class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
@ -21,14 +25,14 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
override var failureCount: Int = 0 override var failureCount: Int = 0
// Error // Error
internal sealed class Error(val description: String) : Exception() { internal sealed class Error(val description: String) : Exception(description) {
object NoAttachment : Error("No such attachment.") object NoAttachment : Error("No such attachment.")
} }
// Settings // Settings
override val maxFailureCount: Int = 20 override val maxFailureCount: Int = 20
companion object { companion object {
val TAG = AttachmentUploadJob::class.qualifiedName val TAG = AttachmentUploadJob::class.simpleName
val KEY: String = "AttachmentUploadJob" val KEY: String = "AttachmentUploadJob"
val maxFailureCount: Int = 20 val maxFailureCount: Int = 20
@ -41,24 +45,30 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
override fun execute() { override fun execute() {
ThreadUtils.queue {
try { try {
val attachmentStream = MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(attachmentID) val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID)
?: return handleFailure(Error.NoAttachment) ?: return@queue handleFailure(Error.NoAttachment)
var server = FileServerAPI.shared.server
var shouldEncrypt = true
val usePadding = false
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
val server = openGroup?.server ?: FileServerAPI.server openGroup?.let {
server = it.server
//TODO add some encryption stuff here shouldEncrypt = false
val isEncryptionRequired = false }
//val isEncryptionRequired = (server == FileServerAPI.server)
val attachmentKey = Util.getSecretBytes(64) val attachmentKey = Util.getSecretBytes(64)
val outputStreamFactory = if (isEncryptionRequired) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory() val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
val ciphertextLength = attachmentStream.length val dataStream = if (usePadding) PaddingInputStream(attachment.inputStream, attachment.length) else attachment.inputStream
val ciphertextLength = if (shouldEncrypt) AttachmentCipherOutputStream.getCiphertextLength(paddedLength) else attachment.length
val attachmentData = PushAttachmentData(attachmentStream.contentType, attachmentStream.inputStream, ciphertextLength, outputStreamFactory, attachmentStream.listener) val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory()
val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener)
FileServerAPI.shared.uploadAttachment(server, attachmentData) val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData)
handleSuccess(attachment, attachmentKey, uploadResult)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
if (e is Error && e == Error.NoAttachment) { if (e is Error && e == Error.NoAttachment) {
@ -70,17 +80,19 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
} }
} }
}
private fun handleSuccess() { private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
Log.w(TAG, "Attachment uploaded successfully.") Log.w(TAG, "Attachment uploaded successfully.")
delegate?.handleJobSucceeded(this) delegate?.handleJobSucceeded(this)
MessagingConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadSucceeded(attachmentID, attachment, attachmentKey, uploadResult)
MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID) MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
//TODO interaction stuff, not sure how to deal with that
} }
private fun handlePermanentFailure(e: Exception) { private fun handlePermanentFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed permanently due to error: $this.") Log.w(TAG, "Attachment upload failed permanently due to error: $this.")
delegate?.handleJobFailedPermanently(this, e) delegate?.handleJobFailedPermanently(this, e)
MessagingConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadFailed(attachmentID)
failAssociatedMessageSendJob(e) failAssociatedMessageSendJob(e)
} }
@ -95,7 +107,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
private fun failAssociatedMessageSendJob(e: Exception) { private fun failAssociatedMessageSendJob(e: Exception) {
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
val messageSendJob = storage.getMessageSendJob(messageSendJobID) val messageSendJob = storage.getMessageSendJob(messageSendJobID)
MessageSender.handleFailedMessageSend(this.message!!, e) MessageSender.handleFailedMessageSend(this.message, e)
if (messageSendJob != null) { if (messageSendJob != null) {
storage.markJobAsFailed(messageSendJob) storage.markJobAsFailed(messageSendJob)
} }
@ -119,7 +131,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
return AttachmentDownloadJob.KEY return KEY
} }
class Factory: Job.Factory<AttachmentUploadJob> { class Factory: Job.Factory<AttachmentUploadJob> {

View File

@ -15,7 +15,8 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
// Settings // Settings
override val maxFailureCount: Int = 10 override val maxFailureCount: Int = 10
companion object { companion object {
val TAG = MessageReceiveJob::class.qualifiedName
val TAG = MessageReceiveJob::class.simpleName
val KEY: String = "MessageReceiveJob" val KEY: String = "MessageReceiveJob"
//keys used for database storage purpose //keys used for database storage purpose
@ -75,7 +76,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
return AttachmentDownloadJob.KEY return KEY
} }
class Factory: Job.Factory<MessageReceiveJob> { class Factory: Job.Factory<MessageReceiveJob> {

View File

@ -19,7 +19,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
// Settings // Settings
override val maxFailureCount: Int = 10 override val maxFailureCount: Int = 10
companion object { companion object {
val TAG = MessageSendJob::class.qualifiedName val TAG = MessageSendJob::class.simpleName
val KEY: String = "MessageSendJob" val KEY: String = "MessageSendJob"
//keys used for database storage purpose //keys used for database storage purpose
@ -32,13 +32,17 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val message = message as? VisibleMessage val message = message as? VisibleMessage
message?.let { message?.let {
if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
val attachments = message.attachmentIDs.map { messageDataProvider.getAttachmentStream(it) }.filterNotNull() val attachmentIDs = mutableListOf<Long>()
val attachmentsToUpload = attachments.filter { !it.isUploaded } attachmentIDs.addAll(message.attachmentIDs)
message.quote?.let { it.attachmentID?.let { attachmentID -> attachmentIDs.add(attachmentID) } }
message.linkPreview?.let { it.attachmentID?.let { attachmentID -> attachmentIDs.add(attachmentID) } }
val attachments = attachmentIDs.mapNotNull { messageDataProvider.getDatabaseAttachment(it) }
val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() }
attachmentsToUpload.forEach { attachmentsToUpload.forEach {
if(MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId) != null) { if (MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) {
// Wait for it to finish // Wait for it to finish
} else { } else {
val job = AttachmentUploadJob(it.attachmentId, message.threadID!!.toString(), message, id!!) val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!)
JobQueue.shared.add(job) JobQueue.shared.add(job)
} }
} }
@ -79,21 +83,21 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
//serialize Message and Destination properties //serialize Message and Destination properties
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096) val output = Output(ByteArray(4096), -1) // maxBufferSize '-1' will dynamically grow internally if we run out of room serializing the message
val serializedDestination = ByteArray(4096) kryo.writeClassAndObject(output, message)
var output = Output(serializedMessage)
kryo.writeObject(output, message)
output.close() output.close()
output = Output(serializedDestination) val serializedMessage = output.toBytes()
kryo.writeObject(output, destination) output.clear()
kryo.writeClassAndObject(output, destination)
output.close() output.close()
val serializedDestination = output.toBytes()
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage) return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage)
.putByteArray(KEY_DESTINATION, serializedDestination) .putByteArray(KEY_DESTINATION, serializedDestination)
.build(); .build();
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
return AttachmentDownloadJob.KEY return KEY
} }
class Factory: Job.Factory<MessageSendJob> { class Factory: Job.Factory<MessageSendJob> {
@ -103,10 +107,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
//deserialize Message and Destination properties //deserialize Message and Destination properties
val kryo = Kryo() val kryo = Kryo()
var input = Input(serializedMessage) var input = Input(serializedMessage)
val message: Message = kryo.readObject(input, Message::class.java) val message = kryo.readClassAndObject(input) as Message
input.close() input.close()
input = Input(serializedDestination) input = Input(serializedDestination)
val destination: Destination = kryo.readObject(input, Destination::class.java) val destination = kryo.readClassAndObject(input) as Destination
input.close() input.close()
return MessageSendJob(message, destination) return MessageSendJob(message, destination)
} }

View File

@ -76,7 +76,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
return AttachmentDownloadJob.KEY return KEY
} }
class Factory: Job.Factory<NotifyPNServerJob> { class Factory: Job.Factory<NotifyPNServerJob> {

Some files were not shown because too many files have changed in this diff Show More