Join group information into conversation list query

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-08-07 16:47:38 -07:00
parent 159fdb317f
commit da94fd5f9e
11 changed files with 163 additions and 104 deletions

View File

@ -1497,8 +1497,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private boolean isActiveGroup() { private boolean isActiveGroup() {
if (!isGroupConversation()) return false; if (!isGroupConversation()) return false;
GroupRecord record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()); Optional<GroupRecord> record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString());
return record != null && record.isActive(); return record.isPresent() && record.get().isActive();
} }
private boolean isSelfConversation() { private boolean isSelfConversation() {

View File

@ -537,18 +537,18 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Optional<GroupData> doInBackground(String... groupIds) { protected Optional<GroupData> doInBackground(String... groupIds) {
final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity); final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity);
final List<Recipient> recipients = db.getGroupMembers(groupIds[0], false); final List<Recipient> recipients = db.getGroupMembers(groupIds[0], false);
final GroupRecord group = db.getGroup(groupIds[0]); final Optional<GroupRecord> group = db.getGroup(groupIds[0]);
final Set<Recipient> existingContacts = new HashSet<>(recipients.size()); final Set<Recipient> existingContacts = new HashSet<>(recipients.size());
existingContacts.addAll(recipients); existingContacts.addAll(recipients);
if (group != null) { if (group.isPresent()) {
return Optional.of(new GroupData(groupIds[0], return Optional.of(new GroupData(groupIds[0],
existingContacts, existingContacts,
BitmapUtil.fromByteArray(group.getAvatar()), BitmapUtil.fromByteArray(group.get().getAvatar()),
group.getAvatar(), group.get().getAvatar(),
group.getTitle())); group.get().getTitle()));
} else { } else {
return Optional.absent(); return Optional.absent();
} }

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.database; package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -11,12 +10,17 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import java.io.IOException; import java.io.IOException;
@ -32,9 +36,9 @@ public class GroupDatabase extends Database {
private static final String TAG = GroupDatabase.class.getSimpleName(); private static final String TAG = GroupDatabase.class.getSimpleName();
private static final String TABLE_NAME = "groups"; static final String TABLE_NAME = "groups";
private static final String ID = "_id"; private static final String ID = "_id";
private static final String GROUP_ID = "group_id"; static final String GROUP_ID = "group_id";
private static final String TITLE = "title"; private static final String TITLE = "title";
private static final String MEMBERS = "members"; private static final String MEMBERS = "members";
private static final String AVATAR = "avatar"; private static final String AVATAR = "avatar";
@ -67,21 +71,33 @@ public class GroupDatabase extends Database {
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");", "CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
}; };
private static final String[] GROUP_PROJECTION = {
GROUP_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
TIMESTAMP, ACTIVE, MMS
};
static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();
public GroupDatabase(Context context, SQLiteOpenHelper databaseHelper) { public GroupDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
} }
public @Nullable GroupRecord getGroup(String groupId) { public Optional<GroupRecord> getGroup(String groupId) {
@SuppressLint("Recycle") try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", new String[] {groupId},
new String[] {groupId}, null, null, null))
null, null, null); {
if (cursor != null && cursor.moveToNext()) {
return getGroup(cursor);
}
Reader reader = new Reader(cursor); return Optional.absent();
GroupRecord record = reader.getNext(); }
}
reader.close(); Optional<GroupRecord> getGroup(Cursor cursor) {
return record; Reader reader = new Reader(cursor);
return Optional.fromNullable(reader.getCurrent());
} }
public boolean isUnknownGroup(String groupId) { public boolean isUnknownGroup(String groupId) {
@ -251,8 +267,8 @@ public class GroupDatabase extends Database {
} }
public boolean isActive(String groupId) { public boolean isActive(String groupId) {
GroupRecord record = getGroup(groupId); Optional<GroupRecord> record = getGroup(groupId);
return record != null && record.isActive(); return record.isPresent() && record.get().isActive();
} }
public void setActive(String groupId, boolean active) { public void setActive(String groupId, boolean active) {
@ -291,6 +307,14 @@ public class GroupDatabase extends Database {
return null; return null;
} }
return getCurrent();
}
public @Nullable GroupRecord getCurrent() {
if (cursor == null || cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)) == null) {
return null;
}
return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)), return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(TITLE)), cursor.getString(cursor.getColumnIndexOrThrow(TITLE)),
cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)),
@ -330,7 +354,6 @@ public class GroupDatabase extends Database {
{ {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.members = Address.fromSerializedList(members, ',');
this.avatar = avatar; this.avatar = avatar;
this.avatarId = avatarId; this.avatarId = avatarId;
this.avatarKey = avatarKey; this.avatarKey = avatarKey;
@ -339,6 +362,9 @@ public class GroupDatabase extends Database {
this.relay = relay; this.relay = relay;
this.active = active; this.active = active;
this.mms = mms; this.mms = mms;
if (!TextUtils.isEmpty(members)) this.members = Address.fromSerializedList(members, ',');
else this.members = new LinkedList<>();
} }
public byte[] getId() { public byte[] getId() {

View File

@ -30,7 +30,7 @@ public class RecipientPreferenceDatabase extends Database {
static final String TABLE_NAME = "recipient_preferences"; static final String TABLE_NAME = "recipient_preferences";
private static final String ID = "_id"; private static final String ID = "_id";
private static final String ADDRESS = "recipient_ids"; static final String ADDRESS = "recipient_ids";
private static final String BLOCK = "block"; private static final String BLOCK = "block";
private static final String NOTIFICATION = "notification"; private static final String NOTIFICATION = "notification";
private static final String VIBRATE = "vibrate"; private static final String VIBRATE = "vibrate";
@ -110,7 +110,7 @@ public class RecipientPreferenceDatabase extends Database {
cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null); cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null);
if (cursor != null && cursor.moveToNext()) { if (cursor != null && cursor.moveToNext()) {
return Optional.of(getRecipientPreferences(cursor)); return getRecipientPreferences(cursor);
} }
return Optional.absent(); return Optional.absent();
@ -119,7 +119,7 @@ public class RecipientPreferenceDatabase extends Database {
} }
} }
RecipientsPreferences getRecipientPreferences(@NonNull Cursor cursor) { Optional<RecipientsPreferences> getRecipientPreferences(@NonNull Cursor cursor) {
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
String notification = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION)); String notification = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE)); int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
@ -141,11 +141,11 @@ public class RecipientPreferenceDatabase extends Database {
color = null; color = null;
} }
return new RecipientsPreferences(blocked, muteUntil, return Optional.of(new RecipientsPreferences(blocked, muteUntil,
VibrateState.fromId(vibrateState), VibrateState.fromId(vibrateState),
notificationUri, color, seenInviteReminder, notificationUri, color, seenInviteReminder,
defaultSubscriptionId, expireMessages, registered, defaultSubscriptionId, expireMessages, registered,
systemDisplayname); systemDisplayname));
} }
public void setColor(Recipient recipient, MaterialColor color) { public void setColor(Recipient recipient, MaterialColor color) {

View File

@ -31,6 +31,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.DisplayRecord;
@ -44,6 +45,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.DelimiterUtil; import org.thoughtcrime.securesms.util.DelimiterUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -95,9 +97,10 @@ public class ThreadDatabase extends Database {
.map(columnName -> TABLE_NAME + "." + columnName) .map(columnName -> TABLE_NAME + "." + columnName)
.toList(); .toList();
private static final List<String> COMBINED_THREAD_RECIPIENT_PROJECTION = Stream.concat(Stream.of(TYPED_THREAD_PROJECTION), private static final List<String> COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION = Stream.concat(Stream.concat(Stream.of(TYPED_THREAD_PROJECTION),
Stream.of(RecipientPreferenceDatabase.TYPED_RECIPIENT_PROJECTION)) Stream.of(RecipientPreferenceDatabase.TYPED_RECIPIENT_PROJECTION)),
.toList(); Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION))
.toList();
public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) { public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
@ -340,11 +343,13 @@ public class ThreadDatabase extends Database {
} }
private Cursor getConversationList(String archived) { private Cursor getConversationList(String archived) {
String projection = Util.join(COMBINED_THREAD_RECIPIENT_PROJECTION, ","); String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ",");
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT " + projection + " FROM " + TABLE_NAME + Cursor cursor = db.rawQuery("SELECT " + projection + " FROM " + TABLE_NAME +
" LEFT OUTER JOIN " + RecipientPreferenceDatabase.TABLE_NAME + " LEFT OUTER JOIN " + RecipientPreferenceDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientPreferenceDatabase.TABLE_NAME + "." + ADDRESS + " ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientPreferenceDatabase.TABLE_NAME + "." + RecipientPreferenceDatabase.ADDRESS +
" LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID +
" WHERE " + ARCHIVED + " = ? AND " + MESSAGE_COUNT + " != 0" + " WHERE " + ARCHIVED + " = ? AND " + MESSAGE_COUNT + " != 0" +
" ORDER BY " + TABLE_NAME + "." + DATE + " DESC", " ORDER BY " + TABLE_NAME + "." + DATE + " DESC",
new String[] {archived}); new String[] {archived});
@ -356,7 +361,7 @@ public class ThreadDatabase extends Database {
public Cursor getDirectShareList() { public Cursor getDirectShareList() {
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getReadableDatabase();
String projection = Util.join(COMBINED_THREAD_RECIPIENT_PROJECTION, ","); String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ",");
return db.rawQuery("SELECT " + projection + " FROM " + TABLE_NAME + return db.rawQuery("SELECT " + projection + " FROM " + TABLE_NAME +
" LEFT OUTER JOIN " + RecipientPreferenceDatabase.TABLE_NAME + " LEFT OUTER JOIN " + RecipientPreferenceDatabase.TABLE_NAME +
@ -601,10 +606,11 @@ public class ThreadDatabase extends Database {
} }
public ThreadRecord getCurrent() { public ThreadRecord getCurrent() {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS))); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS)));
RecipientsPreferences preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientPreferences(cursor); Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientPreferences(cursor);
Recipient recipient = RecipientFactory.getRecipientFor(context, address, preferences, true); Optional<GroupRecord> groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(cursor);
Recipient recipient = RecipientFactory.getRecipientFor(context, address, preferences, groupRecord, true);
DisplayRecord.Body body = getPlaintextBody(cursor); DisplayRecord.Body body = getPlaintextBody(cursor);
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE));

