mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-12 07:27:43 +00:00
Merge branch 'dev' of https://github.com/loki-project/session-android into data-extraction-2
This commit is contained in:
@@ -21,9 +21,9 @@ import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
@@ -32,32 +32,31 @@ import androidx.multidex.MultiDexApplication;
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.avatars.AvatarHelper;
|
||||
import org.session.libsession.snode.SnodeConfiguration;
|
||||
import org.session.libsession.utilities.SSKEnvironment;
|
||||
import org.session.libsession.messaging.jobs.JobQueue;
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller;
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.Poller;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.snode.SnodeConfiguration;
|
||||
import org.session.libsession.utilities.IdentityKeyUtil;
|
||||
import org.session.libsession.utilities.SSKEnvironment;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
||||
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
|
||||
import org.session.libsignal.service.api.util.StreamDetails;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos;
|
||||
import org.session.libsignal.service.loki.api.Poller;
|
||||
import org.session.libsignal.service.loki.api.PushNotificationAPI;
|
||||
import org.session.libsignal.service.loki.api.SnodeAPI;
|
||||
import org.session.libsignal.service.loki.api.SwarmAPI;
|
||||
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.session.libsession.utilities.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
|
||||
import org.thoughtcrime.securesms.jobmanager.DependencyInjector;
|
||||
@@ -65,13 +64,11 @@ import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
|
||||
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
||||
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
|
||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
|
||||
import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller;
|
||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
|
||||
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
||||
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
||||
@@ -142,10 +139,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
public Poller poller = null;
|
||||
public ClosedGroupPoller closedGroupPoller = null;
|
||||
public PublicChatManager publicChatManager = null;
|
||||
private PublicChatAPI publicChatAPI = null;
|
||||
public Broadcaster broadcaster = null;
|
||||
public SignalCommunicationModule communicationModule;
|
||||
private Job firebaseInstanceIdJob;
|
||||
private Handler threadNotificationHandler;
|
||||
|
||||
private volatile boolean isAppVisible;
|
||||
|
||||
@@ -153,7 +150,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
return (ApplicationContext) context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Handler getThreadNotificationHandler() {
|
||||
return this.threadNotificationHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.i(TAG, "onCreate()");
|
||||
@@ -168,6 +169,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
// ========
|
||||
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
|
||||
broadcaster = new Broadcaster(this);
|
||||
threadNotificationHandler = new Handler(Looper.getMainLooper());
|
||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
|
||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||
@@ -193,16 +195,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
// Set application UI mode (day/night theme) to the user selected one.
|
||||
UiModeUtilities.setupUiModeToUserSelected(this);
|
||||
// ========
|
||||
initializeJobManager();
|
||||
initializeExpiringMessageManager();
|
||||
initializeTypingStatusRepository();
|
||||
initializeTypingStatusSender();
|
||||
initializeReadReceiptManager();
|
||||
initializeProfileManager();
|
||||
initializePeriodicTasks();
|
||||
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
|
||||
initializeJobManager();
|
||||
initializeWebRtc();
|
||||
initializeBlobProvider();
|
||||
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,14 +234,14 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
if (closedGroupPoller != null) {
|
||||
closedGroupPoller.stopIfNeeded();
|
||||
}
|
||||
if (publicChatManager != null) {
|
||||
publicChatManager.stopPollers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
stopKovenant(); // Loki
|
||||
if (publicChatManager != null) {
|
||||
publicChatManager.stopPollers();
|
||||
}
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
@@ -287,22 +289,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
}
|
||||
|
||||
// Loki
|
||||
public @Nullable
|
||||
PublicChatAPI getPublicChatAPI() {
|
||||
if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) {
|
||||
return publicChatAPI;
|
||||
}
|
||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
if (userPublicKey == null) {
|
||||
return publicChatAPI;
|
||||
}
|
||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||
GroupDatabase groupDB = DatabaseFactory.getGroupDatabase(this);
|
||||
publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB, groupDB);
|
||||
return publicChatAPI;
|
||||
}
|
||||
|
||||
private void initializeSecurityProvider() {
|
||||
try {
|
||||
@@ -347,6 +333,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
.setJobStorage(new FastJobStorage(DatabaseFactory.getJobDatabase(this)))
|
||||
.setDependencyInjector(this)
|
||||
.build());
|
||||
JobQueue.getShared().resumePendingJobs();
|
||||
}
|
||||
|
||||
private void initializeDependencyInjection() {
|
||||
@@ -478,17 +465,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
return;
|
||||
}
|
||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||
Context context = this;
|
||||
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||
poller = new Poller(userPublicKey, apiDB, envelopes -> {
|
||||
for (SignalServiceProtos.Envelope envelope : envelopes) {
|
||||
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(envelope), false);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
ClosedGroupPoller.Companion.configureIfNeeded(this);
|
||||
closedGroupPoller = ClosedGroupPoller.Companion.getShared();
|
||||
poller = new Poller();
|
||||
closedGroupPoller = new ClosedGroupPoller();
|
||||
}
|
||||
|
||||
public void startPollingIfNeeded() {
|
||||
@@ -539,21 +519,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
|
||||
public void updateOpenGroupProfilePicturesIfNeeded() {
|
||||
AsyncTask.execute(() -> {
|
||||
PublicChatAPI publicChatAPI = null;
|
||||
try {
|
||||
publicChatAPI = getPublicChatAPI();
|
||||
} catch (Exception e) {
|
||||
// Do nothing
|
||||
}
|
||||
if (publicChatAPI == null) {
|
||||
return;
|
||||
}
|
||||
byte[] profileKey = ProfileKeyUtil.getProfileKey(this);
|
||||
String url = TextSecurePreferences.getProfilePictureURL(this);
|
||||
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers();
|
||||
for (String server : servers) {
|
||||
if (profileKey != null) {
|
||||
publicChatAPI.setProfilePicture(server, profileKey, url);
|
||||
OpenGroupAPI.setProfilePicture(server, profileKey, url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -18,9 +18,6 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
@@ -30,16 +27,6 @@ import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -52,19 +39,29 @@ import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.session.libsession.messaging.messages.control.DataExtractionNotification;
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.MediaView;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
@@ -314,6 +311,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
private int cleanupMedia() {
|
||||
int restartItem = mediaPager.getCurrentItem();
|
||||
|
||||
PagerAdapter adapter = mediaPager.getAdapter();
|
||||
if (adapter instanceof CursorPagerAdapter) {
|
||||
((CursorPagerAdapter)adapter).cursor.close();
|
||||
}
|
||||
|
||||
mediaPager.removeAllViews();
|
||||
mediaPager.setAdapter(null);
|
||||
|
||||
|
@@ -66,19 +66,19 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value)
|
||||
}
|
||||
|
||||
override fun getMessageForQuote(timestamp: Long, author: Address): Long? {
|
||||
override fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>? {
|
||||
val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context)
|
||||
return messagingDatabase.getMessageFor(timestamp, author)?.id
|
||||
val message = messagingDatabase.getMessageFor(timestamp, author)
|
||||
return if (message != null) Pair(message.id, message.isMms) else null
|
||||
}
|
||||
|
||||
override fun getAttachmentsAndLinkPreviewFor(messageID: Long): List<Attachment> {
|
||||
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
|
||||
return attachmentDatabase.getAttachmentsForMessage(messageID)
|
||||
override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment> {
|
||||
return DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(mmsId)
|
||||
}
|
||||
|
||||
override fun getMessageBodyFor(messageID: Long): String {
|
||||
val messagingDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||
return messagingDatabase.getMessage(messageID).body
|
||||
override fun getMessageBodyFor(timestamp: Long, author: String): String {
|
||||
val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context)
|
||||
return messagingDatabase.getMessageFor(timestamp, author)!!.body
|
||||
}
|
||||
|
||||
override fun getAttachmentIDsFor(messageID: Long): List<Long> {
|
||||
@@ -93,9 +93,9 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
return message.linkPreviews.firstOrNull()?.attachmentId?.rowId
|
||||
}
|
||||
|
||||
override fun insertAttachment(messageId: Long, attachmentId: Long, stream: InputStream) {
|
||||
override fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream: InputStream) {
|
||||
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
|
||||
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId, 0), stream)
|
||||
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream)
|
||||
}
|
||||
|
||||
override fun isOutgoingMessage(timestamp: Long): Boolean {
|
||||
@@ -190,6 +190,10 @@ fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer {
|
||||
return SessionServiceAttachmentPointer(attachmentId.rowId, contentType, key?.toByteArray(), Optional.fromNullable(size.toInt()), Optional.absent(), width, height, Optional.fromNullable(digest), Optional.fromNullable(fileName), isVoiceNote, Optional.fromNullable(caption), url)
|
||||
}
|
||||
|
||||
fun SessionServiceAttachmentPointer.toSignalPointer(): SignalServiceAttachmentPointer {
|
||||
return SignalServiceAttachmentPointer(id,contentType,key?.toByteArray() ?: byteArrayOf(), size, preview, width, height, digest, fileName, voiceNote, caption, url)
|
||||
}
|
||||
|
||||
fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttachmentStream {
|
||||
val stream = PartAuthority.getAttachmentStream(context, this.dataUri!!)
|
||||
val listener = SignalServiceAttachment.ProgressListener { total: Long, progress: Long -> EventBus.getDefault().postSticky(PartProgressEvent(this, total, progress))}
|
||||
|
@@ -806,7 +806,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime);
|
||||
ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime);
|
||||
ExpirationTimerUpdate message = new ExpirationTimerUpdate(null, expirationTime);
|
||||
message.setSentTimestamp(System.currentTimeMillis());
|
||||
OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient);
|
||||
try {
|
||||
@@ -1011,7 +1011,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
try {
|
||||
if (isClosedGroup) {
|
||||
MessageSender.explicitLeave(groupPublicKey);
|
||||
MessageSender.explicitLeave(groupPublicKey, true);
|
||||
initializeEnabledCheck();
|
||||
} else {
|
||||
Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
|
||||
|
@@ -52,11 +52,21 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
|
||||
import org.session.libsession.utilities.GroupUtil;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.ViewUtil;
|
||||
import org.session.libsession.utilities.views.Stub;
|
||||
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.PublicChatAPI;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.BindableConversationItem;
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
@@ -69,7 +79,6 @@ import org.thoughtcrime.securesms.components.LinkPreviewView;
|
||||
import org.thoughtcrime.securesms.components.QuoteView;
|
||||
import org.thoughtcrime.securesms.components.StickerView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
@@ -78,7 +87,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Quote;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.loki.views.MessageAudioView;
|
||||
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
|
||||
@@ -89,22 +97,11 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.SearchUtil;
|
||||
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.GroupUtil;
|
||||
import org.session.libsession.utilities.ViewUtil;
|
||||
import org.session.libsession.utilities.views.Stub;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -916,7 +913,7 @@ public class ConversationItem extends LinearLayout
|
||||
|
||||
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
|
||||
if (publicChat != null) {
|
||||
boolean isModerator = PublicChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
|
||||
boolean isModerator = OpenGroupAPI.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
|
||||
visibility = isModerator ? View.VISIBLE : View.GONE;
|
||||
}
|
||||
|
||||
|
@@ -37,32 +37,26 @@ import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
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.AttachmentTransferProgress;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras;
|
||||
import org.session.libsession.utilities.MediaTypes;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsignal.utilities.JsonUtil;
|
||||
import org.session.libsignal.utilities.externalstorage.ExternalStorageUtil;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
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.DatabaseAttachmentAudioExtras;
|
||||
|
||||
import org.session.libsignal.utilities.JsonUtil;
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.MediaStream;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.session.libsignal.utilities.externalstorage.ExternalStorageUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
|
||||
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
|
||||
@@ -240,7 +234,11 @@ public class AttachmentDatabase extends Database {
|
||||
null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.addAll(getAttachment(cursor));
|
||||
List<DatabaseAttachment> attachments = getAttachment(cursor);
|
||||
for (DatabaseAttachment attachment : attachments) {
|
||||
if (attachment.isQuote()) continue;
|
||||
results.add(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
|
@@ -16,11 +16,15 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.utilities.Debouncer;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -31,10 +35,13 @@ public abstract class Database {
|
||||
|
||||
protected SQLCipherOpenHelper databaseHelper;
|
||||
protected final Context context;
|
||||
private final Debouncer threadNotificationDebouncer;
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
public Database(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
this.context = context;
|
||||
this.databaseHelper = databaseHelper;
|
||||
this.threadNotificationDebouncer = new Debouncer(ApplicationContext.getInstance(context).getThreadNotificationHandler(), 100);
|
||||
}
|
||||
|
||||
protected void notifyConversationListeners(Set<Long> threadIds) {
|
||||
@@ -47,7 +54,7 @@ public abstract class Database {
|
||||
}
|
||||
|
||||
protected void notifyConversationListListeners() {
|
||||
context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null);
|
||||
threadNotificationDebouncer.publish(()->context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null));
|
||||
}
|
||||
|
||||
protected void notifyStickerListeners() {
|
||||
|
@@ -518,7 +518,9 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
||||
}
|
||||
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
||||
boolean expirationTimer = (outboxType & Types.EXPIRATION_TIMER_UPDATE_BIT) != 0;
|
||||
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, expirationTimer, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
||||
|
||||
if (Types.isSecureType(outboxType)) {
|
||||
return new OutgoingSecureMediaMessage(message);
|
||||
@@ -774,6 +776,11 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
quoteAttachments.addAll(message.getOutgoingQuote().getAttachments());
|
||||
}
|
||||
|
||||
if (isDuplicate(message, threadId)) {
|
||||
Log.w(TAG, "Ignoring duplicate media message (" + message.getSentTimeMillis() + ")");
|
||||
return -1;
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), message.getLinkPreviews(), contentValues, insertListener);
|
||||
|
||||
if (message.getRecipient().getAddress().isGroup()) {
|
||||
@@ -945,6 +952,19 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDuplicate(OutgoingMediaMessage message, long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
|
||||
new String[]{String.valueOf(message.getSentTimeMillis()), message.getRecipient().getAddress().serialize(), String.valueOf(threadId)},
|
||||
null, null, null, "1");
|
||||
|
||||
try {
|
||||
return cursor != null && cursor.moveToFirst();
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSent(long messageId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
try (Cursor cursor = database.query(TABLE_NAME, new String[] { MESSAGE_BOX }, ID + " = ?", new String[] { String.valueOf(messageId)}, null, null, null)) {
|
||||
|
@@ -18,19 +18,19 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteQueryBuilder;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -79,18 +79,16 @@ public class MmsSmsDatabase extends Database {
|
||||
}
|
||||
|
||||
public @Nullable MessageRecord getMessageForTimestamp(long timestamp) {
|
||||
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) {
|
||||
MmsSmsDatabase.Reader reader = db.readerFor(cursor);
|
||||
MmsSmsDatabase.Reader reader = readerFor(cursor);
|
||||
return reader.getNext();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) {
|
||||
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
|
||||
try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) {
|
||||
MmsSmsDatabase.Reader reader = db.readerFor(cursor);
|
||||
MmsSmsDatabase.Reader reader = readerFor(cursor);
|
||||
|
||||
MessageRecord messageRecord;
|
||||
|
||||
|
@@ -456,7 +456,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(THREAD_ID, threadId);
|
||||
contentValues.put(BODY, message.getMessageBody());
|
||||
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
|
||||
contentValues.put(DATE_SENT, date);
|
||||
contentValues.put(DATE_SENT, message.getSentTimestampMillis());
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(TYPE, type);
|
||||
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
||||
@@ -464,6 +464,11 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
|
||||
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
|
||||
|
||||
if (isDuplicate(message, threadId)) {
|
||||
Log.w(TAG, "Duplicate message (" + message.getSentTimestampMillis() + "), ignoring...");
|
||||
return -1;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues);
|
||||
if (insertListener != null) {
|
||||
@@ -530,6 +535,19 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDuplicate(OutgoingTextMessage message, long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
|
||||
new String[]{String.valueOf(message.getSentTimestampMillis()), message.getRecipient().getAddress().serialize(), String.valueOf(threadId)},
|
||||
null, null, null, "1");
|
||||
|
||||
try {
|
||||
return cursor != null && cursor.moveToFirst();
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/*package */void deleteThread(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
|
||||
|
@@ -8,12 +8,14 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
||||
import org.session.libsession.messaging.jobs.Job
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.signal.*
|
||||
import org.session.libsession.messaging.messages.signal.IncomingTextMessage
|
||||
import org.session.libsession.messaging.messages.visible.Attachment
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||
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.DatabaseAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
@@ -24,27 +26,22 @@ import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.IdentityKeyUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.preferences.ProfileKeyUtil
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.libsignal.util.KeyHelper
|
||||
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.SignalServiceGroup
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol
|
||||
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
|
||||
import org.thoughtcrime.securesms.loki.utilities.get
|
||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
|
||||
import org.session.libsession.messaging.messages.signal.IncomingTextMessage
|
||||
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 {
|
||||
override fun getUserPublicKey(): String? {
|
||||
@@ -73,12 +70,27 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
return TextSecurePreferences.getProfilePictureURL(context)
|
||||
}
|
||||
|
||||
override fun setUserProfilePictureUrl(newProfilePicture: String) {
|
||||
val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let {
|
||||
Recipient.from(context, it, false)
|
||||
}
|
||||
TextSecurePreferences.setProfilePictureURL(context, newProfilePicture)
|
||||
RetrieveProfileAvatarJob(ourRecipient, newProfilePicture)
|
||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newProfilePicture))
|
||||
}
|
||||
|
||||
override fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? {
|
||||
val address = Address.fromSerialized(recipientPublicKey)
|
||||
val recipient = Recipient.from(context, address, false)
|
||||
return recipient.profileKey
|
||||
}
|
||||
|
||||
override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) {
|
||||
val address = Address.fromSerialized(recipientPublicKey)
|
||||
val recipient = Recipient.from(context, address, false)
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileKey(recipient, profileKey)
|
||||
}
|
||||
|
||||
override fun getOrGenerateRegistrationID(): Int {
|
||||
var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
|
||||
if (registrationID == 0) {
|
||||
@@ -94,54 +106,52 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
return database.insertAttachments(messageId, databaseAttachments)
|
||||
}
|
||||
|
||||
override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?): Long? {
|
||||
override fun getAttachmentsForMessage(messageId: Long): List<DatabaseAttachment> {
|
||||
val database = DatabaseFactory.getAttachmentDatabase(context)
|
||||
return database.getAttachmentsForMessage(messageId)
|
||||
}
|
||||
|
||||
override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long? {
|
||||
var messageID: Long? = null
|
||||
val senderAddress = Address.fromSerialized(message.sender!!)
|
||||
val senderRecipient = Recipient.from(context, senderAddress, false)
|
||||
var group: Optional<SignalServiceGroup> = Optional.absent()
|
||||
if (openGroupID != null) {
|
||||
group = Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT))
|
||||
} else if (groupPublicKey != null) {
|
||||
group = Optional.of(SignalServiceGroup(groupPublicKey.toByteArray(), SignalServiceGroup.GroupType.SIGNAL))
|
||||
val isUserSender = message.sender!! == getUserPublicKey()
|
||||
val group: Optional<SignalServiceGroup> = when {
|
||||
openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT))
|
||||
groupPublicKey != null -> {
|
||||
val doubleEncoded = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
Optional.of(SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(doubleEncoded), SignalServiceGroup.GroupType.SIGNAL))
|
||||
}
|
||||
else -> Optional.absent()
|
||||
}
|
||||
if (message.isMediaMessage()) {
|
||||
val pointerAttachments = attachments.mapNotNull {
|
||||
it.toSignalAttachment()
|
||||
}
|
||||
val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) {
|
||||
Address.fromSerialized(message.syncTarget!!)
|
||||
} else if (group.isPresent) {
|
||||
Address.fromSerialized(GroupUtil.getEncodedId(group.get()))
|
||||
} else {
|
||||
senderAddress
|
||||
}
|
||||
val targetRecipient = Recipient.from(context, targetAddress, false)
|
||||
|
||||
if (message.isMediaMessage() || attachments.isNotEmpty()) {
|
||||
val quote: Optional<QuoteModel> = if (quotes != null) Optional.of(quotes) else Optional.absent()
|
||||
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
|
||||
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
||||
mmsDatabase.beginTransaction()
|
||||
val insertResult = if (message.sender == getUserPublicKey()) {
|
||||
val targetAddress = if (message.syncTarget != null) {
|
||||
Address.fromSerialized(message.syncTarget!!)
|
||||
} else {
|
||||
if (group.isPresent) {
|
||||
Address.fromSerialized(GroupUtil.getEncodedId(group.get()))
|
||||
} else {
|
||||
Log.d("Loki", "Cannot handle message from self.")
|
||||
return null
|
||||
}
|
||||
}
|
||||
val attachments = message.attachmentIDs.mapNotNull {
|
||||
DatabaseFactory.getAttachmentProvider(context).getSignalAttachmentPointer(it)
|
||||
}.mapNotNull {
|
||||
PointerAttachment.forPointer(Optional.of(it)).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!!)
|
||||
val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull())
|
||||
mmsDatabase.beginTransaction()
|
||||
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!)
|
||||
} else {
|
||||
// It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment
|
||||
val attachments: Optional<List<SignalServiceAttachment>> = Optional.of(message.attachmentIDs.mapNotNull {
|
||||
DatabaseFactory.getAttachmentProvider(context).getSignalAttachmentPointer(it)
|
||||
})
|
||||
// FIXME deal with DataExtraction parameter
|
||||
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, attachments, quote, linkPreviews, Optional.absent())
|
||||
if (group.isPresent) {
|
||||
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID
|
||||
?: -1, message.sentTimestamp!!)
|
||||
} else {
|
||||
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID
|
||||
?: -1)
|
||||
val signalServiceAttachments = attachments.mapNotNull {
|
||||
it.toSignalPointer()
|
||||
}
|
||||
//TODO deal with data extraction instead of Optional.absent()
|
||||
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews, Optional.absent())
|
||||
mmsDatabase.beginTransaction()
|
||||
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0)
|
||||
}
|
||||
if (insertResult.isPresent) {
|
||||
mmsDatabase.setTransactionSuccessful()
|
||||
@@ -151,29 +161,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
} else {
|
||||
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||
val insertResult = if (message.sender == getUserPublicKey()) {
|
||||
val targetAddress = if (message.syncTarget != null) {
|
||||
Address.fromSerialized(message.syncTarget!!)
|
||||
} else {
|
||||
if (group.isPresent) {
|
||||
Address.fromSerialized(GroupUtil.getEncodedId(group.get()))
|
||||
} else {
|
||||
Log.d("Loki", "Cannot handle message from self.")
|
||||
return null
|
||||
}
|
||||
}
|
||||
val textMessage = OutgoingTextMessage.from(message, Recipient.from(context, targetAddress, false))
|
||||
smsDatabase.insertMessageOutbox(message.threadID
|
||||
?: -1, textMessage, message.sentTimestamp!!)
|
||||
val textMessage = OutgoingTextMessage.from(message, targetRecipient)
|
||||
smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!)
|
||||
} else {
|
||||
val textMessage = IncomingTextMessage.from(message, senderAddress, group, senderRecipient.expireMessages * 1000L)
|
||||
if (group.isPresent) {
|
||||
smsDatabase.insertMessageInbox(textMessage, message.sentTimestamp!!)
|
||||
} else {
|
||||
smsDatabase.insertMessageInbox(textMessage)
|
||||
}
|
||||
val textMessage = IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L)
|
||||
val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody)
|
||||
smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0)
|
||||
}
|
||||
if (insertResult.isPresent) {
|
||||
messageID = insertResult.get().messageId
|
||||
insertResult.orNull()?.let { result ->
|
||||
messageID = result.messageId
|
||||
}
|
||||
}
|
||||
return messageID
|
||||
@@ -206,8 +202,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
|
||||
val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return
|
||||
job.delegate = JobQueue.shared
|
||||
job.execute()
|
||||
JobQueue.shared.add(job)
|
||||
}
|
||||
|
||||
override fun isJobCanceled(job: Job): Boolean {
|
||||
@@ -286,12 +281,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
}
|
||||
|
||||
override fun isMessageDuplicated(timestamp: Long, sender: String): Boolean {
|
||||
val database = DatabaseFactory.getMmsSmsDatabase(context)
|
||||
return if (sender.isEmpty()) {
|
||||
database.getMessageForTimestamp(timestamp) != null
|
||||
} else {
|
||||
database.getMessageFor(timestamp, sender) != null
|
||||
}
|
||||
return getReceivedMessageTimestamps().contains(timestamp)
|
||||
}
|
||||
|
||||
override fun setUserCount(group: Long, server: String, newValue: Int) {
|
||||
@@ -390,6 +380,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseFactory.getGroupDatabase(context).create(groupId, title, members, avatar, relay, admins, formationTimestamp)
|
||||
}
|
||||
|
||||
override fun isGroupActive(groupPublicKey: String): Boolean {
|
||||
return DatabaseFactory.getGroupDatabase(context).getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true
|
||||
}
|
||||
|
||||
override fun setActive(groupID: String, value: Boolean) {
|
||||
DatabaseFactory.getGroupDatabase(context).setActive(groupID, value)
|
||||
}
|
||||
@@ -451,6 +445,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
|
||||
}
|
||||
|
||||
override fun getAllActiveClosedGroupPublicKeys(): Set<String> {
|
||||
return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys().filter {
|
||||
getGroup(GroupUtil.doubleEncodeGroupID(it))?.isActive == true
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
override fun addClosedGroupPublicKey(groupPublicKey: String) {
|
||||
DatabaseFactory.getLokiAPIDatabase(context).addClosedGroupPublicKey(groupPublicKey)
|
||||
}
|
||||
@@ -467,8 +467,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
||||
}
|
||||
|
||||
override fun getAllOpenGroups(): Map<Long, PublicChat> {
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats()
|
||||
override fun getAllOpenGroups(): Map<Long, OpenGroup> {
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().mapValues { (_,chat)->
|
||||
OpenGroup(chat.channel, chat.server, chat.displayName, chat.isDeletable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addOpenGroup(server: String, channel: Long) {
|
||||
@@ -492,10 +494,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long {
|
||||
val database = DatabaseFactory.getThreadDatabase(context)
|
||||
if (!openGroupID.isNullOrEmpty()) {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(openGroupID), false)
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
|
||||
return database.getOrCreateThreadIdFor(recipient)
|
||||
} else if (!groupPublicKey.isNullOrEmpty()) {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(groupPublicKey), false)
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
|
||||
return database.getOrCreateThreadIdFor(recipient)
|
||||
} else {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||
@@ -529,6 +531,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
|
||||
}
|
||||
|
||||
override fun setDisplayName(publicKey: String, newName: String) {
|
||||
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName)
|
||||
}
|
||||
|
||||
override fun getServerDisplayName(serverID: String, publicKey: String): String? {
|
||||
return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverID, publicKey)
|
||||
}
|
||||
@@ -542,6 +548,31 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
return if (recipientSettings.isPresent) { recipientSettings.get() } else null
|
||||
}
|
||||
|
||||
override fun addContacts(contacts: List<ConfigurationMessage.Contact>) {
|
||||
val recipientDatabase = DatabaseFactory.getRecipientDatabase(context)
|
||||
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
||||
for (contact in contacts) {
|
||||
val address = Address.fromSerialized(contact.publicKey)
|
||||
val recipient = Recipient.from(context, address, true)
|
||||
if (!contact.profilePicture.isNullOrEmpty()) {
|
||||
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
|
||||
}
|
||||
if (contact.profileKey?.isNotEmpty() == true) {
|
||||
recipientDatabase.setProfileKey(recipient, contact.profileKey)
|
||||
}
|
||||
if (contact.name.isNotEmpty()) {
|
||||
recipientDatabase.setProfileName(recipient, contact.name)
|
||||
}
|
||||
recipientDatabase.setProfileSharing(recipient, true)
|
||||
recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED)
|
||||
// create Thread if needed
|
||||
threadDatabase.getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
if (contacts.isNotEmpty()) {
|
||||
threadDatabase.notifyUpdatedFromConfig()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri {
|
||||
return PartAuthority.getAttachmentDataUri(attachmentId)
|
||||
}
|
||||
|
@@ -324,6 +324,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
attachments,
|
||||
message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000,
|
||||
false,
|
||||
DistributionTypes.DEFAULT, quote.orNull(),
|
||||
sharedContacts.or(Collections.emptyList()),
|
||||
linkPreviews.or(Collections.emptyList()),
|
||||
@@ -473,7 +474,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
OutgoingTextMessage tm = new OutgoingTextMessage(Recipient.from(context, targetAddress, false),
|
||||
body, message.getExpiresInSeconds(), -1);
|
||||
body, message.getExpiresInSeconds(), -1, message.getTimestamp());
|
||||
|
||||
// Ignore the message if it has no body
|
||||
if (tm.getMessageBody().length() == 0) { return; }
|
||||
|
@@ -3,11 +3,11 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
|
||||
public abstract class PushReceivedJob extends BaseJob {
|
||||
|
||||
|
@@ -277,7 +277,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
||||
isLoading = true
|
||||
loaderContainer.fadeIn()
|
||||
val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
|
||||
MessageSender.explicitLeave(groupPublicKey!!)
|
||||
MessageSender.explicitLeave(groupPublicKey!!, true)
|
||||
} else {
|
||||
task {
|
||||
if (hasNameChanged) {
|
||||
|
@@ -26,11 +26,12 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.utilities.*
|
||||
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
|
||||
import org.session.libsignal.service.loki.utilities.toHexString
|
||||
@@ -343,7 +344,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
isClosedGroup = false
|
||||
}
|
||||
if (isClosedGroup) {
|
||||
MessageSender.explicitLeave(groupPublicKey!!)
|
||||
MessageSender.explicitLeave(groupPublicKey!!, false)
|
||||
} else {
|
||||
Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
|
||||
return@launch
|
||||
@@ -359,8 +360,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
|
||||
apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
|
||||
|
||||
ApplicationContext.getInstance(context).publicChatAPI!!
|
||||
.leave(publicChat.channel, publicChat.server)
|
||||
OpenGroupAPI.leave(publicChat.channel, publicChat.server)
|
||||
|
||||
ApplicationContext.getInstance(context).publicChatManager
|
||||
.removeChat(publicChat.server, publicChat.channel)
|
||||
|
@@ -28,6 +28,7 @@ import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.alwaysUi
|
||||
import org.session.libsession.messaging.avatars.AvatarHelper
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
@@ -179,11 +180,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
val promises = mutableListOf<Promise<*, Exception>>()
|
||||
val displayName = displayNameToBeUploaded
|
||||
if (displayName != null) {
|
||||
val publicChatAPI = ApplicationContext.getInstance(this).publicChatAPI
|
||||
if (publicChatAPI != null) {
|
||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||
promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) })
|
||||
}
|
||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||
promises.addAll(servers.map { OpenGroupAPI.setDisplayName(displayName, it) })
|
||||
TextSecurePreferences.setProfileName(this, displayName)
|
||||
}
|
||||
val profilePicture = profilePictureToBeUploaded
|
||||
|
@@ -7,12 +7,14 @@ import androidx.work.*
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.all
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
import org.session.libsignal.service.loki.api.SnodeAPI
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||
@@ -69,20 +71,21 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
|
||||
// Private chats
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val privateChatsPromise = SnodeAPI.shared.getMessages(userPublicKey).map { envelopes ->
|
||||
envelopes.forEach {
|
||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||
envelopes.map { envelope ->
|
||||
MessageReceiveJob(envelope.toByteArray(), false).executeAsync()
|
||||
}
|
||||
}
|
||||
promises.add(privateChatsPromise)
|
||||
promises.addAll(privateChatsPromise.get())
|
||||
|
||||
// Closed groups
|
||||
ClosedGroupPoller.configureIfNeeded(context)
|
||||
promises.addAll(ClosedGroupPoller.shared.pollOnce())
|
||||
promises.addAll(ApplicationContext.getInstance(context).closedGroupPoller.pollOnce())
|
||||
|
||||
// Open Groups
|
||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { (_,chat)->
|
||||
OpenGroup(chat.channel, chat.server, chat.displayName, chat.isDeletable)
|
||||
}
|
||||
for (openGroup in openGroups) {
|
||||
val poller = PublicChatPoller(context, openGroup)
|
||||
val poller = OpenGroupPoller(openGroup)
|
||||
promises.add(poller.pollForNewMessages())
|
||||
}
|
||||
|
||||
|
@@ -1,91 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.utilities.successBackground
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
import org.session.libsignal.service.loki.api.SnodeAPI
|
||||
import org.session.libsignal.service.loki.api.SwarmAPI
|
||||
import org.session.libsignal.service.loki.utilities.getRandomElementOrNull
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
|
||||
class ClosedGroupPoller private constructor(private val context: Context) {
|
||||
private var isPolling = false
|
||||
private val handler: Handler by lazy { Handler() }
|
||||
|
||||
private val task = object : Runnable {
|
||||
|
||||
override fun run() {
|
||||
poll()
|
||||
handler.postDelayed(this, pollInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// region Settings
|
||||
companion object {
|
||||
private val pollInterval: Long = 4 * 1000
|
||||
|
||||
public lateinit var shared: ClosedGroupPoller
|
||||
|
||||
public fun configureIfNeeded(context: Context) {
|
||||
if (::shared.isInitialized) { return; }
|
||||
shared = ClosedGroupPoller(context)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Error
|
||||
class InsufficientSnodesException() : Exception("No snodes left to poll.")
|
||||
class PollingCanceledException() : Exception("Polling canceled.")
|
||||
// endregion
|
||||
|
||||
// region Public API
|
||||
fun startIfNeeded() {
|
||||
if (isPolling) { return }
|
||||
isPolling = true
|
||||
task.run()
|
||||
}
|
||||
|
||||
fun pollOnce(): List<Promise<Unit, Exception>> {
|
||||
if (isPolling) { return listOf() }
|
||||
isPolling = true
|
||||
return poll()
|
||||
}
|
||||
|
||||
fun stopIfNeeded() {
|
||||
isPolling = false
|
||||
handler.removeCallbacks(task)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Private API
|
||||
private fun poll(): List<Promise<Unit, Exception>> {
|
||||
if (!isPolling) { return listOf() }
|
||||
val publicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
|
||||
return publicKeys.map { publicKey ->
|
||||
val promise = SwarmAPI.shared.getSwarm(publicKey).bind { swarm ->
|
||||
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||
if (!isPolling) { throw PollingCanceledException() }
|
||||
SnodeAPI.shared.getRawMessages(snode, publicKey).map {SnodeAPI.shared.parseRawMessagesResponse(it, snode, publicKey) }
|
||||
}
|
||||
promise.successBackground { messages ->
|
||||
if (messages.isNotEmpty()) {
|
||||
Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.")
|
||||
}
|
||||
messages.forEach {
|
||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||
}
|
||||
}
|
||||
promise.fail {
|
||||
Log.d("Loki", "Polling failed for closed group with public key: $publicKey due to error: $it.")
|
||||
}
|
||||
promise.map { Unit }
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
@@ -5,22 +5,26 @@ import android.database.ContentObserver
|
||||
import android.graphics.Bitmap
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupInfo
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChatInfo
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||
import kotlin.jvm.Throws
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class PublicChatManager(private val context: Context) {
|
||||
private var chats = mutableMapOf<Long, PublicChat>()
|
||||
private val pollers = mutableMapOf<Long, PublicChatPoller>()
|
||||
private var chats = mutableMapOf<Long, OpenGroup>()
|
||||
private val pollers = mutableMapOf<Long, OpenGroupPoller>()
|
||||
private val observers = mutableMapOf<Long, ContentObserver>()
|
||||
private var isPolling = false
|
||||
private val executorService = Executors.newScheduledThreadPool(16)
|
||||
|
||||
public fun areAllCaughtUp(): Boolean {
|
||||
var areAllCaughtUp = true
|
||||
@@ -35,7 +39,7 @@ class PublicChatManager(private val context: Context) {
|
||||
public fun markAllAsNotCaughtUp() {
|
||||
refreshChatsAndPollers()
|
||||
for ((threadID, chat) in chats) {
|
||||
val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
|
||||
val poller = pollers[threadID] ?: OpenGroupPoller(chat, executorService)
|
||||
poller.isCaughtUp = false
|
||||
}
|
||||
}
|
||||
@@ -44,7 +48,7 @@ class PublicChatManager(private val context: Context) {
|
||||
refreshChatsAndPollers()
|
||||
|
||||
for ((threadId, chat) in chats) {
|
||||
val poller = pollers[threadId] ?: PublicChatPoller(context, chat)
|
||||
val poller = pollers[threadId] ?: OpenGroupPoller(chat, executorService)
|
||||
poller.startIfNeeded()
|
||||
listenToThreadDeletion(threadId)
|
||||
if (!pollers.containsKey(threadId)) { pollers[threadId] = poller }
|
||||
@@ -55,32 +59,29 @@ class PublicChatManager(private val context: Context) {
|
||||
public fun stopPollers() {
|
||||
pollers.values.forEach { it.stop() }
|
||||
isPolling = false
|
||||
executorService.shutdown()
|
||||
}
|
||||
|
||||
//TODO Declare a specific type of checked exception instead of "Exception".
|
||||
@WorkerThread
|
||||
@Throws(java.lang.Exception::class)
|
||||
public fun addChat(server: String, channel: Long): PublicChat {
|
||||
val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI
|
||||
?: throw IllegalStateException("LokiPublicChatAPI is not set!")
|
||||
|
||||
public fun addChat(server: String, channel: Long): OpenGroup {
|
||||
// Ensure the auth token is acquired.
|
||||
groupChatAPI.getAuthToken(server).get()
|
||||
OpenGroupAPI.getAuthToken(server).get()
|
||||
|
||||
val channelInfo = groupChatAPI.getChannelInfo(channel, server).get()
|
||||
val channelInfo = OpenGroupAPI.getChannelInfo(channel, server).get()
|
||||
return addChat(server, channel, channelInfo)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat {
|
||||
public fun addChat(server: String, channel: Long, info: OpenGroupInfo): OpenGroup {
|
||||
val chat = PublicChat(channel, server, info.displayName, true)
|
||||
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
||||
var profilePicture: Bitmap? = null
|
||||
// Create the group if we don't have one
|
||||
if (threadID < 0) {
|
||||
if (info.profilePictureURL.isNotEmpty()) {
|
||||
val profilePictureAsByteArray = ApplicationContext.getInstance(context).publicChatAPI
|
||||
?.downloadOpenGroupProfilePicture(server, info.profilePictureURL)
|
||||
val profilePictureAsByteArray = OpenGroupAPI.downloadOpenGroupProfilePicture(server, info.profilePictureURL)
|
||||
profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray)
|
||||
}
|
||||
val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, chat.displayName)
|
||||
@@ -90,12 +91,12 @@ class PublicChatManager(private val context: Context) {
|
||||
// Set our name on the server
|
||||
val displayName = TextSecurePreferences.getProfileName(context)
|
||||
if (!TextUtils.isEmpty(displayName)) {
|
||||
ApplicationContext.getInstance(context).publicChatAPI?.setDisplayName(displayName, server)
|
||||
OpenGroupAPI.setDisplayName(displayName, server)
|
||||
}
|
||||
// Start polling
|
||||
Util.runOnMain { startPollersIfNeeded() }
|
||||
|
||||
return chat
|
||||
return OpenGroup.from(chat)
|
||||
}
|
||||
|
||||
public fun removeChat(server: String, channel: Long) {
|
||||
@@ -109,7 +110,8 @@ class PublicChatManager(private val context: Context) {
|
||||
}
|
||||
|
||||
private fun refreshChatsAndPollers() {
|
||||
val chatsInDB = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats()
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val chatsInDB = storage.getAllOpenGroups()
|
||||
val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) }
|
||||
removedChatThreadIds.forEach { pollers.remove(it)?.stop() }
|
||||
|
||||
|
@@ -1,238 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.session.libsession.utilities.IdentityKeyUtil
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.libsignal.util.guava.Optional
|
||||
import org.session.libsignal.utilities.successBackground
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
||||
import org.session.libsignal.service.api.messages.SignalServiceContent
|
||||
import org.session.libsignal.service.api.messages.SignalServiceDataMessage
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress
|
||||
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
|
||||
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.PublicChatMessage
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
class PublicChatPoller(private val context: Context, private val group: PublicChat) {
|
||||
private val handler by lazy { Handler() }
|
||||
private var hasStarted = false
|
||||
private var isPollOngoing = false
|
||||
public var isCaughtUp = false
|
||||
|
||||
// region Convenience
|
||||
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
private var displayNameUpdatees = setOf<String>()
|
||||
|
||||
private val api: PublicChatAPI
|
||||
get() = {
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
||||
val openGroupDatabase = DatabaseFactory.getGroupDatabase(context)
|
||||
PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase)
|
||||
}()
|
||||
// endregion
|
||||
|
||||
// region Tasks
|
||||
private val pollForNewMessagesTask = object : Runnable {
|
||||
|
||||
override fun run() {
|
||||
pollForNewMessages()
|
||||
handler.postDelayed(this, pollForNewMessagesInterval)
|
||||
}
|
||||
}
|
||||
|
||||
private val pollForDeletedMessagesTask = object : Runnable {
|
||||
|
||||
override fun run() {
|
||||
pollForDeletedMessages()
|
||||
handler.postDelayed(this, pollForDeletedMessagesInterval)
|
||||
}
|
||||
}
|
||||
|
||||
private val pollForModeratorsTask = object : Runnable {
|
||||
|
||||
override fun run() {
|
||||
pollForModerators()
|
||||
handler.postDelayed(this, pollForModeratorsInterval)
|
||||
}
|
||||
}
|
||||
|
||||
private val pollForDisplayNamesTask = object : Runnable {
|
||||
|
||||
override fun run() {
|
||||
pollForDisplayNames()
|
||||
handler.postDelayed(this, pollForDisplayNamesInterval)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Settings
|
||||
companion object {
|
||||
private val pollForNewMessagesInterval: Long = 4 * 1000
|
||||
private val pollForDeletedMessagesInterval: Long = 60 * 1000
|
||||
private val pollForModeratorsInterval: Long = 10 * 60 * 1000
|
||||
private val pollForDisplayNamesInterval: Long = 60 * 1000
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Lifecycle
|
||||
fun startIfNeeded() {
|
||||
if (hasStarted) return
|
||||
pollForNewMessagesTask.run()
|
||||
pollForDeletedMessagesTask.run()
|
||||
pollForModeratorsTask.run()
|
||||
pollForDisplayNamesTask.run()
|
||||
hasStarted = true
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
handler.removeCallbacks(pollForNewMessagesTask)
|
||||
handler.removeCallbacks(pollForDeletedMessagesTask)
|
||||
handler.removeCallbacks(pollForModeratorsTask)
|
||||
handler.removeCallbacks(pollForDisplayNamesTask)
|
||||
hasStarted = false
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Polling
|
||||
private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage {
|
||||
val id = group.id.toByteArray()
|
||||
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null)
|
||||
val quote = if (message.quote != null) {
|
||||
SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteePublicKey), message.quote!!.quotedMessageBody, listOf())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val attachments = message.attachments.mapNotNull { attachment ->
|
||||
if (attachment.kind != PublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
|
||||
SignalServiceAttachmentPointer(
|
||||
attachment.serverID,
|
||||
attachment.contentType,
|
||||
ByteArray(0),
|
||||
Optional.of(attachment.size),
|
||||
Optional.absent(),
|
||||
attachment.width, attachment.height,
|
||||
Optional.absent(),
|
||||
Optional.of(attachment.fileName),
|
||||
false,
|
||||
Optional.fromNullable(attachment.caption),
|
||||
attachment.url)
|
||||
}
|
||||
val linkPreview = message.attachments.firstOrNull { it.kind == PublicChatMessage.Attachment.Kind.LinkPreview }
|
||||
val signalLinkPreviews = mutableListOf<SignalServiceDataMessage.Preview>()
|
||||
if (linkPreview != null) {
|
||||
val attachment = SignalServiceAttachmentPointer(
|
||||
linkPreview.serverID,
|
||||
linkPreview.contentType,
|
||||
ByteArray(0),
|
||||
Optional.of(linkPreview.size),
|
||||
Optional.absent(),
|
||||
linkPreview.width, linkPreview.height,
|
||||
Optional.absent(),
|
||||
Optional.of(linkPreview.fileName),
|
||||
false,
|
||||
Optional.fromNullable(linkPreview.caption),
|
||||
linkPreview.url)
|
||||
signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment)))
|
||||
}
|
||||
val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
|
||||
val syncTarget = if (message.senderPublicKey == userHexEncodedPublicKey) group.id else null
|
||||
return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, 0, false, null, quote, null, signalLinkPreviews, null, syncTarget)
|
||||
}
|
||||
|
||||
fun pollForNewMessages(): Promise<Unit, Exception> {
|
||||
if (isPollOngoing) { return Promise.of(Unit) }
|
||||
isPollOngoing = true
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
|
||||
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
|
||||
val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
|
||||
Promise.of(messages)
|
||||
}
|
||||
promise.successBackground { messages ->
|
||||
// Process messages in the background
|
||||
messages.forEach { message ->
|
||||
// If the sender of the current message is not a slave device, set the display name in the database
|
||||
val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
|
||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
|
||||
val senderHexEncodedPublicKey = message.senderPublicKey
|
||||
val serviceDataMessage = getDataMessage(message)
|
||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false)
|
||||
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
|
||||
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||
} else {
|
||||
PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||
}
|
||||
// Update profile picture if needed
|
||||
val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false)
|
||||
if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) {
|
||||
val profileKey = message.profilePicture!!.profileKey
|
||||
val url = message.profilePicture!!.url
|
||||
if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) {
|
||||
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||
database.setProfileKey(senderAsRecipient, profileKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
|
||||
}
|
||||
}
|
||||
}
|
||||
isCaughtUp = true
|
||||
isPollOngoing = false
|
||||
}
|
||||
promise.fail {
|
||||
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
||||
isPollOngoing = false
|
||||
}
|
||||
return promise.map { Unit }
|
||||
}
|
||||
|
||||
private fun pollForDisplayNames() {
|
||||
if (displayNameUpdatees.isEmpty()) { return }
|
||||
val hexEncodedPublicKeys = displayNameUpdatees
|
||||
displayNameUpdatees = setOf()
|
||||
api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping ->
|
||||
for (pair in mapping.entries) {
|
||||
val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})"
|
||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName)
|
||||
}
|
||||
}.fail {
|
||||
displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pollForDeletedMessages() {
|
||||
api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs ->
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) }
|
||||
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||
val mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context)
|
||||
deletedMessageIDs.forEach {
|
||||
smsMessageDatabase.deleteMessage(it)
|
||||
mmsMessageDatabase.delete(it)
|
||||
}
|
||||
}.fail {
|
||||
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun pollForModerators() {
|
||||
api.getModerators(group.channel, group.server)
|
||||
}
|
||||
// endregion
|
||||
}
|
@@ -4,13 +4,13 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.service.loki.api.MessageWrapper
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
|
||||
class PushNotificationService : FirebaseMessagingService() {
|
||||
|
||||
@@ -27,8 +27,7 @@ class PushNotificationService : FirebaseMessagingService() {
|
||||
val data = base64EncodedData?.let { Base64.decode(it) }
|
||||
if (data != null) {
|
||||
try {
|
||||
val envelope = MessageWrapper.unwrap(data)
|
||||
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
|
||||
JobQueue.shared.add(MessageReceiveJob(MessageWrapper.unwrap(data).toByteArray(),true))
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to unwrap data for message due to error: $e.")
|
||||
}
|
||||
|
@@ -2,21 +2,21 @@ package org.thoughtcrime.securesms.loki.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import org.session.libsession.utilities.IdentityKeyUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
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.service.loki.api.Snode
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.service.loki.utilities.toHexString
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.session.libsignal.service.loki.api.Snode
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.service.loki.utilities.toHexString
|
||||
import org.session.libsession.utilities.IdentityKeyUtil
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
||||
import java.util.*
|
||||
|
||||
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
|
||||
@@ -416,7 +416,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
val index = "$groupPublicKey-$timestamp"
|
||||
val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removing05PrefixIfNeeded()
|
||||
val encryptionKeyPairPrivateKey = encryptionKeyPair.privateKey.serialize().toHexString()
|
||||
val row = wrap(mapOf( Companion.closedGroupsEncryptionKeyPairIndex to index, Companion.encryptionKeyPairPublicKey to encryptionKeyPairPublicKey,
|
||||
val row = wrap(mapOf(closedGroupsEncryptionKeyPairIndex to index, Companion.encryptionKeyPairPublicKey to encryptionKeyPairPublicKey,
|
||||
Companion.encryptionKeyPairPrivateKey to encryptionKeyPairPrivateKey ))
|
||||
database.insertOrUpdate(closedGroupEncryptionKeyPairsTable, row, "${Companion.closedGroupsEncryptionKeyPairIndex} = ?", wrap(index))
|
||||
}
|
||||
|
@@ -126,6 +126,5 @@ object MultiDeviceProtocol {
|
||||
threadDatabase.notifyUpdatedFromConfig()
|
||||
}
|
||||
}
|
||||
// TODO: handle new configuration message fields or handle in new pipeline
|
||||
}
|
||||
}
|
@@ -3,16 +3,15 @@ package org.thoughtcrime.securesms.loki.utilities
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.session.libsession.utilities.preferences.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.preferences.ProfileKeyUtil
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||
import java.lang.Exception
|
||||
import java.lang.IllegalStateException
|
||||
import kotlin.jvm.Throws
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
|
||||
//TODO Refactor so methods declare specific type of checked exceptions and not generalized Exception.
|
||||
object OpenGroupUtilities {
|
||||
@@ -22,29 +21,27 @@ object OpenGroupUtilities {
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
@Throws(Exception::class)
|
||||
fun addGroup(context: Context, url: String, channel: Long): PublicChat {
|
||||
fun addGroup(context: Context, url: String, channel: Long): OpenGroup {
|
||||
// Check for an existing group.
|
||||
val groupID = PublicChat.getId(channel, url)
|
||||
val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||
if (openGroup != null) { return openGroup }
|
||||
if (openGroup != null) { return OpenGroup.from(openGroup) }
|
||||
|
||||
// Add the new group.
|
||||
val application = ApplicationContext.getInstance(context)
|
||||
val displayName = TextSecurePreferences.getProfileName(context)
|
||||
val lokiPublicChatAPI = application.publicChatAPI
|
||||
?: throw IllegalStateException("LokiPublicChatAPI is not initialized.")
|
||||
|
||||
val group = application.publicChatManager.addChat(url, channel)
|
||||
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
|
||||
lokiPublicChatAPI.getMessages(channel, url)
|
||||
lokiPublicChatAPI.setDisplayName(displayName, url)
|
||||
lokiPublicChatAPI.join(channel, url)
|
||||
OpenGroupAPI.getMessages(channel, url)
|
||||
OpenGroupAPI.setDisplayName(displayName, url)
|
||||
OpenGroupAPI.join(channel, url)
|
||||
val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context)
|
||||
val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context)
|
||||
lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
|
||||
OpenGroupAPI.setProfilePicture(url, profileKey, profileUrl)
|
||||
return group
|
||||
}
|
||||
|
||||
@@ -58,18 +55,15 @@ object OpenGroupUtilities {
|
||||
@WorkerThread
|
||||
@Throws(Exception::class)
|
||||
fun updateGroupInfo(context: Context, url: String, channel: Long) {
|
||||
val publicChatAPI = ApplicationContext.getInstance(context).publicChatAPI
|
||||
?: throw IllegalStateException("Public chat API is not initialized!")
|
||||
|
||||
// Check if open group has a related DB record.
|
||||
val groupId = GroupUtil.getEncodedOpenGroupID(PublicChat.getId(channel, url).toByteArray())
|
||||
if (!DatabaseFactory.getGroupDatabase(context).hasGroup(groupId)) {
|
||||
throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId")
|
||||
}
|
||||
|
||||
val info = publicChatAPI.getChannelInfo(channel, url).get()
|
||||
val info = OpenGroupAPI.getChannelInfo(channel, url).get()
|
||||
|
||||
publicChatAPI.updateProfileIfNeeded(channel, url, groupId, info, false)
|
||||
OpenGroupAPI.updateProfileIfNeeded(channel, url, groupId, info, false)
|
||||
|
||||
EventBus.getDefault().post(GroupInfoUpdatedEvent(url, channel))
|
||||
}
|
||||
|
@@ -8,9 +8,9 @@ import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsignal.service.loki.utilities.mentions.Mention
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
var mentionCandidate = Mention("", "")
|
||||
@@ -38,7 +38,7 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr:
|
||||
profilePictureView.glide = glide!!
|
||||
profilePictureView.update()
|
||||
if (publicChatServer != null && publicChatChannel != null) {
|
||||
val isUserModerator = PublicChatAPI.isUserModerator(mentionCandidate.publicKey, publicChatChannel!!, publicChatServer!!)
|
||||
val isUserModerator = OpenGroupAPI.isUserModerator(mentionCandidate.publicKey, publicChatChannel!!, publicChatServer!!)
|
||||
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||
} else {
|
||||
moderatorIconImageView.visibility = View.GONE
|
||||
|
@@ -1,17 +1,18 @@
|
||||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
@@ -27,7 +28,9 @@ public class MediaPreviewViewModel extends ViewModel {
|
||||
|
||||
public void setCursor(@NonNull Context context, @Nullable Cursor cursor, boolean leftIsRecent) {
|
||||
boolean firstLoad = (this.cursor == null) && (cursor != null);
|
||||
|
||||
if (this.cursor != null) {
|
||||
this.cursor.close();
|
||||
}
|
||||
this.cursor = cursor;
|
||||
this.leftIsRecent = leftIsRecent;
|
||||
|
||||
|
@@ -6,14 +6,13 @@ import android.os.Looper;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.Poller;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsession.utilities.Debouncer;
|
||||
import org.session.libsignal.utilities.ThreadUtils;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
||||
import org.session.libsession.utilities.Debouncer;
|
||||
import org.session.libsignal.service.loki.api.Poller;
|
||||
import org.session.libsignal.utilities.ThreadUtils;
|
||||
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@@ -4,9 +4,14 @@ import android.content.Context;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
|
||||
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.utilities.GroupUtil;
|
||||
import org.session.libsession.utilities.SSKEnvironment;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos;
|
||||
@@ -20,6 +25,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -65,45 +72,78 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpirationTimer(@Nullable Long messageID, int duration, @NotNull String senderPublicKey, @NotNull SignalServiceProtos.Content content) {
|
||||
public void setExpirationTimer(@NotNull ExpirationTimerUpdate message) {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
String senderPublicKey = message.getSender();
|
||||
int duration = message.getDuration();
|
||||
String groupPK = message.getGroupPublicKey();
|
||||
Long sentTimestamp = message.getSentTimestamp();
|
||||
|
||||
Optional<SignalServiceGroup> groupInfo = Optional.absent();
|
||||
Address address;
|
||||
|
||||
try {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Address address = Address.fromSerialized(senderPublicKey);
|
||||
if (groupPK != null) {
|
||||
String groupID = GroupUtil.doubleEncodeGroupID(groupPK);
|
||||
groupInfo = Optional.of(new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL));
|
||||
address = Address.fromSerialized(groupID);
|
||||
} else {
|
||||
address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : senderPublicKey);
|
||||
}
|
||||
Recipient recipient = Recipient.from(context, address, false);
|
||||
|
||||
if (recipient.isBlocked()) return;
|
||||
|
||||
Optional<SignalServiceGroup> groupInfo = Optional.absent();
|
||||
if (content.getDataMessage().hasGroup()) {
|
||||
GroupContext groupContext = content.getDataMessage().getGroup();
|
||||
groupInfo = Optional.of(new SignalServiceGroup(groupContext.getId().toByteArray(), SignalServiceGroup.GroupType.SIGNAL));
|
||||
// Notify the user
|
||||
if (userPublicKey.equals(senderPublicKey)) {
|
||||
// sender is a linked device
|
||||
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipient,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
message.getSentTimestamp(),
|
||||
-1,
|
||||
duration * 1000L,
|
||||
true,
|
||||
DistributionTypes.DEFAULT,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
database.insertSecureDecryptedMessageOutbox(mediaMessage, -1, sentTimestamp);
|
||||
} else {
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1,
|
||||
duration * 1000L, true,
|
||||
false,
|
||||
Optional.absent(),
|
||||
groupInfo,
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent());
|
||||
//insert the timer update message
|
||||
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
}
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, content.getDataMessage().getTimestamp(), -1,
|
||||
duration * 1000L, true,
|
||||
false,
|
||||
Optional.absent(),
|
||||
groupInfo,
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent());
|
||||
|
||||
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
//set the timer to the conversation
|
||||
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration);
|
||||
|
||||
if (messageID != null) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(messageID);
|
||||
if (message.getId() != null) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(message.getId());
|
||||
}
|
||||
} catch (MmsException e) {
|
||||
Log.e("Loki", "Failed to insert expiration update message.");
|
||||
} catch (IOException ioe) {
|
||||
Log.e("Loki", "Failed to insert expiration update message.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableExpirationTimer(@Nullable Long messageID, @NotNull String senderPublicKey, @NotNull SignalServiceProtos.Content content) {
|
||||
setExpirationTimer(messageID, 0, senderPublicKey, content);
|
||||
public void disableExpirationTimer(@NotNull ExpirationTimerUpdate message) {
|
||||
setExpirationTimer(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
24
app/src/main/res/raw/lf_session_cert.pem
Normal file
24
app/src/main/res/raw/lf_session_cert.pem
Normal file
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL
|
||||
BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
|
||||
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
|
||||
HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx
|
||||
WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y
|
||||
aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl
|
||||
Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e
|
||||
RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+
|
||||
K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh
|
||||
GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ
|
||||
0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY
|
||||
PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC
|
||||
AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j
|
||||
BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV
|
||||
HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF
|
||||
BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI
|
||||
zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv
|
||||
ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz
|
||||
Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ
|
||||
lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9
|
||||
OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I
|
||||
-----END CERTIFICATE-----
|
25
app/src/main/res/raw/seed1cert.pem
Normal file
25
app/src/main/res/raw/seed1cert.pem
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL
|
||||
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
|
||||
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
|
||||
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw
|
||||
MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
|
||||
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
|
||||
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp
|
||||
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd
|
||||
kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k
|
||||
TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM
|
||||
OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP
|
||||
F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw
|
||||
+axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP
|
||||
OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K
|
||||
zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs
|
||||
gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj
|
||||
lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP
|
||||
hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln
|
||||
N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT
|
||||
/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV
|
||||
gxJsm+g=
|
||||
-----END CERTIFICATE-----
|
25
app/src/main/res/raw/seed3cert.pem
Normal file
25
app/src/main/res/raw/seed3cert.pem
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIUc486Dy9Y00bUFfDeYmJIgSS5xREwDQYJKoZIhvcNAQEL
|
||||
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
|
||||
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
|
||||
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQzLmxva2kubmV0d29yazAeFw0yMTA0MDcw
|
||||
MTIwNTJaFw0yMzA0MDcwMTIwNTJaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
|
||||
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
|
||||
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMy5sb2tp
|
||||
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtokMlsFzf
|
||||
piYeD0EVNikMyvjltpF6fUEde9NOVrTtNTQT6kkDk+/0HF5LYgPaatv6v7fpUQHi
|
||||
kIwd6F0LTRGeWDFdsaWMdtlR1n/GxLPrOROsE8dcLt6GLavPf9rDabgva93m/JD6
|
||||
XW+Ne+MPEwqS8dAmFGhZd0gju6AtKFoSHnIf5pSQN6fSZUF/JQtHLVprAKKWKDiS
|
||||
ZwmWbmrZR2aofLD/VRpetabajnZlv9EeWloQwvUsw1C1hkAmmtFeeXtg7ePwrOzo
|
||||
6CnmcUJwOmi+LWqQV4A+58RZPFKaZoC5pzaKd0OYB8eZ8HB1F41UjGJgheX5Cyl4
|
||||
+amfF3l8dSq1AgMBAAGjgZAwgY0wHQYDVR0OBBYEFM9VSq4pGydjtX92Beul4+ml
|
||||
jBKtMB8GA1UdIwQYMBaAFM9VSq4pGydjtX92Beul4+mljBKtMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMy5sb2tpLm5ldHdvcmswEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAAYxmhhkcKE1n6g1
|
||||
JqOa3UCBo4EfbqY5+FDZ0FVqv/cwemwVpKLbe6luRIS8poomdPCyMOS45V7wN3H9
|
||||
cFpfJ1TW19ydPVKmCXrl29ngmnY1q7YDwE/4qi3VK/UiqDkTHMKWjVPkenOyi8u6
|
||||
VVQANXSnKrn6GtigNFjGyD38O+j7AUSXBtXOJczaoF6r6BWgwQZ2WmgjuwvKTWSN
|
||||
4r8uObERoAQYVaeXfgdr4e9X/JdskBDaLFfoW/rrSozHB4FqVNFW96k+aIUgRa5p
|
||||
9kv115QcBPCSh9qOyTHij4tswS6SyOFaiKrNC4hgHQXP4QgioKmtsR/2Y+qJ6ddH
|
||||
6oo+4QU=
|
||||
-----END CERTIFICATE-----
|
@@ -3,4 +3,22 @@
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
</domain-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="false">public.loki.foundation</domain>
|
||||
<trust-anchors>
|
||||
<certificates src="@raw/lf_session_cert"/>
|
||||
</trust-anchors>
|
||||
</domain-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="false">storage.seed1.loki.network</domain>
|
||||
<trust-anchors>
|
||||
<certificates src="@raw/seed1cert"/>
|
||||
</trust-anchors>
|
||||
</domain-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="false">storage.seed3.loki.network</domain>
|
||||
<trust-anchors>
|
||||
<certificates src="@raw/seed3cert"/>
|
||||
</trust-anchors>
|
||||
</domain-config>
|
||||
</network-security-config>
|
Reference in New Issue
Block a user