From 4307140e5c87cf58955c159c45276a84326945cb Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Fri, 20 Nov 2020 18:59:13 +1100 Subject: [PATCH] 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. --- build.gradle | 2 + .../securesms/GroupCreateActivity.java | 2 +- .../RecipientPreferenceActivity.java | 4 +- .../securesms/components/QuoteView.java | 2 +- .../components/TypingStatusSender.java | 2 +- .../conversation/ConversationActivity.java | 30 ++++---- .../securesms/database/GroupDatabase.java | 26 ++++++- .../securesms/database/MmsDatabase.java | 6 +- .../securesms/database/SmsDatabase.java | 6 +- .../securesms/database/SmsMigrator.java | 4 +- .../securesms/database/ThreadDatabase.java | 6 +- .../loaders/BucketedThreadMediaLoader.java | 2 +- .../database/loaders/PagingMediaLoader.java | 2 +- .../database/loaders/ThreadMediaLoader.java | 2 +- .../securesms/groups/GroupManager.java | 28 +++++++- .../groups/GroupMessageProcessor.java | 4 +- .../securesms/jobs/PushDecryptJob.java | 18 ++--- .../activities/CreateClosedGroupActivity.kt | 2 +- .../securesms/loki/activities/HomeActivity.kt | 8 ++- .../loki/activities/JoinPublicChatActivity.kt | 27 ++++--- .../loki/api/BackgroundPollWorker.kt | 14 ++-- .../loki/api/PublicChatInfoUpdateWorker.kt | 55 ++++++++++++++ .../securesms/loki/api/PublicChatManager.kt | 29 +++++--- .../securesms/loki/api/PublicChatPoller.kt | 4 +- .../loki/database/LokiThreadDatabase.kt | 3 +- .../loki/protocol/ClosedGroupsProtocol.kt | 13 ++-- .../protocol/SessionManagementProtocol.kt | 6 +- .../protocol/SessionResetImplementation.kt | 2 +- .../protocol/shelved/SyncMessagesProtocol.kt | 2 + .../loki/utilities/NotificationUtilities.kt | 2 +- .../loki/utilities/OpenGroupUtilities.kt | 71 +++++++++++++++---- .../securesms/loki/views/UserView.kt | 5 +- .../securesms/recipients/Recipient.java | 4 ++ .../recipients/RecipientProvider.java | 8 +++ .../securesms/sms/MessageSender.java | 4 +- .../securesms/util/CommunicationActions.java | 2 +- .../securesms/util/IdentityUtil.java | 4 +- 37 files changed, 289 insertions(+), 122 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt diff --git a/build.gradle b/build.gradle index 05ce8a73b..5b96847b4 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index 3aeb4111d..2ed0d9b6e 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index 3604d4065..c446f8da7 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -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 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(); diff --git a/src/org/thoughtcrime/securesms/components/QuoteView.java b/src/org/thoughtcrime/securesms/components/QuoteView.java index 112f522ce..b01691fb2 100644 --- a/src/org/thoughtcrime/securesms/components/QuoteView.java +++ b/src/org/thoughtcrime/securesms/components/QuoteView.java @@ -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()))) { diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java index 55fdd513f..d8b070d86 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -90,7 +90,7 @@ public class TypingStatusSender { Set 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)); } } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index d9f463844..402da15c8 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -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), diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index c5bd65e90..b2b9432d4 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -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
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; diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index eccb3600c..d01046adf 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index ee4527d00..6ebd0ec57 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -556,7 +556,7 @@ public class SmsDatabase extends MessagingDatabase { private @NonNull Pair 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()); diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index 5eaae3d17..66e83a36f 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index f58471b9b..20d822f8a 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -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()}; diff --git a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java index 6f50bd161..4cb29cfc9 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java @@ -62,7 +62,7 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader> { @Nullable @Override public Pair 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()) { diff --git a/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java index 0e8fc9a0b..abcff10e1 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java @@ -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); diff --git a/src/org/thoughtcrime/securesms/groups/GroupManager.java b/src/org/thoughtcrime/securesms/groups/GroupManager.java index 694627f62..9ea420da5 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -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 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); } } diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index 4b2549344..80ec6d7ae 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -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); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 8801ce4aa..900ba496a 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -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."); diff --git a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index eb8181274..aff8e7752 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -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() diff --git a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 8a5cb4ae5..e5f84a673 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -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) diff --git a/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt index 60b872883..65ea1f3de 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt @@ -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 \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt b/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt index 66ce90905..27489a285 100644 --- a/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt +++ b/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt @@ -25,10 +25,9 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor @JvmStatic fun scheduleInstant(context: Context) { val workRequest = OneTimeWorkRequestBuilder() - .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(15, TimeUnit.MINUTES) - .setConstraints( - Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() + .setConstraints(Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() ) .build() diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt new file mode 100644 index 000000000..e7b7b9be3 --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt @@ -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() + .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() + } + } +} diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index 6b710c5ea..a4281704e 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -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() @@ -23,7 +25,7 @@ class PublicChatManager(private val context: Context) { private val observers = mutableMapOf() 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 { + //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) diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt index 2d682f07d..e15cb90d7 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt @@ -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 } diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt index 5a4697b00..5c5aadd0e 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt @@ -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 { diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index bed24db12..73b071bc3 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -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 } diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt index 073af39af..93ae8337b 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt @@ -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) } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt b/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt index b8e0fa8e8..900391806 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt @@ -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) { diff --git a/src/org/thoughtcrime/securesms/loki/protocol/shelved/SyncMessagesProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/shelved/SyncMessagesProtocol.kt index 487dfe725..827e321f8 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/shelved/SyncMessagesProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/shelved/SyncMessagesProtocol.kt @@ -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) { val userPublicKey = TextSecurePreferences.getLocalNumber(context) val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey) diff --git a/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt index 69a00115e..2b98a4bc3 100644 --- a/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt @@ -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) { diff --git a/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt index 4cf69ea31..fa23d46b3 100644 --- a/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt @@ -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 { - // 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) } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/views/UserView.kt b/src/org/thoughtcrime/securesms/loki/views/UserView.kt index 14614f512..0f45a9bb0 100644 --- a/src/org/thoughtcrime/securesms/loki/views/UserView.kt +++ b/src/org/thoughtcrime/securesms/loki/views/UserView.kt @@ -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 diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 24c44b19a..be90b012e 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -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, diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index c1f80d804..6d23221fd 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -79,6 +79,10 @@ class RecipientProvider { return Optional.fromNullable(recipientCache.get(address)); } + boolean removeCached(@NonNull Address address) { + return recipientCache.remove(address); + } + private @NonNull Optional createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord) @@ -230,6 +234,10 @@ class RecipientProvider { cache.put(address, recipient); } + public synchronized boolean remove(Address address) { + return cache.remove(address) != null; + } + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 3deb7565d..c59094756 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -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; } diff --git a/src/org/thoughtcrime/securesms/util/CommunicationActions.java b/src/org/thoughtcrime/securesms/util/CommunicationActions.java index db66b955c..c7fcf5c49 100644 --- a/src/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/src/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -64,7 +64,7 @@ public class CommunicationActions { new AsyncTask() { @Override protected Long doInBackground(Void... voids) { - return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); + return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient); } @Override diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java index 38fa1c347..c116009b3 100644 --- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -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);