Conversation activity no longer pulls the public chat info directly.

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

@ -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'

@ -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);
} }

@ -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();

@ -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()))) {

@ -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));
} }
} }

@ -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),

@ -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);
@ -262,7 +274,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
long avatarId; long avatarId;
if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong()); if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong());
else avatarId = 0; else avatarId = 0;
ContentValues contentValues = new ContentValues(2); 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}); 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;

@ -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) {

@ -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());

@ -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);
} }

@ -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()};

@ -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)) {

@ -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()) {

@ -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);

@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
@ -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);
} }
} }

@ -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);

@ -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.");

@ -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()

@ -26,13 +26,12 @@ import kotlinx.android.synthetic.main.activity_home.*
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
@ -343,6 +342,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val recipient = thread.recipient val recipient = thread.recipient
val threadDB = DatabaseFactory.getThreadDatabase(this) val threadDB = DatabaseFactory.getThreadDatabase(this)
val deleteThread = Runnable { val deleteThread = Runnable {
//TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
AsyncTask.execute { AsyncTask.execute {
val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID) val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID)
if (publicChat != null) { if (publicChat != null) {
@ -351,6 +351,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server) apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server) apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(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) threadDB.deleteConversation(threadID)
ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity) ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity)

@ -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 {
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) SyncMessagesProtocol.syncAllOpenGroups(this@JoinPublicChatActivity)
}.successUi { withContext(Dispatchers.Main) { finish() }
finish()
}.failUi {
hideLoader()
Toast.makeText(this, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
} }
} }
// 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

@ -25,10 +25,9 @@ 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()
) )
.build() .build()
@ -41,10 +40,9 @@ 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()
) )
.build() .build()

@ -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()
}
}
}

@ -4,6 +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 androidx.annotation.WorkerThread
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
@ -16,6 +17,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>()
@ -23,7 +25,7 @@ class PublicChatManager(private val context: Context) {
private val observers = mutableMapOf<Long, ContentObserver>() private val observers = mutableMapOf<Long, ContentObserver>()
private var isPolling = false private var isPolling = false
public fun areAllCaughtUp():Boolean { public fun areAllCaughtUp(): Boolean {
var areAllCaughtUp = true var areAllCaughtUp = true
refreshChatsAndPollers() refreshChatsAndPollers()
for ((threadID, chat) in chats) { for ((threadID, chat) in chats) {
@ -58,19 +60,24 @@ 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)
var profilePicture: Bitmap? = null var profilePicture: Bitmap? = null
// Create the group if we don't have one // Create the group if we don't have one
if (threadID < 0) { if (threadID < 0) {
@ -105,7 +112,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 +126,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)

@ -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 }

@ -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 {

@ -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
} }

@ -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)
} }
} }

@ -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) {

@ -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)

@ -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) {

@ -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
val groupID = PublicChat.getId(channel, url) @JvmStatic
val threadID = GroupManager.getOpenGroupThreadID(groupID, context) @WorkerThread
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) @Throws(Exception::class)
if (openGroup != null) { return Promise.of(openGroup) } fun addGroup(context: Context, url: String, channel: Long): PublicChat {
// Add the new group // Check for an existing group.
val application = ApplicationContext.getInstance(context) val groupID = PublicChat.getId(channel, url)
val displayName = TextSecurePreferences.getProfileName(context) val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.") val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
return application.publicChatManager.addChat(url, channel).then { group -> 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).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)
} }

@ -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

@ -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,

@ -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;
}
} }
} }

@ -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;
} }

@ -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

@ -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);