Distinguish public chat and rss feed from regular groups

This commit is contained in:
Mikunj 2019-12-12 10:07:17 +11:00
parent 97cde203ca
commit 33788189dd
21 changed files with 220 additions and 115 deletions

View File

@ -529,7 +529,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void createDefaultPublicChatsIfNeeded() {
List<LokiPublicChat> defaultPublicChats = LokiPublicChatAPI.Companion.getDefaultChats(BuildConfig.DEBUG);
for (LokiPublicChat publiChat : defaultPublicChats) {
long threadID = GroupManager.getThreadId(publiChat.getId(), this);
long threadID = GroupManager.getPublicChatThreadId(publiChat.getId(), this);
String migrationKey = publiChat.getId() + "_migrated";
boolean isChatMigrated = TextSecurePreferences.getBooleanPreference(this, migrationKey, false);
boolean isChatSetUp = TextSecurePreferences.isChatSetUp(this, publiChat.getId());
@ -552,7 +552,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
for (LokiRSSFeed feed : feeds) {
boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId());
if (!isFeedSetUp || !feed.isDeletable()) {
GroupManager.createGroup(feed.getId(), this, new HashSet<>(), null, feed.getDisplayName(), false);
GroupManager.createRSSFeedGroup(feed.getId(), this, null, feed.getDisplayName());
TextSecurePreferences.markChatSetUp(this, feed.getId());
}
}
@ -561,7 +561,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private void createRSSFeedPollersIfNeeded() {
// Only create the RSS feed pollers if their threads aren't deleted
LokiRSSFeed lokiNewsFeed = lokiNewsFeed();
long lokiNewsFeedThreadID = GroupManager.getThreadId(lokiNewsFeed.getId(), this);
long lokiNewsFeedThreadID = GroupManager.getRSSFeedThreadId(lokiNewsFeed.getId(), this);
if (lokiNewsFeedThreadID >= 0 && lokiNewsFeedPoller == null) {
lokiNewsFeedPoller = new LokiRSSFeedPoller(this, lokiNewsFeed);
// Set up deletion listeners if needed

View File

@ -442,10 +442,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, this);
if (this.recipient.isGroupRecipient()) {
if (this.recipient.getName().equals("Loki Public Chat")) {
Analytics.Companion.getShared().track("Loki Public Chat Opened");
} else {
if (this.recipient.getAddress().isPublicChat()) {
Analytics.Companion.getShared().track("Public Chat Opened");
} else if (this.recipient.getAddress().isRSSFeed()) {
Analytics.Companion.getShared().track("RSS Feed Opened");
} else {
Analytics.Companion.getShared().track("Private Group Chat Opened");
}
} else {
Analytics.Companion.getShared().track("Conversation Opened");
@ -687,9 +689,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater();
menu.clear();
boolean isLokiPublicChat = isGroupConversation(); // TODO: Figure out a better way of determining this
boolean isLokiGroupChat = recipient.getAddress().isPublicChat() || recipient.getAddress().isRSSFeed();
if (isSecureText && !isLokiPublicChat) { // TODO:
if (isSecureText && !isLokiGroupChat) {
if (recipient.getExpireMessages() > 0) {
inflater.inflate(R.menu.conversation_expiring_on, menu);
@ -709,7 +711,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
*/
} else if (isGroupConversation() && !isLokiPublicChat) {
} else if (isGroupConversation() && !isLokiGroupChat) {
inflater.inflate(R.menu.conversation_group_options, menu);
if (!isPushGroupConversation()) {
@ -2007,7 +2009,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void setBlockedUserState(Recipient recipient, boolean isSecureText, boolean isDefaultSms) {
if (recipient.isGroupRecipient() && (recipient.getName().equals("Loki News") || recipient.getName().equals("Loki Messenger Updates"))) {
if (recipient.isGroupRecipient() && recipient.getAddress().isRSSFeed()) {
unblockButton.setVisibility(View.GONE);
composePanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
@ -2036,7 +2038,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void setGroupShareProfileReminder(@NonNull Recipient recipient) {
if (recipient.isPushGroupRecipient() && !recipient.isProfileSharing()) {
if (recipient.isPushGroupRecipient() && !recipient.isProfileSharing() && !recipient.getAddress().isPublicChat() && !recipient.getAddress().isRSSFeed()) {
groupShareProfileView.get().setRecipient(recipient);
groupShareProfileView.get().setVisibility(View.VISIBLE);
} else if (groupShareProfileView.resolved()) {

View File

@ -52,17 +52,9 @@ public class Address implements Parcelable, Comparable<Address> {
private final String address;
// Loki - Special flag to indicate whether this address represents a public chat or not
private Boolean isPublicChat;
private Address(@NonNull String address) {
this(address, false);
}
private Address(@NonNull String address, Boolean isPublicChat) {
if (address == null) throw new AssertionError(address);
this.address = address.toLowerCase();
this.isPublicChat = isPublicChat;
}
public Address(Parcel in) {
@ -77,10 +69,6 @@ public class Address implements Parcelable, Comparable<Address> {
return Address.fromSerialized(external);
}
public static @NonNull Address fromPublicChatGroupID(@NonNull String serialized) {
return new Address(serialized, true);
}
public static @NonNull List<Address> fromSerializedList(@NonNull String serialized, char delimiter) {
String[] escapedAddresses = DelimiterUtil.split(serialized, delimiter);
List<Address> addresses = new LinkedList<>();
@ -121,13 +109,15 @@ public class Address implements Parcelable, Comparable<Address> {
}
}
public boolean isGroup() {
return GroupUtil.isEncodedGroup(address);
}
public boolean isGroup() { return GroupUtil.isEncodedGroup(address); }
public boolean isMmsGroup() {
return GroupUtil.isMmsGroup(address);
}
public boolean isSignalGroup() { return !isPublicChat() && !isRSSFeed(); }
public boolean isPublicChat() { return GroupUtil.isPublicChat(address); }
public boolean isRSSFeed() { return GroupUtil.isRssFeed(address); }
public boolean isMmsGroup() { return GroupUtil.isMmsGroup(address); }
public boolean isEmail() {
return NumberUtil.isValidEmail(address);
@ -143,7 +133,7 @@ public class Address implements Parcelable, Comparable<Address> {
}
public @NonNull String toPhoneString() {
if (!isPhone() && !isPublicChat) {
if (!isPhone() && !isPublicChat()) {
if (isEmail()) throw new AssertionError("Not e164, is email");
if (isGroup()) throw new AssertionError("Not e164, is group");
throw new AssertionError("Not e164, unknown");

View File

@ -448,6 +448,10 @@ public class GroupDatabase extends Database {
return mms;
}
public boolean isPublicChat() { return Address.fromSerialized(id).isPublicChat(); }
public boolean isRSSFeed() { return Address.fromSerialized(id).isRSSFeed(); }
public String getUrl() { return url; }
}
}

View File

@ -37,10 +37,14 @@ import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.*;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
import java.io.File;
import java.security.acl.Group;
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@ -71,8 +75,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV2 = 23;
private static final int lokiV3 = 24;
private static final int lokiV4 = 25;
private static final int lokiV5 = 26;
private static final int DATABASE_VERSION = lokiV4; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV5; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -510,6 +515,40 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
}
if (oldVersion < lokiV5) {
// Migrate public chats from __textsecure_group__ to __loki_public_chat_group__
try (Cursor lokiPublicChatCursor = db.rawQuery("SELECT public_chat FROM loki_public_chat_database", null)) {
while (lokiPublicChatCursor != null && lokiPublicChatCursor.moveToNext()) {
String chatString = lokiPublicChatCursor.getString(0);
LokiPublicChat publicChat = LokiPublicChat.fromJSON(chatString);
if (publicChat != null) {
byte[] groupId = publicChat.getId().getBytes();
String oldId = GroupUtil.getEncodedId(groupId, false);
String newId = GroupUtil.getEncodedPublicChatId(groupId);
ContentValues threadUpdate = new ContentValues();
threadUpdate.put("recipient_ids", newId);
db.update("thread", threadUpdate, "recipient_ids = ?", new String[]{ oldId });
ContentValues groupUpdate = new ContentValues();
groupUpdate.put("group_id", newId);
db.update("groups", groupUpdate,"group_id = ?", new String[] { oldId });
}
}
}
// Migrate rss feeds from __textsecure_group__ to __loki_rss_feed_group__
String[] rssFeedIds = new String[] { "loki.network.feed", "loki.network.messenger-updates.feed" };
for (String groupId : rssFeedIds) {
String oldId = GroupUtil.getEncodedId(groupId.getBytes(), false);
String newId = GroupUtil.getEncodedRSSFeedId(groupId.getBytes());
ContentValues threadUpdate = new ContentValues();
threadUpdate.put("recipient_ids", newId);
db.update("thread", threadUpdate, "recipient_ids = ?", new String[]{ oldId });
ContentValues groupUpdate = new ContentValues();
groupUpdate.put("group_id", newId);
db.update("groups", groupUpdate,"group_id = ?", new String[] { oldId });
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

View File

@ -36,8 +36,13 @@ import java.util.Set;
public class GroupManager {
public static long getThreadId(String id, @NonNull Context context) {
final String groupId = GroupUtil.getEncodedId(id.getBytes(), false);
public static long getPublicChatThreadId(String id, @NonNull Context context) {
final String groupId = GroupUtil.getEncodedPublicChatId(id.getBytes());
return getThreadIdFromGroupId(groupId, context);
}
public static long getRSSFeedThreadId(String id, @NonNull Context context) {
final String groupId = GroupUtil.getEncodedRSSFeedId(id.getBytes());
return getThreadIdFromGroupId(groupId, context);
}
@ -73,16 +78,6 @@ public class GroupManager {
memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null);
if (!mms) {
groupDatabase.updateAvatar(groupId, avatarBytes);
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadId);
/* Loki: Original Code
==================
if (!mms) {
groupDatabase.updateAvatar(groupId, avatarBytes);
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
@ -91,7 +86,43 @@ public class GroupManager {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadId);
}
*/
}
public static @NonNull GroupActionResult createPublicChatGroup(@NonNull String id,
@NonNull Context context,
@Nullable Bitmap avatar,
@Nullable String name)
{
final String groupId = GroupUtil.getEncodedPublicChatId(id.getBytes());
return createLokiGroup(groupId, context, avatar, name);
}
public static @NonNull GroupActionResult createRSSFeedGroup(@NonNull String id,
@NonNull Context context,
@Nullable Bitmap avatar,
@Nullable String name)
{
final String groupId = GroupUtil.getEncodedRSSFeedId(id.getBytes());
return createLokiGroup(groupId, context, avatar, name);
}
private static @NonNull GroupActionResult createLokiGroup(@NonNull String groupId,
@NonNull Context context,
@Nullable Bitmap avatar,
@Nullable String name)
{
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false);
final Set<Address> memberAddresses = new HashSet<>();
memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null);
groupDatabase.updateAvatar(groupId, avatarBytes);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadId);
}
public static GroupActionResult updateGroup(@NonNull Context context,

View File

@ -58,7 +58,7 @@ public class GroupMessageProcessor {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
SignalServiceGroup group = message.getGroupInfo().get();
String id = GroupUtil.getEncodedId(group.getGroupId(), false);
String id = GroupUtil.getEncodedId(group);
Optional<GroupRecord> record = database.getGroup(id);
if (record.isPresent() && group.getType() == Type.UPDATE) {
@ -81,7 +81,7 @@ public class GroupMessageProcessor {
boolean outgoing)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group.getGroupId(), false);
String id = GroupUtil.getEncodedId(group);
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
@ -108,7 +108,7 @@ public class GroupMessageProcessor {
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group.getGroupId(), false);
String id = GroupUtil.getEncodedId(group);
Set<Address> recordMembers = new HashSet<>(groupRecord.getMembers());
Set<Address> messageMembers = new HashSet<>();
@ -179,7 +179,7 @@ public class GroupMessageProcessor {
boolean outgoing)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group.getGroupId(), false);
String id = GroupUtil.getEncodedId(group);
List<Address> members = record.getMembers();
GroupContext.Builder builder = createGroupContext(group);
@ -204,14 +204,14 @@ public class GroupMessageProcessor {
{
if (group.getAvatar().isPresent()) {
ApplicationContext.getInstance(context).getJobManager()
.add(new AvatarDownloadJob(group.getGroupId()));
.add(new AvatarDownloadJob(GroupUtil.getEncodedId(group)));
}
try {
if (outgoing) {
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false));
Recipient recipient = Recipient.from(context, addres, false);
Address address = Address.fromExternal(context, GroupUtil.getEncodedId(group));
Recipient recipient = Recipient.from(context, address, false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList(), Collections.emptyList());
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);

View File

@ -40,9 +40,9 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
@Inject SignalServiceMessageReceiver receiver;
private byte[] groupId;
private String groupId;
public AvatarDownloadJob(@NonNull byte[] groupId) {
public AvatarDownloadJob(@NonNull String groupId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(10)
@ -50,14 +50,14 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
groupId);
}
private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull byte[] groupId) {
private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull String groupId) {
super(parameters);
this.groupId = groupId;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)).build();
return new Data.Builder().putString(KEY_GROUP_ID, groupId).build();
}
@Override
@ -67,9 +67,8 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
@Override
public void onRun() throws IOException {
String encodeId = GroupUtil.getEncodedId(groupId, false);
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
Optional<GroupRecord> record = database.getGroup(encodeId);
Optional<GroupRecord> record = database.getGroup(groupId);
File attachment = null;
try {
@ -97,7 +96,7 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
database.updateAvatar(encodeId, avatar);
database.updateAvatar(groupId, avatar);
inputStream.close();
}
} catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) {
@ -120,11 +119,7 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
public static final class Factory implements Job.Factory<AvatarDownloadJob> {
@Override
public @NonNull AvatarDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
try {
return new AvatarDownloadJob(parameters, GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID)));
} catch (IOException e) {
throw new AssertionError(e);
}
return new AvatarDownloadJob(parameters, data.getString(KEY_GROUP_ID));
}
}
}

View File

@ -85,7 +85,7 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
reader = DatabaseFactory.getGroupDatabase(context).getGroups();
while ((record = reader.getNext()) != null) {
if (!record.isMms()) {
if (!record.isMms() && !record.isPublicChat() && !record.isRSSFeed()) {
List<String> members = new LinkedList<>();
for (Address member : record.getMembers()) {

View File

@ -350,7 +350,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
else if (message.getBody().isPresent())
handleTextMessage(content, message, smsMessageId, Optional.absent());
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
handleUnknownGroupMessage(content, message.getGroupInfo().get());
}
@ -606,9 +606,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleUnknownGroupMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group)
{
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RequestGroupInfoJob(content.getSender(), group.getGroupId()));
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RequestGroupInfoJob(content.getSender(), group.getGroupId()));
}
}
private void handleExpirationUpdate(@NonNull SignalServiceContent content,
@ -733,7 +735,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
threadId = handleSynchronizeSentTextMessage(message);
}
if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) {
if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get()))) {
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
}
@ -743,7 +745,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Recipient recipient = null;
if (message.getDestination().isPresent()) recipient = Recipient.from(context, Address.fromSerialized(message.getDestination().get()), false);
else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)), false);
else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false);
if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) {
@ -1571,10 +1573,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
long threadId;
if (typingMessage.getGroupId().isPresent()) {
// Typing messages should only apply to signal groups, thus we use `getEncodedId`
Address groupAddress = Address.fromSerialized(GroupUtil.getEncodedId(typingMessage.getGroupId().get(), false));
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else {
// See if we need to redirect the message
author = getPrimaryDeviceRecipient(content.getSender());
@ -1723,7 +1726,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private Recipient getSyncMessageDestination(SentTranscriptMessage message) {
if (message.getMessage().getGroupInfo().isPresent()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)), false);
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false);
} else {
return Recipient.from(context, Address.fromSerialized(message.getDestination().get()), false);
}
@ -1739,7 +1742,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.getGroupInfo().isPresent()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false);
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
} else {
return Recipient.from(context, Address.fromSerialized(content.getSender()), false);
}
@ -1804,7 +1807,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return true;
} else if (conversation.isGroupRecipient()) {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Optional<String> groupId = message.getGroupInfo().isPresent() ? Optional.of(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))
Optional<String> groupId = message.getGroupInfo().isPresent() ? Optional.of(GroupUtil.getEncodedId(message.getGroupInfo().get()))
: Optional.absent();
if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) {

View File

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
@ -231,7 +232,15 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
throws IOException, UntrustedIdentityException, UndeliverableMessageException {
// rotateSenderCertificateIfNecessary();
String groupId = message.getRecipient().getAddress().toGroupString();
// Messages shouldn't be able to be sent to RSS Feeds
Address groupAddress = message.getRecipient().getAddress();
if (groupAddress.isRSSFeed()) {
List<SendMessageResult> results = new ArrayList<>();
for (Address destination : destinations) results.add(SendMessageResult.networkFailure(new SignalServiceAddress(destination.toPhoneString())));
return results;
}
String groupId = groupAddress.toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
Optional<Quote> quote = getQuoteFor(message);
Optional<SignalServiceDataMessage.Sticker> sticker = getStickerFor(message);
@ -247,24 +256,28 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient))
.toList();
if (message.isGroup()) {
SignalServiceGroup.GroupType groupType = SignalServiceGroup.GroupType.SIGNAL;
if (groupAddress.isPublicChat()) {
groupType = SignalServiceGroup.GroupType.PUBLIC_CHAT;
}
if (message.isGroup() && groupAddress.isSignalGroup()) {
// Loki - Only send GroupUpdate or GroupQuit to signal groups
OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message;
GroupContext groupContext = groupMessage.getGroupContext();
SignalServiceAttachment avatar = attachmentPointers.isEmpty() ? null : attachmentPointers.get(0);
SignalServiceGroup.Type type = groupMessage.isGroupQuit() ? SignalServiceGroup.Type.QUIT : SignalServiceGroup.Type.UPDATE;
SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedId(groupId), groupContext.getName(), groupContext.getMembersList(), avatar);
SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedId(groupId), groupType, groupContext.getName(), groupContext.getMembersList(), avatar);
SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getSentTimeMillis())
.withExpiration(message.getRecipient().getExpireMessages())
.withBody(message.getBody())
.asGroupMessage(group)
.build();
// Loki - Disable group updates for now
List<SendMessageResult> results = new ArrayList<>();
for (Address destination : destinations) results.add(SendMessageResult.success(new SignalServiceAddress(destination.toPhoneString()), false, false));
return results;
return messageSender.sendMessage(messageId, addresses, unidentifiedAccess, groupDataMessage);
} else {
SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId));
SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId), groupType);
SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getSentTimeMillis())
.asGroupMessage(group)
@ -284,26 +297,24 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
}
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
ArrayList<Address> result = new ArrayList<>();
if (GroupUtil.isRssFeed(groupId)) { return new ArrayList<>(); }
// Loki - All group messages should be directed to their respective servers
long threadID = GroupManager.getThreadIdFromGroupId(groupId, context);
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID);
if (publicChat != null) {
// We need to somehow maintain information that will allow the sender to map
// a recipient to the correct public chat thread, and so this might be a bit hacky
result.add(Address.fromPublicChatGroupID(groupId));
// Loki - All public chat group messages should be directed to their respective servers
if (GroupUtil.isPublicChat(groupId)) {
ArrayList<Address> result = new ArrayList<>();
long threadID = GroupManager.getThreadIdFromGroupId(groupId, context);
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID);
if (publicChat != null) {
result.add(Address.fromSerialized(groupId));
}
return result;
} else {
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList();
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
return Stream.of(members).map(Recipient::getAddress).toList();
}
return result;
/*
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList();
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
return Stream.of(members).map(Recipient::getAddress).toList();
*/
}
public static class Factory implements Job.Factory<PushGroupSendJob> {

View File

@ -104,7 +104,7 @@ public class PushGroupUpdateJob extends BaseJob implements InjectableType {
SignalServiceGroup groupContext = SignalServiceGroup.newBuilder(Type.UPDATE)
.withAvatar(avatar)
.withId(groupId)
.withId(groupId, SignalServiceGroup.GroupType.SIGNAL)
.withMembers(members)
.withName(record.get().getTitle())
.build();

View File

@ -71,7 +71,7 @@ public class RequestGroupInfoJob extends BaseJob implements InjectableType {
@Override
public void onRun() throws IOException, UntrustedIdentityException {
SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO)
.withId(groupId)
.withId(groupId, SignalServiceGroup.GroupType.SIGNAL)
.build();
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()

View File

@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiPublicChat
import java.util.*
class LokiPublicChatManager(private val context: Context) {
private var chats = mutableMapOf<Long, LokiPublicChat>()
@ -49,10 +48,10 @@ class LokiPublicChatManager(private val context: Context) {
public fun addChat(server: String, channel: Long, name: String): LokiPublicChat {
val chat = LokiPublicChat(channel, server, name, true)
var threadID = GroupManager.getThreadId(chat.id, context)
var threadID = GroupManager.getPublicChatThreadId(chat.id, context)
// Create the group if we don't have one
if (threadID < 0) {
val result = GroupManager.createGroup(chat.id, context, HashSet(), null, chat.displayName, false)
val result = GroupManager.createPublicChatGroup(chat.id, context, null, chat.displayName)
threadID = result.threadId
}
DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID)
@ -73,7 +72,7 @@ class LokiPublicChatManager(private val context: Context) {
removedChatThreadIds.forEach { pollers.remove(it)?.stop() }
// Only append to chats if we have a thread for the chat
chats = chatsInDB.filter { GroupManager.getThreadId(it.value.id, context) > -1 }.toMutableMap()
chats = chatsInDB.filter { GroupManager.getPublicChatThreadId(it.value.id, context) > -1 }.toMutableMap()
}
private fun listenToThreadDeletion(threadID: Long) {

View File

@ -112,7 +112,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
// region Polling
private fun getDataMessage(message: LokiPublicChatMessage): SignalServiceDataMessage {
val id = group.id.toByteArray()
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null)
val quote = if (message.quote != null) {
SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf())
} else {

View File

@ -64,7 +64,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
bodyAsHTML = matcher.replaceAll("$2 ($1)")
val body = Html.fromHtml(bodyAsHTML).toString().trim()
val id = feed.id.toByteArray()
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null)
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false)
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())

View File

@ -79,7 +79,7 @@ public class IncomingMediaMessage {
this.quote = quote.orNull();
this.unidentified = unidentified;
if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false));
if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get()));
else this.groupId = null;
this.attachments.addAll(PointerAttachment.forPointers(attachments));

