Merge remote-tracking branch 'anton/open-group-info-update-refactoring' into dev

This commit is contained in:
Niels Andriesse 2020-12-04 13:07:43 +11:00
commit a8e67fafb9
37 changed files with 331 additions and 174 deletions

View File

@ -89,7 +89,9 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8: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.work:work-runtime-ktx:2.4.0"
implementation "androidx.core:core-ktx:1.3.2"
implementation ("com.google.firebase:firebase-messaging:18.0.0") { implementation ("com.google.firebase:firebase-messaging:18.0.0") {
exclude group: 'com.google.firebase', module: 'firebase-core' 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)); String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true, Collections.singletonList(local));
Recipient groupRecipient = Recipient.from(activity, Address.fromSerialized(groupId), true); 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); return new GroupActionResult(groupRecipient, threadId);
} }

View File

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

View File

@ -90,7 +90,7 @@ public class TypingStatusSender {
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize()); Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
for (String device : linkedDevices) { for (String device : linkedDevices) {
Recipient deviceAsRecipient = Recipient.from(context, Address.fromSerialized(device), false); 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)); 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.logging.Log;
import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity; import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity;
import org.thoughtcrime.securesms.loki.activities.HomeActivity; 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.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; 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.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView; import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
import org.thoughtcrime.securesms.loki.views.ProfilePictureView; import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView; import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
@ -462,20 +464,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) { if (publicChat != null) {
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(this).getPublicChatAPI(); // Request open group info update and handle the successful result in #onOpenGroupInfoUpdated().
publicChatAPI.getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(info -> { PublicChatInfoUpdateWorker.scheduleInstant(this, publicChat.getServer(), publicChat.getChannel());
String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes());
publicChatAPI.updateProfileIfNeeded(
publicChat.getChannel(),
publicChat.getServer(),
groupId,
info,
false);
runOnUiThread(ConversationActivity.this::updateSubtitleTextView);
return Unit.INSTANCE;
});
} }
View rootView = findViewById(R.id.rootView); View rootView = findViewById(R.id.rootView);
@ -1940,6 +1930,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
.show(TooltipPopup.POSITION_ABOVE); .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() { private void initializeReceivers() {
securityUpdateReceiver = new BroadcastReceiver() { securityUpdateReceiver = new BroadcastReceiver() {
@Override @Override
@ -2095,7 +2095,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
long threadId = params[0]; long threadId = params[0];
if (drafts.size() > 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); draftDatabase.insertDrafts(threadId, drafts);
threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this),

View File

@ -218,6 +218,18 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
notifyConversationListListeners(); 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) { public void update(String groupId, String title, SignalServiceAttachmentPointer avatar) {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
if (title != null) contentValues.put(TITLE, title); if (title != null) contentValues.put(TITLE, title);
@ -300,7 +312,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); 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); List<Address> currentMembers = getCurrentMembers(groupId);
currentMembers.remove(source); currentMembers.remove(source);
@ -352,13 +364,21 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId}); database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId});
} }
public byte[] allocateGroupId() { public byte[] allocateGroupId() {
byte[] groupId = new byte[16]; byte[] groupId = new byte[16];
new SecureRandom().nextBytes(groupId); new SecureRandom().nextBytes(groupId);
return 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 { public static class Reader implements Closeable {
private final Cursor cursor; private final Cursor cursor;

View File

@ -321,10 +321,10 @@ public class MmsDatabase extends MessagingDatabase {
private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException, MmsException { private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException, MmsException {
if (retrieved.getGroupId() != null) { if (retrieved.getGroupId() != null) {
Recipient groupRecipients = Recipient.from(context, retrieved.getGroupId(), true); Recipient groupRecipients = Recipient.from(context, retrieved.getGroupId(), true);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients); return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipients);
} else { } else {
Recipient sender = Recipient.from(context, retrieved.getFrom(), true); 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()) ? Util.toIsoString(notification.getFrom().getTextString())
: ""; : "";
Recipient recipient = Recipient.from(context, Address.fromExternal(context, fromString), false); 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) { 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) { private @NonNull Pair<Long, Long> insertCallLog(@NonNull Address address, long type, boolean unread) {
Recipient recipient = Recipient.from(context, address, true); 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); ContentValues values = new ContentValues(6);
values.put(ADDRESS, address.serialize()); values.put(ADDRESS, address.serialize());
@ -620,8 +620,8 @@ public class SmsDatabase extends MessagingDatabase {
long threadId; long threadId;
if (groupRecipient == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); if (groupRecipient == null) threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); else threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
ContentValues values = new ContentValues(6); ContentValues values = new ContentValues(6);
values.put(ADDRESS, message.getSender().serialize()); values.put(ADDRESS, message.getSender().serialize());

View File

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

View File

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

View File

@ -62,7 +62,7 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMed
@Override @Override
public BucketedThreadMedia loadInBackground() { public BucketedThreadMedia loadInBackground() {
BucketedThreadMedia result = new BucketedThreadMedia(getContext()); 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); DatabaseFactory.getMediaDatabase(getContext()).subscribeToMediaChanges(observer);
try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId)) { try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId)) {

View File

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

View File

@ -23,7 +23,7 @@ public class ThreadMediaLoader extends AbstractCursorLoader {
@Override @Override
public Cursor getCursor() { 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); if (gallery) return DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId);
else return DatabaseFactory.getMediaDatabase(getContext()).getDocumentMediaForThread(threadId); else return DatabaseFactory.getMediaDatabase(getContext()).getDocumentMediaForThread(threadId);

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -89,7 +90,8 @@ public class GroupManager {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses); return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
} else { } 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); return new GroupActionResult(groupRecipient, threadId);
} }
} }
@ -127,10 +129,30 @@ public class GroupManager {
groupDatabase.updateProfilePicture(groupId, avatarBytes); 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); 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, public static GroupActionResult updateGroup(@NonNull Context context,
@NonNull String groupId, @NonNull String groupId,
@NonNull Set<Recipient> members, @NonNull Set<Recipient> members,
@ -154,7 +176,7 @@ public class GroupManager {
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses); return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
} else { } else {
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true); 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); return new GroupActionResult(groupRecipient, threadId);
} }
} }

