Conversation activity no longer pulls the public chat info directly.

Public chat API partially refactored to avoid thread branching and to use Kotlin coroutines instead of kovenant futures.
This commit is contained in:
Anton Chekulaev 2020-11-20 18:59:13 +11:00
parent 55da7056dc
commit 4307140e5c
37 changed files with 289 additions and 122 deletions

View File

@ -89,7 +89,9 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation "androidx.work:work-runtime-ktx:2.4.0"
implementation "androidx.core:core-ktx:1.3.2"
implementation ("com.google.firebase:firebase-messaging:18.0.0") {
exclude group: 'com.google.firebase', module: 'firebase-core'

View File

@ -348,7 +348,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true, Collections.singletonList(local));
Recipient groupRecipient = Recipient.from(activity, Address.fromSerialized(groupId), true);
long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
long threadId = DatabaseFactory.getThreadDatabase(activity).getOrCreateThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
return new GroupActionResult(groupRecipient, threadId);
}

View File

@ -767,7 +767,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
.setBlocked(recipient, blocked);
if (recipient.isGroupRecipient() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.getAddress().toGroupString())) {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
Optional<OutgoingGroupMediaMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(context, recipient);
if (threadId != -1 && leaveMessage.isPresent()) {
@ -776,7 +776,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
String groupId = recipient.getAddress().toGroupString();
groupDatabase.setActive(groupId, false);
groupDatabase.remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
groupDatabase.removeMember(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
} else {
Log.w(TAG, "Failed to leave group. Can't block.");
Toast.makeText(context, R.string.RecipientPreferenceActivity_error_leaving_group, Toast.LENGTH_LONG).show();

View File

@ -197,7 +197,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
String quoteeDisplayName = author.toShortString();
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(conversationRecipient);
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(conversationRecipient);
String senderHexEncodedPublicKey = author.getAddress().serialize();
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID);
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {

View File

@ -90,7 +90,7 @@ public class TypingStatusSender {
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
for (String device : linkedDevices) {
Recipient deviceAsRecipient = Recipient.from(context, Address.fromSerialized(device), false);
long deviceThreadID = threadDatabase.getThreadIdFor(deviceAsRecipient);
long deviceThreadID = threadDatabase.getOrCreateThreadIdFor(deviceAsRecipient);
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted));
}
}

View File

@ -156,6 +156,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
@ -163,6 +164,7 @@ import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
@ -462,20 +464,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) {
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(this).getPublicChatAPI();
publicChatAPI.getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(info -> {
String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes());
publicChatAPI.updateProfileIfNeeded(
publicChat.getChannel(),
publicChat.getServer(),
groupId,
info,
false);
runOnUiThread(ConversationActivity.this::updateSubtitleTextView);
return Unit.INSTANCE;
});
// Request open group info update and handle the successful result in #onOpenGroupInfoUpdated().
PublicChatInfoUpdateWorker.scheduleInstant(this, publicChat.getServer(), publicChat.getChannel());
}
View rootView = findViewById(R.id.rootView);
@ -1940,6 +1930,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
.show(TooltipPopup.POSITION_ABOVE);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) {
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null &&
publicChat.getChannel() == event.getChannel() &&
publicChat.getServer().equals(event.getUrl())) {
this.updateSubtitleTextView();
}
}
private void initializeReceivers() {
securityUpdateReceiver = new BroadcastReceiver() {
@Override
@ -2095,7 +2095,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
long threadId = params[0];
if (drafts.size() > 0) {
if (threadId == -1) threadId = threadDatabase.getThreadIdFor(getRecipient(), thisDistributionType);
if (threadId == -1) threadId = threadDatabase.getOrCreateThreadIdFor(getRecipient(), thisDistributionType);
draftDatabase.insertDrafts(threadId, drafts);
threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this),

View File

@ -218,6 +218,18 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
notifyConversationListListeners();
}
public boolean delete(@NonNull String groupId) {
int result = databaseHelper.getWritableDatabase().delete(TABLE_NAME, GROUP_ID + " = ?", new String[]{groupId});
if (result > 0) {
Recipient.removeCached(Address.fromSerialized(groupId));
notifyConversationListListeners();
return true;
} else {
return false;
}
}
public void update(String groupId, String title, SignalServiceAttachmentPointer avatar) {
ContentValues contentValues = new ContentValues();
if (title != null) contentValues.put(TITLE, title);
@ -262,7 +274,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
long avatarId;
if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong());
else avatarId = 0;
else avatarId = 0;
ContentValues contentValues = new ContentValues(2);
@ -300,7 +312,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId});
}
public void remove(String groupId, Address source) {
public void removeMember(String groupId, Address source) {
List<Address> currentMembers = getCurrentMembers(groupId);
currentMembers.remove(source);
@ -352,13 +364,21 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId});
}
public byte[] allocateGroupId() {
byte[] groupId = new byte[16];
new SecureRandom().nextBytes(groupId);
return groupId;
}
public boolean hasGroup(@NonNull String groupId) {
try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(
"SELECT 1 FROM " + TABLE_NAME + " WHERE " + GROUP_ID + " = ? LIMIT 1",
new String[]{groupId}
)) {
return cursor.getCount() > 0;
}
}
public static class Reader implements Closeable {
private final Cursor cursor;

View File

@ -321,10 +321,10 @@ public class MmsDatabase extends MessagingDatabase {
private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException, MmsException {
if (retrieved.getGroupId() != null) {
Recipient groupRecipients = Recipient.from(context, retrieved.getGroupId(), true);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipients);
} else {
Recipient sender = Recipient.from(context, retrieved.getFrom(), true);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(sender);
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(sender);
}
}
@ -333,7 +333,7 @@ public class MmsDatabase extends MessagingDatabase {
? Util.toIsoString(notification.getFrom().getTextString())
: "";
Recipient recipient = Recipient.from(context, Address.fromExternal(context, fromString), false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
}
private Cursor rawQuery(@NonNull String where, @Nullable String[] arguments) {

View File

@ -556,7 +556,7 @@ public class SmsDatabase extends MessagingDatabase {
private @NonNull Pair<Long, Long> insertCallLog(@NonNull Address address, long type, boolean unread) {
Recipient recipient = Recipient.from(context, address, true);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, address.serialize());
@ -620,8 +620,8 @@ public class SmsDatabase extends MessagingDatabase {
long threadId;
if (groupRecipient == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
if (groupRecipient == null) threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
else threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, message.getSender().serialize());

View File

@ -209,7 +209,7 @@ public class SmsMigrator {
if (ourRecipients != null) {
if (ourRecipients.size() == 1) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients.iterator().next());
long ourThreadId = threadDatabase.getOrCreateThreadIdFor(ourRecipients.iterator().next());
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
} else if (ourRecipients.size() > 1) {
ourRecipients.add(Recipient.from(context, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), true));
@ -222,7 +222,7 @@ public class SmsMigrator {
String ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(memberAddresses, true, null);
Recipient ourGroupRecipient = Recipient.from(context, Address.fromSerialized(ourGroupId), true);
long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
long ourThreadId = threadDatabase.getOrCreateThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
}

View File

@ -512,11 +512,11 @@ public class ThreadDatabase extends Database {
}
}
public long getThreadIdFor(Recipient recipient) {
return getThreadIdFor(recipient, DistributionTypes.DEFAULT);
public long getOrCreateThreadIdFor(Recipient recipient) {
return getOrCreateThreadIdFor(recipient, DistributionTypes.DEFAULT);
}
public long getThreadIdFor(Recipient recipient, int distributionType) {
public long getOrCreateThreadIdFor(Recipient recipient, int distributionType) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String where = ADDRESS + " = ?";
String[] recipientsArg = new String[]{recipient.getAddress().serialize()};

View File

@ -62,7 +62,7 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMed
@Override
public BucketedThreadMedia loadInBackground() {
BucketedThreadMedia result = new BucketedThreadMedia(getContext());
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.from(getContext(), address, true));
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(Recipient.from(getContext(), address, true));
DatabaseFactory.getMediaDatabase(getContext()).subscribeToMediaChanges(observer);
try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId)) {

View File

@ -34,7 +34,7 @@ public class PagingMediaLoader extends AsyncLoader<Pair<Cursor, Integer>> {
@Nullable
@Override
public Pair<Cursor, Integer> loadInBackground() {
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(recipient);
Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId);
while (cursor != null && cursor.moveToNext()) {

View File

@ -23,7 +23,7 @@ public class ThreadMediaLoader extends AbstractCursorLoader {
@Override
public Cursor getCursor() {
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.from(getContext(), address, true));
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(Recipient.from(getContext(), address, true));
if (gallery) return DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId);
else return DatabaseFactory.getMediaDatabase(getContext()).getDocumentMediaForThread(threadId);

View File

@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
@ -89,7 +90,8 @@ public class GroupManager {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
} else {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(
groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadId);
}
}
@ -127,10 +129,30 @@ public class GroupManager {
groupDatabase.updateProfilePicture(groupId, avatarBytes);
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
long threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(
groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadID);
}
public static boolean deleteGroup(@NonNull String groupId,
@NonNull Context context)
{
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false);
if (!groupDatabase.getGroup(groupId).isPresent()) {
return false;
}
long threadId = threadDatabase.getThreadIdIfExistsFor(groupRecipient);
if (threadId != -1L) {
threadDatabase.deleteConversation(threadId);
}
return groupDatabase.delete(groupId);
}
public static GroupActionResult updateGroup(@NonNull Context context,
@NonNull String groupId,
@NonNull Set<Recipient> members,
@ -154,7 +176,7 @@ public class GroupManager {
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
} else {
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
return new GroupActionResult(groupRecipient, threadId);
}
}

View File

@ -233,7 +233,7 @@ public class GroupMessageProcessor {
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
if (masterDevice == null) { masterDevice = content.getSender(); }
if (members.contains(Address.fromExternal(context, masterDevice))) {
database.remove(id, Address.fromExternal(context, masterDevice));
database.removeMember(id, Address.fromExternal(context, masterDevice));
if (outgoing) database.setActive(id, false);
return storeMessage(context, content, group, builder.build(), outgoing);
@ -260,7 +260,7 @@ public class GroupMessageProcessor {
Address address = Address.fromExternal(context, GroupUtil.getEncodedId(group));
Recipient recipient = Recipient.from(context, address, false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList(), Collections.emptyList());
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
mmsDatabase.markAsSent(messageId, true);

View File

@ -498,7 +498,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipient, "", -1);
OutgoingEndSessionMessage outgoingEndSessionMessage = new OutgoingEndSessionMessage(outgoingTextMessage);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
if (!recipient.isGroupRecipient()) {
// TODO: Handle session reset on sync messages
@ -808,7 +808,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (result.getMessageId() > -1) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
long originalThreadId = threadDatabase.getOrCreateThreadIdFor(originalRecipient);
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
}
}
@ -822,7 +822,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
message.getTimestamp(),
message.getMessage().getExpiresInSeconds() * 1000L);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
long messageId = database.insertMessageOutbox(expirationUpdateMessage, threadId, false, null);
database.markAsSent(messageId, true);
@ -864,7 +864,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleSynchronizeSentExpirationUpdate(message);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipients);
database.beginTransaction();
@ -995,7 +995,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (result.getMessageId() > -1) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
long originalThreadId = threadDatabase.getOrCreateThreadIdFor(originalRecipient);
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
}
}
@ -1018,7 +1018,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleSynchronizeSentExpirationUpdate(message);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
boolean isGroup = recipient.getAddress().isGroup();
MessagingDatabase database;
@ -1102,7 +1102,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (canRecoverAutomatically(e)) {
Recipient recipient = Recipient.from(context, Address.fromSerialized(sender), false);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
threadDB.addSessionRestoreDevice(threadID, sender);
SessionManagementProtocol.startSessionReset(context, sender);
} else {
@ -1249,7 +1249,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else {
// See if we need to redirect the message
author = getMessageMasterDestination(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author);
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(author);
}
if (threadId <= 0) {
@ -1459,7 +1459,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) {
Recipient author = Recipient.from(context, Address.fromSerialized(sender), false);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(conversationRecipient);
if (threadId > 0) {
Log.d(TAG, "Typing stopped on thread " + threadId + " due to an incoming message.");

View File

@ -125,7 +125,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
loader.fadeOut()
isLoading = false
val threadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
if (!isFinishing) {
openConversationActivity(this, threadID, Recipient.from(this, Address.fromSerialized(groupID), false))
finish()

View File

@ -26,13 +26,12 @@ import kotlinx.android.synthetic.main.activity_home.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob
import org.thoughtcrime.securesms.loki.dialogs.ConversationOptionsBottomSheet
import org.thoughtcrime.securesms.loki.dialogs.LightThemeFeatureIntroBottomSheet
import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet
@ -343,6 +342,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val recipient = thread.recipient
val threadDB = DatabaseFactory.getThreadDatabase(this)
val deleteThread = Runnable {
//TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
AsyncTask.execute {
val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID)
if (publicChat != null) {
@ -351,6 +351,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server)
//FIXME Group deletion should be synchronized with the related thread deletion.
val groupId = threadDB.getRecipientForThreadId(threadID)!!.address.serialize()
GroupManager.deleteGroup(groupId, this@HomeActivity)
}
threadDB.deleteConversation(threadID)
ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity)

View File

@ -11,8 +11,12 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_join_public_chat.*
import kotlinx.android.synthetic.main.fragment_enter_chat_url.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
@ -22,6 +26,7 @@ import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.loki.protocol.shelved.SyncMessagesProtocol
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import java.lang.Exception
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val adapter = JoinPublicChatActivityAdapter(this)
@ -67,13 +72,19 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
}
showLoader()
val channel: Long = 1
OpenGroupUtilities.addGroup(this, url, channel).success {
lifecycleScope.launch(Dispatchers.IO) {
try {
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
hideLoader()
Toast.makeText(this@JoinPublicChatActivity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
}
return@launch
}
SyncMessagesProtocol.syncAllOpenGroups(this@JoinPublicChatActivity)
}.successUi {
finish()
}.failUi {
hideLoader()
Toast.makeText(this, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
withContext(Dispatchers.Main) { finish() }
}
}
// endregion
@ -123,13 +134,13 @@ class EnterChatURLFragment : Fragment() {
}
private fun joinPublicChatIfPossible() {
val inputMethodManager = context!!.getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
var chatURL = chatURLEditText.text.trim().toString().toLowerCase().replace("http://", "https://")
if (!chatURL.toLowerCase().startsWith("https")) {
chatURL = "https://$chatURL"
}
(activity!! as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
}
}
// endregion

View File

@ -25,10 +25,9 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
@JvmStatic
fun scheduleInstant(context: Context) {
val workRequest = OneTimeWorkRequestBuilder<BackgroundPollWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
@ -41,10 +40,9 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
fun schedulePeriodic(context: Context) {
Log.v(TAG, "Scheduling periodic work.")
val workRequest = PeriodicWorkRequestBuilder<BackgroundPollWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()

View File

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.loki.api
import android.content.Context
import androidx.work.*
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
/**
* Delegates the [OpenGroupUtilities.updateGroupInfo] call to the work manager.
*/
class PublicChatInfoUpdateWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
companion object {
const val TAG = "PublicChatInfoUpdateWorker"
private const val DATA_KEY_SERVER_URL = "server_uRL"
private const val DATA_KEY_CHANNEL = "channel"
@JvmStatic
fun scheduleInstant(context: Context, serverURL: String, channel: Long) {
val workRequest = OneTimeWorkRequestBuilder<PublicChatInfoUpdateWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.setInputData(workDataOf(
DATA_KEY_SERVER_URL to serverURL,
DATA_KEY_CHANNEL to channel
))
.build()
WorkManager
.getInstance(context)
.enqueue(workRequest)
}
}
override fun doWork(): Result {
val serverUrl = inputData.getString(DATA_KEY_SERVER_URL)!!
val channel = inputData.getLong(DATA_KEY_CHANNEL, -1)
val publicChatId = PublicChat.getId(channel, serverUrl)
return try {
Log.v(TAG, "Updating open group info for $publicChatId.")
OpenGroupUtilities.updateGroupInfo(context, serverUrl, channel)
Log.v(TAG, "Open group info was successfully updated for $publicChatId.")
Result.success()
} catch (e: Exception) {
Log.e(TAG, "Failed to update open group info for $publicChatId", e)
Result.failure()
}
}
}

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.database.ContentObserver
import android.graphics.Bitmap
import android.text.TextUtils
import androidx.annotation.WorkerThread
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatInfo
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
import kotlin.jvm.Throws
class PublicChatManager(private val context: Context) {
private var chats = mutableMapOf<Long, PublicChat>()
@ -23,7 +25,7 @@ class PublicChatManager(private val context: Context) {
private val observers = mutableMapOf<Long, ContentObserver>()
private var isPolling = false
public fun areAllCaughtUp():Boolean {
public fun areAllCaughtUp(): Boolean {
var areAllCaughtUp = true
refreshChatsAndPollers()
for ((threadID, chat) in chats) {
@ -58,19 +60,24 @@ class PublicChatManager(private val context: Context) {
isPolling = false
}
public fun addChat(server: String, channel: Long): Promise<PublicChat, Exception> {
//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
?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
return groupChatAPI.getAuthToken(server).bind {
groupChatAPI.getChannelInfo(channel, server)
}.map {
addChat(server, channel, it)
}
?: throw IllegalStateException("LokiPublicChatAPI is not set!")
// Ensure the auth token is acquired.
groupChatAPI.getAuthToken(server).get()
val channelInfo = groupChatAPI.getChannelInfo(channel, server).get()
return addChat(server, channel, channelInfo)
}
@WorkerThread
public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat {
val chat = PublicChat(channel, server, info.displayName, true)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
var profilePicture: Bitmap? = null
// Create the group if we don't have one
if (threadID < 0) {
@ -105,7 +112,7 @@ class PublicChatManager(private val context: Context) {
private fun listenToThreadDeletion(threadID: Long) {
if (threadID < 0 || observers[threadID] != null) { return }
val observer = createDeletionObserver(threadID, Runnable {
val observer = createDeletionObserver(threadID) {
val chat = chats[threadID]
// Reset last message cache
@ -119,7 +126,7 @@ class PublicChatManager(private val context: Context) {
pollers.remove(threadID)?.stop()
observers.remove(threadID)
startPollersIfNeeded()
})
}
observers[threadID] = observer
context.applicationContext.contentResolver.registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadID), true, observer)

View File

@ -193,8 +193,8 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
val messageID = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID)
var isDuplicate = false
if (messageID != null) {
isDuplicate = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageID) > 0
|| DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID) > 0
isDuplicate = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageID) >= 0
|| DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID) >= 0
}
if (isDuplicate) { return }
if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.loki.database
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.util.Log
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
@ -34,7 +33,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
override fun getThreadID(hexEncodedPublicKey: String): Long {
val address = Address.fromSerialized(hexEncodedPublicKey)
val recipient = Recipient.from(context, address, false)
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
}
fun getThreadID(messageID: Long): Long {

View File

@ -25,7 +25,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.GroupType
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext
import org.whispersystems.signalservice.loki.api.SnodeAPI
import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupRatchet
import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType
import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupSenderKey
@ -82,7 +81,7 @@ object ClosedGroupsProtocol {
// Add the group to the user's set of public keys to poll for
DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey)
// Notify the user
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
// Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
@ -166,7 +165,7 @@ object ClosedGroupsProtocol {
if (isUserLeaving) {
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
groupDB.setActive(groupID, false)
groupDB.remove(groupID, Address.fromSerialized(userPublicKey))
groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
// Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
} else {
@ -230,7 +229,7 @@ object ClosedGroupsProtocol {
}
// Notify the user
val infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
deferred.resolve(Unit)
}.start()
@ -385,7 +384,7 @@ object ClosedGroupsProtocol {
if (wasCurrentUserRemoved) {
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
groupDB.setActive(groupID, false)
groupDB.remove(groupID, Address.fromSerialized(userPublicKey))
groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
// Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
} else {
@ -510,7 +509,7 @@ object ClosedGroupsProtocol {
@JvmStatic
fun leaveLegacyGroup(context: Context, recipient: Recipient): Boolean {
if (!recipient.address.isClosedGroup) { return true }
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
val message = GroupUtil.createGroupLeaveMessage(context, recipient).orNull()
if (threadID < 0 || message == null) { return false }
MessageSender.send(context, message, threadID, false, null)
@ -522,7 +521,7 @@ object ClosedGroupsProtocol {
val groupDatabase = DatabaseFactory.getGroupDatabase(context)
val groupID = recipient.address.toGroupString()
groupDatabase.setActive(groupID, false)
groupDatabase.remove(groupID, Address.fromSerialized(userPublicKey))
groupDatabase.removeMember(groupID, Address.fromSerialized(userPublicKey))
return true
}

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.loki.protocol
import android.content.Context
import android.os.AsyncTask
import android.util.Log
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
@ -11,7 +10,6 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
import org.thoughtcrime.securesms.loki.utilities.recipient
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
@ -28,7 +26,7 @@ object SessionManagementProtocol {
val recipient = recipient(context, publicKey)
if (recipient.isGroupRecipient) { return }
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
for (device in devices) {
val endSessionMessage = OutgoingEndSessionMessage(OutgoingTextMessage(recipient, "TERMINATE", 0, -1))
@ -106,7 +104,7 @@ object SessionManagementProtocol {
if (TextSecurePreferences.getRestorationTime(context) > errorTimestamp) {
return ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(publicKey)
}
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(masterDeviceAsRecipient)
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(masterDeviceAsRecipient)
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, publicKey)
}
}

View File

@ -27,7 +27,7 @@ class SessionResetImplementation(private val context: Context) : SessionResetPro
}
val smsDB = DatabaseFactory.getSmsDatabase(context)
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
if (infoMessageID > -1) {

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.protocol.shelved
import android.content.Context
import android.util.Log
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData
@ -132,6 +133,7 @@ object SyncMessagesProtocol {
}
@JvmStatic
@WorkerThread
fun handleOpenGroupSyncMessage(context: Context, content: SignalServiceContent, openGroups: List<PublicChat>) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)

View File

@ -6,7 +6,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.recipients.Recipient
fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String {
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(threadRecipient)
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
val publicKey = recipient.address.toString()
val displayName = if (publicChat != null) {

View File

@ -1,28 +1,42 @@
package org.thoughtcrime.securesms.loki.utilities
import android.content.Context
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.then
import androidx.annotation.WorkerThread
import org.greenrobot.eventbus.EventBus
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
import java.lang.Exception
import java.lang.IllegalStateException
import kotlin.jvm.Throws
//TODO Refactor so methods declare specific type of checked exceptions and not generalized Exception.
object OpenGroupUtilities {
@JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise<PublicChat, Exception> {
// 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 Promise.of(openGroup) }
// Add the new group
val application = ApplicationContext.getInstance(context)
val displayName = TextSecurePreferences.getProfileName(context)
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
return application.publicChatManager.addChat(url, channel).then { group ->
private const val TAG = "OpenGroupUtilities"
@JvmStatic
@WorkerThread
@Throws(Exception::class)
fun addGroup(context: Context, url: String, channel: Long): PublicChat {
// 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
// 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)
@ -31,7 +45,34 @@ object OpenGroupUtilities {
val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context)
val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context)
lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
group
return group
}
}
/**
* Pulls the general public chat data from the server and updates related records.
* Fires [GroupInfoUpdatedEvent] on [EventBus] upon success.
*
* Consider using [org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker] for lazy approach.
*/
@JvmStatic
@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()
publicChatAPI.updateProfileIfNeeded(channel, url, groupId, info, false)
EventBus.getDefault().post(GroupInfoUpdatedEvent(url, channel))
}
data class GroupInfoUpdatedEvent(val url: String, val channel: Long)
}

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.loki.views
import android.content.Context
import android.text.TextUtils
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
@ -10,11 +9,9 @@ import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView
import kotlinx.android.synthetic.main.view_user.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
class UserView : LinearLayout {
var openGroupThreadID: Long = -1 // FIXME: This is a bit ugly
@ -63,7 +60,7 @@ class UserView : LinearLayout {
return result ?: publicKey
}
}
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(user)
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(user)
MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this
val address = user.address.serialize()
profilePictureView.glide = glide

View File

@ -121,6 +121,10 @@ public class Recipient implements RecipientModifiedListener {
if (recipient.isPresent()) consumer.accept(recipient.get());
}
public static boolean removeCached(@NonNull Address address) {
return provider.removeCached(address);
}
Recipient(@NonNull Context context,
@NonNull Address address,
@Nullable Recipient stale,

View File

@ -79,6 +79,10 @@ class RecipientProvider {
return Optional.fromNullable(recipientCache.get(address));
}
boolean removeCached(@NonNull Address address) {
return recipientCache.remove(address);
}
private @NonNull Optional<RecipientDetails> createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address,
@NonNull Optional<RecipientSettings> settings,
@NonNull Optional<GroupRecord> groupRecord)
@ -230,6 +234,10 @@ class RecipientProvider {
cache.put(address, recipient);
}
public synchronized boolean remove(Address address) {
return cache.remove(address) != null;
}
}
}

View File

@ -69,7 +69,7 @@ public class MessageSender {
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
} else {
allocatedThreadId = threadId;
}
@ -94,7 +94,7 @@ public class MessageSender {
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipient(), message.getDistributionType());
allocatedThreadId = threadDatabase.getOrCreateThreadIdFor(message.getRecipient(), message.getDistributionType());
} else {
allocatedThreadId = threadId;
}

View File

@ -64,7 +64,7 @@ public class CommunicationActions {
new AsyncTask<Void, Void, Long>() {
@Override
protected Long doInBackground(Void... voids) {
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
}
@Override

View File

@ -88,7 +88,7 @@ public class IdentityUtil {
smsDatabase.insertMessageInbox(incoming);
} else {
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(group.getGroupId(), false)), true);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
OutgoingTextMessage outgoing ;
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
@ -112,7 +112,7 @@ public class IdentityUtil {
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
else outgoing = new OutgoingIdentityDefaultMessage(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
Log.i(TAG, "Inserting verified outbox...");
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);