View File

@ -78,7 +78,7 @@ public class IncomingTextMessage implements Parcelable {
this.unidentified = unidentified;
if (group.isPresent()) {
this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false));
this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get()));
} else {
this.groupId = null;
}

View File

@ -202,7 +202,7 @@ public class MessageSender {
if (attachment != null) { message.getAttachments().add(attachment); }
long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
// Loki - Set the message's friend request status as soon as it has hit the database
if (message.isFriendRequest) {
if (message.isFriendRequest && !recipient.getAddress().isGroup()) {
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId);
}
sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());
@ -215,7 +215,7 @@ public class MessageSender {
try {
long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
// Loki - Set the message's friend request status as soon as it has hit the database
if (message.isFriendRequest) {
if (message.isFriendRequest && !recipient.getAddress().isGroup()) {
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId);
}
sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());

View File

@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import java.io.IOException;
import java.util.Collections;
@ -28,12 +29,32 @@ public class GroupUtil {
private static final String ENCODED_SIGNAL_GROUP_PREFIX = "__textsecure_group__!";
private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!";
private static final String ENCODED_PUBLIC_CHAT_GROUP_PREFIX = "__loki_public_chat_group__!";
private static final String ENCODED_RSS_FEED_GROUP_PREFIX = "__loki_rss_feed_group__!";
private static final String TAG = GroupUtil.class.getSimpleName();
public static String getEncodedId(SignalServiceGroup group) {
byte[] groupId = group.getGroupId();
if (group.getGroupType() == SignalServiceGroup.GroupType.PUBLIC_CHAT) {
return getEncodedPublicChatId(groupId);
} else if (group.getGroupType() == SignalServiceGroup.GroupType.RSS_FEED) {
return getEncodedRSSFeedId(groupId);
}
return getEncodedId(groupId, false);
}
public static String getEncodedId(byte[] groupId, boolean mms) {
return (mms ? ENCODED_MMS_GROUP_PREFIX : ENCODED_SIGNAL_GROUP_PREFIX) + Hex.toStringCondensed(groupId);
}
public static String getEncodedPublicChatId(byte[] groupId) {
return ENCODED_PUBLIC_CHAT_GROUP_PREFIX + Hex.toStringCondensed(groupId);
}
public static String getEncodedRSSFeedId(byte[] groupId) {
return ENCODED_RSS_FEED_GROUP_PREFIX + Hex.toStringCondensed(groupId);
}
public static byte[] getDecodedId(String groupId) throws IOException {
if (!isEncodedGroup(groupId)) {
throw new IOException("Invalid encoding");
@ -48,13 +69,21 @@ public class GroupUtil {
}
public static boolean isEncodedGroup(@NonNull String groupId) {
return groupId.startsWith(ENCODED_SIGNAL_GROUP_PREFIX) || groupId.startsWith(ENCODED_MMS_GROUP_PREFIX);
return groupId.startsWith(ENCODED_SIGNAL_GROUP_PREFIX) || groupId.startsWith(ENCODED_MMS_GROUP_PREFIX) || groupId.startsWith(ENCODED_PUBLIC_CHAT_GROUP_PREFIX) || groupId.startsWith(ENCODED_RSS_FEED_GROUP_PREFIX);
}
public static boolean isMmsGroup(@NonNull String groupId) {
return groupId.startsWith(ENCODED_MMS_GROUP_PREFIX);
}
public static boolean isPublicChat(@NonNull String groupId) {
return groupId.startsWith(ENCODED_PUBLIC_CHAT_GROUP_PREFIX);
}
public static boolean isRssFeed(@NonNull String groupId) {
return groupId.startsWith(ENCODED_RSS_FEED_GROUP_PREFIX);
}
@WorkerThread
public static Optional<OutgoingGroupMediaMessage> createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) {
String encodedGroupId = groupRecipient.getAddress().toGroupString();

View File

@ -74,8 +74,9 @@ public class IdentityUtil {
GroupDatabase.GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.isRSSFeed() || groupRecord.isPublicChat()) { continue; }
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive() && !groupRecord.isMms()) {
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId(), SignalServiceGroup.GroupType.SIGNAL);
if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
@ -126,8 +127,9 @@ public class IdentityUtil {
GroupDatabase.GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.isRSSFeed() || groupRecord.isPublicChat()) { continue; }
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) {
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId(), SignalServiceGroup.GroupType.SIGNAL);
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);