View File

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

View File

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

View File

@ -9,7 +9,6 @@ import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
@ -18,21 +17,24 @@ import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob 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.ConversationOptionsBottomSheet
import org.thoughtcrime.securesms.loki.dialogs.LightThemeFeatureIntroBottomSheet import org.thoughtcrime.securesms.loki.dialogs.LightThemeFeatureIntroBottomSheet
import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet
@ -72,24 +74,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
// Process any outstanding deletes
val threadDatabase = DatabaseFactory.getThreadDatabase(this)
val archivedConversationCount = threadDatabase.archivedConversationListCount
if (archivedConversationCount > 0) {
val archivedConversations = threadDatabase.archivedConversationList
archivedConversations.moveToFirst()
fun deleteThreadAtCurrentPosition() {
val threadID = archivedConversations.getLong(archivedConversations.getColumnIndex(ThreadDatabase.ID))
AsyncTask.execute {
threadDatabase.deleteConversation(threadID)
(applicationContext as ApplicationContext).messageNotifier.updateNotification(this)
}
}
deleteThreadAtCurrentPosition()
while (archivedConversations.moveToNext()) {
deleteThreadAtCurrentPosition()
}
}
// Double check that the long poller is up // Double check that the long poller is up
(applicationContext as ApplicationContext).startPollingIfNeeded() (applicationContext as ApplicationContext).startPollingIfNeeded()
// Set content view // Set content view
@ -342,53 +326,56 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val threadID = thread.threadId val threadID = thread.threadId
val recipient = thread.recipient val recipient = thread.recipient
val threadDB = DatabaseFactory.getThreadDatabase(this) val threadDB = DatabaseFactory.getThreadDatabase(this)
val deleteThread = Runnable {
AsyncTask.execute {
val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID)
if (publicChat != null) {
val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity)
apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server)
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server)
}
threadDB.deleteConversation(threadID)
ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity)
}
}
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
val dialog = AlertDialog.Builder(this) val dialog = AlertDialog.Builder(this)
dialog.setMessage(dialogMessage) dialog.setMessage(dialogMessage)
dialog.setPositiveButton(R.string.yes) { _, _ -> dialog.setPositiveButton(R.string.yes) { _, _ -> lifecycleScope.launch(Dispatchers.Main) {
val context = this@HomeActivity as Context
val isClosedGroup = recipient.address.isClosedGroup val isClosedGroup = recipient.address.isClosedGroup
// Send a leave group message if this is an active closed group // Send a leave group message if this is an active closed group
if (isClosedGroup && DatabaseFactory.getGroupDatabase(this).isActive(recipient.address.toGroupString())) { if (isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) {
var isSSKBasedClosedGroup: Boolean var isSSKBasedClosedGroup: Boolean
var groupPublicKey: String? var groupPublicKey: String?
try { try {
groupPublicKey = ClosedGroupsProtocol.doubleDecodeGroupID(recipient.address.toString()).toHexString() groupPublicKey = ClosedGroupsProtocol.doubleDecodeGroupID(recipient.address.toString()).toHexString()
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey) isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(groupPublicKey)
} catch (e: IOException) { } catch (e: IOException) {
groupPublicKey = null groupPublicKey = null
isSSKBasedClosedGroup = false isSSKBasedClosedGroup = false
} }
if (isSSKBasedClosedGroup) { if (isSSKBasedClosedGroup) {
ClosedGroupsProtocol.leave(this, groupPublicKey!!) ClosedGroupsProtocol.leave(context, groupPublicKey!!)
} else if (!ClosedGroupsProtocol.leaveLegacyGroup(this, recipient)) { } else if (!ClosedGroupsProtocol.leaveLegacyGroup(context, recipient)) {
Toast.makeText(this, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
return@setPositiveButton return@launch
} }
} }
// Archive the conversation and then delete it after 10 seconds (the case where the
// app was closed before the conversation could be deleted is handled in onCreate) withContext(Dispatchers.IO) {
threadDB.archiveConversation(threadID) val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
val delay = if (isClosedGroup) 10000L else 1000L //TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
val handler = Handler() if (publicChat != null) {
handler.postDelayed(deleteThread, delay) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server)
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
ApplicationContext.getInstance(context).publicChatAPI!!
.leave(publicChat.channel, publicChat.server)
ApplicationContext.getInstance(context).publicChatManager
.removeChat(publicChat.server, publicChat.channel)
} else {
threadDB.deleteConversation(threadID)
}
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context)
}
// Notify the user // Notify the user
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show() Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show()
} }}
dialog.setNegativeButton(R.string.no) { _, _ -> dialog.setNegativeButton(R.string.no) { _, _ ->
// Do nothing // Do nothing
} }