View File

@ -58,19 +58,19 @@ public class GroupMessageProcessor {
return null; return null;
} }
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
SignalServiceGroup group = message.getGroupInfo().get(); SignalServiceGroup group = message.getGroupInfo().get();
String id = GroupUtil.getEncodedId(group.getGroupId(), false); String id = GroupUtil.getEncodedId(group.getGroupId(), false);
GroupRecord record = database.getGroup(id); Optional<GroupRecord> record = database.getGroup(id);
if (record != null && group.getType() == Type.UPDATE) { if (record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupUpdate(context, masterSecret, envelope, group, record, outgoing); return handleGroupUpdate(context, masterSecret, envelope, group, record.get(), outgoing);
} else if (record == null && group.getType() == Type.UPDATE) { } else if (record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupCreate(context, masterSecret, envelope, group, outgoing); return handleGroupCreate(context, masterSecret, envelope, group, outgoing);
} else if (record != null && group.getType() == Type.QUIT) { } else if (record.isPresent() && group.getType() == Type.QUIT) {
return handleGroupLeave(context, masterSecret, envelope, group, record, outgoing); return handleGroupLeave(context, masterSecret, envelope, group, record.get(), outgoing);
} else if (record != null && group.getType() == Type.REQUEST_INFO) { } else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) {
return handleGroupInfoRequest(context, envelope, group, record); return handleGroupInfoRequest(context, envelope, group, record.get());
} else { } else {
Log.w(TAG, "Received unknown type, ignoring..."); Log.w(TAG, "Received unknown type, ignoring...");
return null; return null;

View File

@ -8,6 +8,7 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel; import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
@ -55,18 +56,18 @@ public class AvatarDownloadJob extends MasterSecretJob implements InjectableType
@Override @Override
public void onRun(MasterSecret masterSecret) throws IOException { public void onRun(MasterSecret masterSecret) throws IOException {
String encodeId = GroupUtil.getEncodedId(groupId, false); String encodeId = GroupUtil.getEncodedId(groupId, false);
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
GroupDatabase.GroupRecord record = database.getGroup(encodeId); Optional<GroupRecord> record = database.getGroup(encodeId);
File attachment = null; File attachment = null;
try { try {
if (record != null) { if (record.isPresent()) {
long avatarId = record.getAvatarId(); long avatarId = record.get().getAvatarId();
String contentType = record.getAvatarContentType(); String contentType = record.get().getAvatarContentType();
byte[] key = record.getAvatarKey(); byte[] key = record.get().getAvatarKey();
String relay = record.getRelay(); String relay = record.get().getRelay();
Optional<byte[]> digest = Optional.fromNullable(record.getAvatarDigest()); Optional<byte[]> digest = Optional.fromNullable(record.get().getAvatarDigest());
Optional<String> fileName = Optional.absent(); Optional<String> fileName = Optional.absent();
if (avatarId == -1 || key == null) { if (avatarId == -1 || key == null) {

View File

@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalM
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
@ -60,7 +61,7 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
public void onRun() throws IOException, UntrustedIdentityException { public void onRun() throws IOException, UntrustedIdentityException {
SignalServiceMessageSender messageSender = messageSenderFactory.create(); SignalServiceMessageSender messageSender = messageSenderFactory.create();
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
GroupRecord record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false)); Optional<GroupRecord> record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false));
SignalServiceAttachment avatar = null; SignalServiceAttachment avatar = null;
if (record == null) { if (record == null) {
@ -68,17 +69,17 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
return; return;
} }
if (record.getAvatar() != null) { if (record.get().getAvatar() != null) {
avatar = SignalServiceAttachmentStream.newStreamBuilder() avatar = SignalServiceAttachmentStream.newStreamBuilder()
.withContentType("image/jpeg") .withContentType("image/jpeg")
.withStream(new ByteArrayInputStream(record.getAvatar())) .withStream(new ByteArrayInputStream(record.get().getAvatar()))
.withLength(record.getAvatar().length) .withLength(record.get().getAvatar().length)
.build(); .build();
} }
List<String> members = new LinkedList<>(); List<String> members = new LinkedList<>();
for (Address member : record.getMembers()) { for (Address member : record.get().getMembers()) {
members.add(member.serialize()); members.add(member.serialize());
} }
@ -86,7 +87,7 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
.withAvatar(avatar) .withAvatar(avatar)
.withId(groupId) .withId(groupId)
.withMembers(members) .withMembers(members)
.withName(record.getTitle()) .withName(record.get().getTitle())
.build(); .build();
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()

View File

@ -69,7 +69,7 @@ public class Recipient implements RecipientModifiedListener {
Recipient(@NonNull Address address, Recipient(@NonNull Address address,
@Nullable Recipient stale, @Nullable Recipient stale,
@NonNull Optional<RecipientsPreferences> preferences, @NonNull Optional<RecipientDetails> details,
@NonNull ListenableFutureTask<RecipientDetails> future) @NonNull ListenableFutureTask<RecipientDetails> future)
{ {
this.address = address; this.address = address;
@ -90,17 +90,17 @@ public class Recipient implements RecipientModifiedListener {
this.expireMessages = stale.expireMessages; this.expireMessages = stale.expireMessages;
} }
if (preferences.isPresent()) { if (details.isPresent()) {
if (!TextUtils.isEmpty(preferences.get().getSystemDisplayName())) { this.name = details.get().name;
this.name = preferences.get().getSystemDisplayName(); this.contactPhoto = details.get().avatar;
} this.color = details.get().color;
this.ringtone = details.get().ringtone;
this.color = preferences.get().getColor(); this.mutedUntil = details.get().mutedUntil;
this.ringtone = preferences.get().getRingtone(); this.blocked = details.get().blocked;
this.mutedUntil = preferences.get().getMuteUntil(); this.vibrate = details.get().vibrateState;
this.blocked = preferences.get().isBlocked(); this.expireMessages = details.get().expireMessages;
this.vibrate = preferences.get().getVibrateState(); this.participants.clear();
this.expireMessages = preferences.get().getExpireMessages(); this.participants.addAll(details.get().participants);
} }
future.addListener(new FutureTaskListener<RecipientDetails>() { future.addListener(new FutureTaskListener<RecipientDetails>() {
@ -118,6 +118,8 @@ public class Recipient implements RecipientModifiedListener {
Recipient.this.blocked = result.blocked; Recipient.this.blocked = result.blocked;
Recipient.this.vibrate = result.vibrateState; Recipient.this.vibrate = result.vibrateState;
Recipient.this.expireMessages = result.expireMessages; Recipient.this.expireMessages = result.expireMessages;
Recipient.this.participants.clear();
Recipient.this.participants.addAll(result.participants); Recipient.this.participants.addAll(result.participants);
Recipient.this.resolving = false; Recipient.this.resolving = false;

View File

@ -21,7 +21,7 @@ import android.content.Intent;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -33,12 +33,12 @@ public class RecipientFactory {
public static @NonNull Recipient getRecipientFor(@NonNull Context context, @NonNull Address address, boolean asynchronous) { public static @NonNull Recipient getRecipientFor(@NonNull Context context, @NonNull Address address, boolean asynchronous) {
if (address == null) throw new AssertionError(address); if (address == null) throw new AssertionError(address);
return provider.getRecipient(context, address, Optional.absent(), asynchronous); return provider.getRecipient(context, address, Optional.absent(), Optional.absent(), asynchronous);
} }
public static @NonNull Recipient getRecipientFor(@NonNull Context context, @NonNull Address address, @NonNull RecipientsPreferences preferences, boolean asynchronous) { public static @NonNull Recipient getRecipientFor(@NonNull Context context, @NonNull Address address, @NonNull Optional<RecipientsPreferences> preferences, @NonNull Optional<GroupRecord> groupRecord, boolean asynchronous) {
if (address == null) throw new AssertionError(address); if (address == null) throw new AssertionError(address);
return provider.getRecipient(context, address, Optional.of(preferences), asynchronous); return provider.getRecipient(context, address, preferences, groupRecord, asynchronous);
} }
public static void clearCache(Context context) { public static void clearCache(Context context) {

View File

@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory; import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.LRUCache;
@ -68,16 +68,18 @@ class RecipientProvider {
null, null)); null, null));
}}; }};
@NonNull Recipient getRecipient(Context context, Address address, Optional<RecipientsPreferences> preferences, boolean asynchronous) { @NonNull Recipient getRecipient(Context context, Address address, Optional<RecipientsPreferences> preferences, Optional<GroupRecord> groupRecord, boolean asynchronous) {
Recipient cachedRecipient = recipientCache.get(address); Recipient cachedRecipient = recipientCache.get(address);
if (cachedRecipient != null && !cachedRecipient.isStale() && (asynchronous || !cachedRecipient.isResolving())) { if (cachedRecipient != null && !cachedRecipient.isStale() && (asynchronous || !cachedRecipient.isResolving())) {
return cachedRecipient; return cachedRecipient;
} }
Optional<RecipientDetails> prefetchedRecipientDetails = createPrefetchedRecipientDetails(context, address, preferences, groupRecord);
if (asynchronous) { if (asynchronous) {
cachedRecipient = new Recipient(address, cachedRecipient, preferences, getRecipientDetailsAsync(context, address, preferences)); cachedRecipient = new Recipient(address, cachedRecipient, prefetchedRecipientDetails, getRecipientDetailsAsync(context, address, preferences, groupRecord));
} else { } else {
cachedRecipient = new Recipient(address, getRecipientDetailsSync(context, address, preferences, false)); cachedRecipient = new Recipient(address, getRecipientDetailsSync(context, address, preferences, groupRecord, false));
} }
recipientCache.set(address, cachedRecipient); recipientCache.set(address, cachedRecipient);
@ -88,12 +90,25 @@ class RecipientProvider {
recipientCache.reset(); recipientCache.reset();
} }
private @NonNull ListenableFutureTask<RecipientDetails> getRecipientDetailsAsync(final Context context, final @NonNull Address address, final @NonNull Optional<RecipientsPreferences> preferences) private @NonNull Optional<RecipientDetails> createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address,
@NonNull Optional<RecipientsPreferences> preferences,
@NonNull Optional<GroupRecord> groupRecord)
{
if (address.isGroup() && preferences.isPresent() && groupRecord.isPresent()) {
return Optional.of(getGroupRecipientDetails(context, address, groupRecord, preferences, true));
} else if (!address.isGroup() && preferences.isPresent()) {
return Optional.of(new RecipientDetails(null, null, null, ContactPhotoFactory.getLoadingPhoto(), preferences.get(), null));
}
return Optional.absent();
}
private @NonNull ListenableFutureTask<RecipientDetails> getRecipientDetailsAsync(final Context context, final @NonNull Address address, final @NonNull Optional<RecipientsPreferences> preferences, final @NonNull Optional<GroupRecord> groupRecord)
{ {
Callable<RecipientDetails> task = new Callable<RecipientDetails>() { Callable<RecipientDetails> task = new Callable<RecipientDetails>() {
@Override @Override
public RecipientDetails call() throws Exception { public RecipientDetails call() throws Exception {
return getRecipientDetailsSync(context, address, preferences, true); return getRecipientDetailsSync(context, address, preferences, groupRecord, true);
} }
}; };
@ -102,8 +117,8 @@ class RecipientProvider {
return future; return future;
} }
private @NonNull RecipientDetails getRecipientDetailsSync(Context context, @NonNull Address address, Optional<RecipientsPreferences> preferences, boolean nestedAsynchronous) { private @NonNull RecipientDetails getRecipientDetailsSync(Context context, @NonNull Address address, Optional<RecipientsPreferences> preferences, Optional<GroupRecord> groupRecord, boolean nestedAsynchronous) {
if (address.isGroup()) return getGroupRecipientDetails(context, address, nestedAsynchronous); if (address.isGroup()) return getGroupRecipientDetails(context, address, groupRecord, preferences, nestedAsynchronous);
else return getIndividualRecipientDetails(context, address, preferences); else return getIndividualRecipientDetails(context, address, preferences);
} }
@ -141,27 +156,33 @@ class RecipientProvider {
else return new RecipientDetails(null, null, null, ContactPhotoFactory.getDefaultContactPhoto(null), preferences.orNull(), null); else return new RecipientDetails(null, null, null, ContactPhotoFactory.getDefaultContactPhoto(null), preferences.orNull(), null);
} }
private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, boolean asynchronous) { private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, Optional<GroupRecord> groupRecord, Optional<RecipientsPreferences> preferences, boolean asynchronous) {
GroupDatabase.GroupRecord record = DatabaseFactory.getGroupDatabase(context).getGroup(groupId.toGroupString()); if (!groupRecord.isPresent()) {
groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId.toGroupString());
}
if (record != null) { if (!preferences.isPresent()) {
ContactPhoto contactPhoto = ContactPhotoFactory.getGroupContactPhoto(record.getAvatar()); preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(groupId);
String title = record.getTitle(); }
List<Address> memberAddresses = record.getMembers();
if (groupRecord.isPresent()) {
ContactPhoto contactPhoto = ContactPhotoFactory.getGroupContactPhoto(groupRecord.get().getAvatar());
String title = groupRecord.get().getTitle();
List<Address> memberAddresses = groupRecord.get().getMembers();
List<Recipient> members = new LinkedList<>(); List<Recipient> members = new LinkedList<>();
for (Address memberAddress : memberAddresses) { for (Address memberAddress : memberAddresses) {
members.add(getRecipient(context, memberAddress, Optional.absent(), asynchronous)); members.add(getRecipient(context, memberAddress, Optional.absent(), Optional.absent(), asynchronous));
} }
if (!groupId.isMmsGroup() && title == null) { if (!groupId.isMmsGroup() && title == null) {
title = context.getString(R.string.RecipientProvider_unnamed_group);; title = context.getString(R.string.RecipientProvider_unnamed_group);;
} }
return new RecipientDetails(title, null, null, contactPhoto, null, members); return new RecipientDetails(title, null, null, contactPhoto, preferences.orNull(), members);
} }
return new RecipientDetails(context.getString(R.string.RecipientProvider_unnamed_group), null, null, ContactPhotoFactory.getDefaultGroupPhoto(), null, null); return new RecipientDetails(context.getString(R.string.RecipientProvider_unnamed_group), null, null, ContactPhotoFactory.getDefaultGroupPhoto(), preferences.orNull(), null);
} }
static class RecipientDetails { static class RecipientDetails {
@ -182,7 +203,6 @@ class RecipientProvider {
@Nullable RecipientsPreferences preferences, @Nullable RecipientsPreferences preferences,
@Nullable List<Recipient> participants) @Nullable List<Recipient> participants)
{ {
this.name = name;
this.customLabel = customLabel; this.customLabel = customLabel;
this.avatar = avatar; this.avatar = avatar;
this.contactUri = contactUri; this.contactUri = contactUri;
@ -193,6 +213,9 @@ class RecipientProvider {
this.blocked = preferences != null && preferences.isBlocked(); this.blocked = preferences != null && preferences.isBlocked();
this.expireMessages = preferences != null ? preferences.getExpireMessages() : 0; this.expireMessages = preferences != null ? preferences.getExpireMessages() : 0;
this.participants = participants == null ? new LinkedList<Recipient>() : participants; this.participants = participants == null ? new LinkedList<Recipient>() : participants;
if (name == null && preferences != null) this.name = preferences.getSystemDisplayName();
else this.name = name;
} }
} }