View File

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

View File

@ -25,8 +25,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
@JvmStatic @JvmStatic
fun scheduleInstant(context: Context) { fun scheduleInstant(context: Context) {
val workRequest = OneTimeWorkRequestBuilder<BackgroundPollWorker>() val workRequest = OneTimeWorkRequestBuilder<BackgroundPollWorker>()
.setConstraints( .setConstraints(Constraints.Builder()
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()
) )
@ -41,8 +40,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
fun schedulePeriodic(context: Context) { fun schedulePeriodic(context: Context) {
Log.v(TAG, "Scheduling periodic work.") Log.v(TAG, "Scheduling periodic work.")
val workRequest = PeriodicWorkRequestBuilder<BackgroundPollWorker>(15, TimeUnit.MINUTES) val workRequest = PeriodicWorkRequestBuilder<BackgroundPollWorker>(15, TimeUnit.MINUTES)
.setConstraints( .setConstraints(Constraints.Builder()
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .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,9 +4,7 @@ import android.content.Context
import android.database.ContentObserver import android.database.ContentObserver
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import nl.komponents.kovenant.Promise import androidx.annotation.WorkerThread
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
@ -16,6 +14,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatInfo import org.whispersystems.signalservice.loki.api.opengroups.PublicChatInfo
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
import kotlin.jvm.Throws
class PublicChatManager(private val context: Context) { class PublicChatManager(private val context: Context) {
private var chats = mutableMapOf<Long, PublicChat>() private var chats = mutableMapOf<Long, PublicChat>()
@ -58,16 +57,21 @@ class PublicChatManager(private val context: Context) {
isPolling = false 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 val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI
?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!")) ?: throw IllegalStateException("LokiPublicChatAPI is not set!")
return groupChatAPI.getAuthToken(server).bind {
groupChatAPI.getChannelInfo(channel, server) // Ensure the auth token is acquired.
}.map { groupChatAPI.getAuthToken(server).get()
addChat(server, channel, it)
} val channelInfo = groupChatAPI.getChannelInfo(channel, server).get()
return addChat(server, channel, channelInfo)
} }
@WorkerThread
public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat { public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat {
val chat = PublicChat(channel, server, info.displayName, true) val chat = PublicChat(channel, server, info.displayName, true)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
@ -94,6 +98,16 @@ class PublicChatManager(private val context: Context) {
return chat return chat
} }
public fun removeChat(server: String, channel: Long) {
val threadDB = DatabaseFactory.getThreadDatabase(context)
val groupId = PublicChat.getId(channel, server)
val threadId = GroupManager.getOpenGroupThreadID(groupId, context)
val groupAddress = threadDB.getRecipientForThreadId(threadId)!!.address.serialize()
GroupManager.deleteGroup(groupAddress, context)
Util.runOnMain { startPollersIfNeeded() }
}
private fun refreshChatsAndPollers() { private fun refreshChatsAndPollers() {
val chatsInDB = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats() val chatsInDB = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats()
val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) } val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) }
@ -105,7 +119,7 @@ class PublicChatManager(private val context: Context) {
private fun listenToThreadDeletion(threadID: Long) { private fun listenToThreadDeletion(threadID: Long) {
if (threadID < 0 || observers[threadID] != null) { return } if (threadID < 0 || observers[threadID] != null) { return }
val observer = createDeletionObserver(threadID, Runnable { val observer = createDeletionObserver(threadID) {
val chat = chats[threadID] val chat = chats[threadID]
// Reset last message cache // Reset last message cache
@ -119,7 +133,7 @@ class PublicChatManager(private val context: Context) {
pollers.remove(threadID)?.stop() pollers.remove(threadID)?.stop()
observers.remove(threadID) observers.remove(threadID)
startPollersIfNeeded() startPollersIfNeeded()
}) }
observers[threadID] = observer observers[threadID] = observer
context.applicationContext.contentResolver.registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadID), true, 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) val messageID = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID)
var isDuplicate = false var isDuplicate = false
if (messageID != null) { if (messageID != null) {
isDuplicate = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageID) > 0 isDuplicate = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageID) >= 0
|| DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID) > 0 || DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID) >= 0
} }
if (isDuplicate) { return } if (isDuplicate) { return }
if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { 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.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.util.Log
import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
@ -34,7 +33,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
override fun getThreadID(hexEncodedPublicKey: String): Long { override fun getThreadID(hexEncodedPublicKey: String): Long {
val address = Address.fromSerialized(hexEncodedPublicKey) val address = Address.fromSerialized(hexEncodedPublicKey)
val recipient = Recipient.from(context, address, false) val recipient = Recipient.from(context, address, false)
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient) return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
} }
fun getThreadID(messageID: Long): Long { 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.api.messages.SignalServiceGroup.GroupType
import org.whispersystems.signalservice.internal.push.SignalServiceProtos import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext 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.ClosedGroupRatchet
import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType
import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupSenderKey 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 // Add the group to the user's set of public keys to poll for
DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey)
// Notify the user // 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) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
// Notify the PN server // Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
@ -166,7 +165,7 @@ object ClosedGroupsProtocol {
if (isUserLeaving) { if (isUserLeaving) {
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
groupDB.setActive(groupID, false) groupDB.setActive(groupID, false)
groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
// Notify the PN server // Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
} else { } else {
@ -230,7 +229,7 @@ object ClosedGroupsProtocol {
} }
// Notify the user // Notify the user
val infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE 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) insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
deferred.resolve(Unit) deferred.resolve(Unit)
}.start() }.start()
@ -385,7 +384,7 @@ object ClosedGroupsProtocol {
if (wasCurrentUserRemoved) { if (wasCurrentUserRemoved) {
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
groupDB.setActive(groupID, false) groupDB.setActive(groupID, false)
groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
// Notify the PN server // Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
} else { } else {
@ -510,7 +509,7 @@ object ClosedGroupsProtocol {
@JvmStatic @JvmStatic
fun leaveLegacyGroup(context: Context, recipient: Recipient): Boolean { fun leaveLegacyGroup(context: Context, recipient: Recipient): Boolean {
if (!recipient.address.isClosedGroup) { return true } 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() val message = GroupUtil.createGroupLeaveMessage(context, recipient).orNull()
if (threadID < 0 || message == null) { return false } if (threadID < 0 || message == null) { return false }
MessageSender.send(context, message, threadID, false, null) MessageSender.send(context, message, threadID, false, null)
@ -522,7 +521,7 @@ object ClosedGroupsProtocol {
val groupDatabase = DatabaseFactory.getGroupDatabase(context) val groupDatabase = DatabaseFactory.getGroupDatabase(context)
val groupID = recipient.address.toGroupString() val groupID = recipient.address.toGroupString()
groupDatabase.setActive(groupID, false) groupDatabase.setActive(groupID, false)
groupDatabase.remove(groupID, Address.fromSerialized(userPublicKey)) groupDatabase.removeMember(groupID, Address.fromSerialized(userPublicKey))
return true return true
} }

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.loki.protocol package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import android.os.AsyncTask
import android.util.Log import android.util.Log
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil 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.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
import org.thoughtcrime.securesms.loki.utilities.recipient import org.thoughtcrime.securesms.loki.utilities.recipient
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage
import org.thoughtcrime.securesms.sms.OutgoingTextMessage import org.thoughtcrime.securesms.sms.OutgoingTextMessage
@ -28,7 +26,7 @@ object SessionManagementProtocol {
val recipient = recipient(context, publicKey) val recipient = recipient(context, publicKey)
if (recipient.isGroupRecipient) { return } if (recipient.isGroupRecipient) { return }
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context) val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient) val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
val devices = lokiThreadDB.getSessionRestoreDevices(threadID) val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
for (device in devices) { for (device in devices) {
val endSessionMessage = OutgoingEndSessionMessage(OutgoingTextMessage(recipient, "TERMINATE", 0, -1)) val endSessionMessage = OutgoingEndSessionMessage(OutgoingTextMessage(recipient, "TERMINATE", 0, -1))
@ -106,7 +104,7 @@ object SessionManagementProtocol {
if (TextSecurePreferences.getRestorationTime(context) > errorTimestamp) { if (TextSecurePreferences.getRestorationTime(context) > errorTimestamp) {
return ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(publicKey) 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) 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 smsDB = DatabaseFactory.getSmsDatabase(context)
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) 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 infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null) val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
if (infoMessageID > -1) { if (infoMessageID > -1) {

View File

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

View File

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

View File

@ -1,28 +1,42 @@
package org.thoughtcrime.securesms.loki.utilities package org.thoughtcrime.securesms.loki.utilities
import android.content.Context import android.content.Context
import nl.komponents.kovenant.Promise import androidx.annotation.WorkerThread
import nl.komponents.kovenant.then import org.greenrobot.eventbus.EventBus
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat 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 { object OpenGroupUtilities {
@JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise<PublicChat, Exception> { private const val TAG = "OpenGroupUtilities"
// Check for an existing group
@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 groupID = PublicChat.getId(channel, url)
val threadID = GroupManager.getOpenGroupThreadID(groupID, context) val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
if (openGroup != null) { return Promise.of(openGroup) } if (openGroup != null) return openGroup
// Add the new group
// Add the new group.
val application = ApplicationContext.getInstance(context) val application = ApplicationContext.getInstance(context)
val displayName = TextSecurePreferences.getProfileName(context) val displayName = TextSecurePreferences.getProfileName(context)
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.") val lokiPublicChatAPI = application.publicChatAPI
return application.publicChatManager.addChat(url, channel).then { group -> ?: throw IllegalStateException("LokiPublicChatAPI is not initialized.")
val group = application.publicChatManager.addChat(url, channel)
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url) DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url) DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
lokiPublicChatAPI.getMessages(channel, url) lokiPublicChatAPI.getMessages(channel, url)
@ -31,7 +45,34 @@ object OpenGroupUtilities {
val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context) val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context)
val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context) val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context)
lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl) 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 package org.thoughtcrime.securesms.loki.views
import android.content.Context import android.content.Context
import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View 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 kotlinx.android.synthetic.main.view_user.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
class UserView : LinearLayout { class UserView : LinearLayout {
var openGroupThreadID: Long = -1 // FIXME: This is a bit ugly var openGroupThreadID: Long = -1 // FIXME: This is a bit ugly
@ -63,7 +60,7 @@ class UserView : LinearLayout {
return result ?: publicKey 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 MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this
val address = user.address.serialize() val address = user.address.serialize()
profilePictureView.glide = glide profilePictureView.glide = glide

View File

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

View File

@ -79,6 +79,10 @@ class RecipientProvider {
return Optional.fromNullable(recipientCache.get(address)); 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, private @NonNull Optional<RecipientDetails> createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address,
@NonNull Optional<RecipientSettings> settings, @NonNull Optional<RecipientSettings> settings,
@NonNull Optional<GroupRecord> groupRecord) @NonNull Optional<GroupRecord> groupRecord)
@ -230,6 +234,10 @@ class RecipientProvider {
cache.put(address, recipient); 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; long allocatedThreadId;
if (threadId == -1) { if (threadId == -1) {
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
} else { } else {
allocatedThreadId = threadId; allocatedThreadId = threadId;
} }
@ -94,7 +94,7 @@ public class MessageSender {
long allocatedThreadId; long allocatedThreadId;
if (threadId == -1) { if (threadId == -1) {
allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipient(), message.getDistributionType()); allocatedThreadId = threadDatabase.getOrCreateThreadIdFor(message.getRecipient(), message.getDistributionType());
} else { } else {
allocatedThreadId = threadId; allocatedThreadId = threadId;
} }

View File

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

View File

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