Listen for group updates, fix group updates, and Recipient marshing

This commit is contained in:
Moxie Marlinspike 2014-02-24 00:19:54 -08:00
parent 86b3de2a93
commit 5000957b99
13 changed files with 119 additions and 116 deletions

View file

@ -73,6 +73,8 @@ import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter;
import org.thoughtcrime.securesms.mms.MediaTooLargeException; import org.thoughtcrime.securesms.mms.MediaTooLargeException;
import org.thoughtcrime.securesms.mms.MmsSendHelper; import org.thoughtcrime.securesms.mms.MmsSendHelper;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -85,8 +87,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.ActionBarUtil; import org.thoughtcrime.securesms.util.ActionBarUtil;
import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapDecodingException;
@ -116,6 +116,7 @@ import java.util.List;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
/** /**
@ -155,6 +156,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private AttachmentTypeSelectorAdapter attachmentAdapter; private AttachmentTypeSelectorAdapter attachmentAdapter;
private AttachmentManager attachmentManager; private AttachmentManager attachmentManager;
private BroadcastReceiver securityUpdateReceiver; private BroadcastReceiver securityUpdateReceiver;
private BroadcastReceiver groupUpdateReceiver;
private EmojiDrawer emojiDrawer; private EmojiDrawer emojiDrawer;
private EmojiToggle emojiToggle; private EmojiToggle emojiToggle;
@ -218,6 +220,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
@Override @Override
protected void onDestroy() { protected void onDestroy() {
unregisterReceiver(securityUpdateReceiver); unregisterReceiver(securityUpdateReceiver);
unregisterReceiver(groupUpdateReceiver);
saveDraft(); saveDraft();
MemoryCleaner.clean(masterSecret); MemoryCleaner.clean(masterSecret);
super.onDestroy(); super.onDestroy();
@ -456,10 +459,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
initializeEnabledCheck(); initializeEnabledCheck();
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
Toast.makeText(ConversationActivity.this, "Error leaving group....", Toast.LENGTH_LONG); Toast.makeText(ConversationActivity.this, "Error leaving group....", Toast.LENGTH_LONG).show();
} catch (MmsException e) { } catch (MmsException e) {
Log.w(TAG, e); Log.w(TAG, e);
Toast.makeText(ConversationActivity.this, "Error leaving group...", Toast.LENGTH_LONG); Toast.makeText(ConversationActivity.this, "Error leaving group...", Toast.LENGTH_LONG).show();
} }
} }
}); });
@ -706,7 +709,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private void initializeResources() { private void initializeResources() {
recipientsPanel = (RecipientsPanel)findViewById(R.id.recipients); recipientsPanel = (RecipientsPanel)findViewById(R.id.recipients);
recipients = getIntent().getParcelableExtra(RECIPIENTS_EXTRA); recipients = RecipientFactory.getRecipientsForIds(this, getIntent().getStringExtra(RECIPIENTS_EXTRA), true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA,
ThreadDatabase.DistributionTypes.DEFAULT); ThreadDatabase.DistributionTypes.DEFAULT);
@ -739,6 +742,13 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
emojiDrawer.setComposeEditText(composeText); emojiDrawer.setComposeEditText(composeText);
emojiToggle.setOnClickListener(new EmojiToggleListener()); emojiToggle.setOnClickListener(new EmojiToggleListener());
recipients.addListener(new RecipientModifiedListener() {
@Override
public void onModified(Recipient recipient) {
initializeTitleBar();
}
});
registerForContextMenu(sendButton); registerForContextMenu(sendButton);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
@ -777,9 +787,30 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
} }
}; };
groupUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.w("ConversationActivity", "Group update received...");
if (recipients != null) {
String ids = recipients.toIdString();
Log.w("ConversationActivity", "Looking up new recipients...");
recipients = RecipientFactory.getRecipientsForIds(context, ids, true);
recipients.addListener(new RecipientModifiedListener() {
@Override
public void onModified(Recipient recipient) {
initializeTitleBar();
}
});
}
}
};
registerReceiver(securityUpdateReceiver, registerReceiver(securityUpdateReceiver,
new IntentFilter(KeyExchangeProcessor.SECURITY_UPDATE_EVENT), new IntentFilter(KeyExchangeProcessor.SECURITY_UPDATE_EVENT),
KeyCachingService.KEY_PERMISSION, null); KeyCachingService.KEY_PERMISSION, null);
registerReceiver(groupUpdateReceiver,
new IntentFilter(GroupDatabase.DATABASE_UPDATE_ACTION));
} }
@ -959,7 +990,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
} }
private boolean isPushGroupConversation() { private boolean isPushGroupConversation() {
return getRecipients().isGroupRecipient(); return getRecipients() != null && getRecipients().isGroupRecipient();
} }
private boolean isPushDestination() { private boolean isPushDestination() {
@ -967,6 +998,9 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
if (!TextSecurePreferences.isPushRegistered(this)) if (!TextSecurePreferences.isPushRegistered(this))
return false; return false;
if (getRecipients() == null)
return false;
if (isPushGroupConversation()) if (isPushGroupConversation())
return true; return true;

View file

@ -20,6 +20,7 @@ import android.widget.CursorAdapter;
import com.actionbarsherlock.app.SherlockListFragment; import com.actionbarsherlock.app.SherlockListFragment;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.loaders.ConversationLoader; import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
@ -183,9 +184,11 @@ public class ConversationFragment extends SherlockListFragment
} }
private void initializeResources() { private void initializeResources() {
String recipientIds = this.getActivity().getIntent().getStringExtra("recipients");
this.masterSecret = (MasterSecret)this.getActivity().getIntent() this.masterSecret = (MasterSecret)this.getActivity().getIntent()
.getParcelableExtra("master_secret"); .getParcelableExtra("master_secret");
this.recipients = this.getActivity().getIntent().getParcelableExtra("recipients"); this.recipients = RecipientFactory.getRecipientsForIds(getActivity(), recipientIds, true);
this.threadId = this.getActivity().getIntent().getLongExtra("thread_id", -1); this.threadId = this.getActivity().getIntent().getLongExtra("thread_id", -1);
} }

View file

@ -177,7 +177,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
private void createConversation(long threadId, Recipients recipients, int distributionType) { private void createConversation(long threadId, Recipients recipients, int distributionType) {
Intent intent = new Intent(this, ConversationActivity.class); Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.toIdString());
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);

View file

@ -34,12 +34,12 @@ import org.thoughtcrime.securesms.contacts.RecipientsEditor;
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.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
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.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.util.ActionBarUtil; import org.thoughtcrime.securesms.util.ActionBarUtil;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
@ -341,18 +341,6 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
new UpdateWhisperGroupAsyncTask().execute(); new UpdateWhisperGroupAsyncTask().execute();
} }
private static List<String> recipientsToNormalizedStrings(Collection<Recipient> recipients, String localNumber) {
final List<String> e164numbers = new ArrayList<String>(recipients.size());
for (Recipient contact : recipients) {
try {
e164numbers.add(PhoneNumberFormatter.formatNumber(contact.getNumber(), localNumber));
} catch (InvalidNumberException ine) {
Log.w(TAG, "Failed to format number for added group member.", ine);
}
}
return e164numbers;
}
private void enableWhisperGroupCreatingUi() { private void enableWhisperGroupCreatingUi() {
findViewById(R.id.group_details_layout).setVisibility(View.GONE); findViewById(R.id.group_details_layout).setVisibility(View.GONE);
findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE); findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE);
@ -428,10 +416,11 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
{ {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
byte[] groupId = groupDatabase.allocateGroupId(); byte[] groupId = groupDatabase.allocateGroupId();
List<String> memberE164Numbers = getE164Numbers(members); Set<String> memberE164Numbers = getE164Numbers(members);
groupDatabase.create(groupId, TextSecurePreferences.getLocalNumber(this), groupName, memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));
memberE164Numbers, null, null);
groupDatabase.create(groupId, groupName, new LinkedList<String>(memberE164Numbers), null, null);
groupDatabase.updateAvatar(groupId, avatar); groupDatabase.updateAvatar(groupId, avatar);
return handlePushOperation(groupId, groupName, avatar, memberE164Numbers); return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
@ -442,24 +431,21 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
throws InvalidNumberException, MmsException throws InvalidNumberException, MmsException
{ {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
List<String> memberE164Numbers = getE164Numbers(members); Set<String> memberE164Numbers = getE164Numbers(members);
memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));
GroupDatabase.GroupRecord record = groupDatabase.getGroup(groupId); for (String number : memberE164Numbers)
Set<String> newMembers = new HashSet<String>(memberE164Numbers); Log.w(TAG, "Updating: " + number);
newMembers.removeAll(record.getMembers());
groupDatabase.add(groupId, TextSecurePreferences.getLocalNumber(this),
new LinkedList<String>(newMembers));
groupDatabase.updateMembers(groupId, new LinkedList<String>(memberE164Numbers));
groupDatabase.updateTitle(groupId, groupName); groupDatabase.updateTitle(groupId, groupName);
groupDatabase.updateAvatar(groupId, avatar); groupDatabase.updateAvatar(groupId, avatar);
return handlePushOperation(groupId, groupName, avatar, memberE164Numbers); return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
} }
private Pair<Long, Recipients> handlePushOperation(byte[] groupId, String groupName, byte[] avatar, private Pair<Long, Recipients> handlePushOperation(byte[] groupId, String groupName, byte[] avatar,
List<String> e164numbers) Set<String> e164numbers)
throws MmsException, InvalidNumberException throws MmsException, InvalidNumberException
{ {
@ -501,10 +487,10 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
return arrayList; return arrayList;
} }
private List<String> getE164Numbers(Set<Recipient> recipients) private Set<String> getE164Numbers(Set<Recipient> recipients)
throws InvalidNumberException throws InvalidNumberException
{ {
List<String> results = new LinkedList<String>(); Set<String> results = new HashSet<String>();
for (Recipient recipient : recipients) { for (Recipient recipient : recipients) {
results.add(Util.canonicalizeNumber(this, recipient.getNumber())); results.add(Util.canonicalizeNumber(this, recipient.getNumber()));
@ -545,7 +531,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
ArrayList<Recipient> selectedContactsList = setToArrayList(selectedContacts); ArrayList<Recipient> selectedContactsList = setToArrayList(selectedContacts);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, new Recipients(selectedContactsList)); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, new Recipients(selectedContactsList).toIdString());
startActivity(intent); startActivity(intent);
finish(); finish();
} else { } else {
@ -573,7 +559,9 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
} }
final String name = (groupName.getText() != null) ? groupName.getText().toString() : null; final String name = (groupName.getText() != null) ? groupName.getText().toString() : null;
try { try {
return handleUpdatePushGroup(groupId, name, avatarBytes, selectedContacts); Set<Recipient> unionContacts = new HashSet<Recipient>(selectedContacts);
unionContacts.addAll(existingContacts);
return handleUpdatePushGroup(groupId, name, avatarBytes, unionContacts);
} catch (MmsException e) { } catch (MmsException e) {
Log.w(TAG, e); Log.w(TAG, e);
return new Pair<Long,Recipients>(RES_MMS_EXCEPTION, null); return new Pair<Long,Recipients>(RES_MMS_EXCEPTION, null);
@ -638,7 +626,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.toIdString());
startActivity(intent); startActivity(intent);
finish(); finish();
} else if (threadId == RES_BAD_NUMBER) { } else if (threadId == RES_BAD_NUMBER) {
@ -671,7 +659,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
final GroupDatabase db = DatabaseFactory.getGroupDatabase(GroupCreateActivity.this); final GroupDatabase db = DatabaseFactory.getGroupDatabase(GroupCreateActivity.this);
final Recipients recipients = db.getGroupMembers(groupId); final Recipients recipients = db.getGroupMembers(groupId, false);
if (recipients != null) { if (recipients != null) {
final List<Recipient> recipientList = recipients.getRecipientsList(); final List<Recipient> recipientList = recipients.getRecipientsList();
if (recipientList != null) { if (recipientList != null) {

View file

@ -37,7 +37,7 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, Recipients> {
try { try {
String groupId = recipients.getPrimaryRecipient().getNumber(); String groupId = recipients.getPrimaryRecipient().getNumber();
return DatabaseFactory.getGroupDatabase(context) return DatabaseFactory.getGroupDatabase(context)
.getGroupMembers(GroupUtil.getDecodedId(groupId)); .getGroupMembers(GroupUtil.getDecodedId(groupId), true);
} catch (IOException e) { } catch (IOException e) {
Log.w("ConverstionActivity", e); Log.w("ConverstionActivity", e);
return new Recipients(new LinkedList<Recipient>()); return new Recipients(new LinkedList<Recipient>());

View file

@ -135,7 +135,7 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
private Intent getConversationIntent(ConversationParameters parameters) { private Intent getConversationIntent(ConversationParameters parameters) {
Intent intent = new Intent(this, ConversationActivity.class); Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, parameters.recipients); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, parameters.recipients != null ? parameters.recipients.toIdString() : "");
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, parameters.thread); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, parameters.thread);
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, parameters.draftText); intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, parameters.draftText);

View file

@ -113,7 +113,7 @@ public class SingleContactSelectionActivity extends PassphraseRequiredSherlockFr
private void openNewConversation(Recipients recipients) { private void openNewConversation(Recipients recipients) {
if (recipients != null) { if (recipients != null) {
Intent intent = new Intent(SingleContactSelectionActivity.this, ConversationActivity.class); Intent intent = new Intent(SingleContactSelectionActivity.this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.toIdString());
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
long existingThread = DatabaseFactory.getThreadDatabase(SingleContactSelectionActivity.this).getThreadIdIfExistsFor(recipients); long existingThread = DatabaseFactory.getThreadDatabase(SingleContactSelectionActivity.this).getThreadIdIfExistsFor(recipients);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);

View file

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@ -24,10 +25,14 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer; import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
public class GroupDatabase extends Database { public class GroupDatabase extends Database {
public static final String DATABASE_UPDATE_ACTION = "org.thoughtcrime.securesms.database.GroupDatabase.UPDATE";
private static final String TAG = GroupDatabase.class.getSimpleName(); private static final String TAG = GroupDatabase.class.getSimpleName();
private static final String TABLE_NAME = "groups"; private static final String TABLE_NAME = "groups";
@ -77,11 +82,15 @@ public class GroupDatabase extends Database {
return record; return record;
} }
public Recipients getGroupMembers(byte[] groupId) { public Recipients getGroupMembers(byte[] groupId, boolean includeSelf) {
List<String> members = getCurrentMembers(groupId); String localNumber = TextSecurePreferences.getLocalNumber(context);
List<Recipient> recipients = new LinkedList<Recipient>(); List<String> members = getCurrentMembers(groupId);
List<Recipient> recipients = new LinkedList<Recipient>();
for (String member : members) { for (String member : members) {
if (!includeSelf && member.equals(localNumber))
continue;
try { try {
recipients.addAll(RecipientFactory.getRecipientsFromString(context, member, false) recipients.addAll(RecipientFactory.getRecipientsFromString(context, member, false)
.getRecipientsList()); .getRecipientsList());
@ -93,27 +102,13 @@ public class GroupDatabase extends Database {
return new Recipients(recipients); return new Recipients(recipients);
} }
public void create(byte[] groupId, String owner, String title, public void create(byte[] groupId, String title, List<String> members,
List<String> members, AttachmentPointer avatar, AttachmentPointer avatar, String relay)
String relay)
{ {
List<String> filteredMembers = new LinkedList<String>();
String localNumber = TextSecurePreferences.getLocalNumber(context);
if (!localNumber.equals(owner)) {
filteredMembers.add(owner);
}
for (String member : members) {
if (!member.equals(localNumber)) {
filteredMembers.add(member);
}
}
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId)); contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
contentValues.put(TITLE, title); contentValues.put(TITLE, title);
contentValues.put(MEMBERS, Util.join(filteredMembers, ",")); contentValues.put(MEMBERS, Util.join(members, ","));
if (avatar != null) { if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId()); contentValues.put(AVATAR_ID, avatar.getId());
@ -142,7 +137,8 @@ public class GroupDatabase extends Database {
GROUP_ID + " = ?", GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(groupId)}); new String[] {GroupUtil.getEncodedId(groupId)});
if (title != null) updateGroupRecipientTitle(groupId, title); RecipientFactory.clearCache();
notifyDatabaseListeners();
} }
public void updateTitle(byte[] groupId, String title) { public void updateTitle(byte[] groupId, String title) {
@ -151,43 +147,32 @@ public class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(groupId)}); new String[] {GroupUtil.getEncodedId(groupId)});
if (title != null) updateGroupRecipientTitle(groupId, title); RecipientFactory.clearCache();
notifyDatabaseListeners();
} }
public void updateAvatar(byte[] groupId, Bitmap avatar) { public void updateAvatar(byte[] groupId, Bitmap avatar) {
updateAvatarInDatabase(groupId, BitmapUtil.toByteArray(avatar)); updateAvatar(groupId, BitmapUtil.toByteArray(avatar));
updateGroupRecipientAvatar(groupId, avatar);
} }
public void updateAvatar(byte[] groupId, byte[] avatar) { public void updateAvatar(byte[] groupId, byte[] avatar) {
updateAvatarInDatabase(groupId, avatar);
updateGroupRecipientAvatar(groupId, BitmapFactory.decodeByteArray(avatar, 0, avatar.length));
}
private void updateAvatarInDatabase(byte[] groupId, byte[] avatar) {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(AVATAR, avatar); contentValues.put(AVATAR, avatar);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(groupId)}); new String[] {GroupUtil.getEncodedId(groupId)});
RecipientFactory.clearCache();
notifyDatabaseListeners();
} }
public void add(byte[] id, String source, List<String> members) { public void updateMembers(byte[] id, List<String> members) {
List<String> currentMembers = getCurrentMembers(id); ContentValues contents = new ContentValues();
contents.put(MEMBERS, Util.join(members, ","));
contents.put(ACTIVE, 1);
for (String currentMember : currentMembers) { databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
if (currentMember.equals(source)) { new String[] {GroupUtil.getEncodedId(id)});
List<String> concatenatedMembers = new LinkedList<String>(currentMembers);
concatenatedMembers.addAll(members);
ContentValues contents = new ContentValues();
contents.put(MEMBERS, Util.join(concatenatedMembers, ","));
contents.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(id)});
}
}
} }
public void remove(byte[] id, String source) { public void remove(byte[] id, String source) {
@ -198,7 +183,7 @@ public class GroupDatabase extends Database {
contents.put(MEMBERS, Util.join(currentMembers, ",")); contents.put(MEMBERS, Util.join(currentMembers, ","));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[]{GroupUtil.getEncodedId(id)}); new String[] {GroupUtil.getEncodedId(id)});
} }
private List<String> getCurrentMembers(byte[] id) { private List<String> getCurrentMembers(byte[] id) {
@ -244,6 +229,10 @@ public class GroupDatabase extends Database {
} }
} }
private void notifyDatabaseListeners() {
Intent intent = new Intent(DATABASE_UPDATE_ACTION);
context.sendBroadcast(intent);
}
public static class Reader { public static class Reader {
@ -342,28 +331,4 @@ public class GroupDatabase extends Database {
return active; return active;
} }
} }
private Recipient getGroupRecipient(byte[] groupId) {
try {
return RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true)
.getPrimaryRecipient();
} catch (RecipientFormattingException e) {
Log.w(TAG, e);
return null;
}
}
private void updateGroupRecipientTitle(byte[] groupId, String title) {
Recipient groupRecipient = getGroupRecipient(groupId);
Log.i(TAG, "updating group recipient title for recipient " + System.identityHashCode(groupRecipient));
if (groupRecipient != null) groupRecipient.setName(title);
else Log.w(TAG, "Couldn't update group title because recipient couldn't be found.");
}
private void updateGroupRecipientAvatar(byte[] groupId, Bitmap photo) {
Recipient groupRecipient = getGroupRecipient(groupId);
if (groupRecipient != null) groupRecipient.setContactPhoto(photo);
else Log.w(TAG, "Couldn't update group title because recipient couldn't be found.");
}
} }

View file

@ -84,7 +84,9 @@ public abstract class MessageRecord extends DisplayRecord {
@Override @Override
public SpannableString getDisplayBody() { public SpannableString getDisplayBody() {
if (isGroupUpdate()) { if (isGroupUpdate() && isOutgoing()) {
return emphasisAdded("Updated the group.");
} else if (isGroupUpdate()) {
return emphasisAdded(GroupUtil.getDescription(getBody().getBody())); return emphasisAdded(GroupUtil.getDescription(getBody().getBody()));
} else if (isGroupQuit() && isOutgoing()) { } else if (isGroupQuit() && isOutgoing()) {
return emphasisAdded("You have left the group."); return emphasisAdded("You have left the group.");

View file

@ -23,6 +23,7 @@ import android.util.Patterns;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import org.whispersystems.textsecure.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
@ -137,6 +138,16 @@ public class Recipients implements Parcelable {
return this.recipients; return this.recipients;
} }
public String toIdString() {
List<String> ids = new LinkedList<String>();
for (Recipient recipient : recipients) {
ids.add(String.valueOf(recipient.getRecipientId()));
}
return Util.join(ids, " ");
}
public String[] toNumberStringArray(boolean scrub) { public String[] toNumberStringArray(boolean scrub) {
String[] recipientsArray = new String[recipients.size()]; String[] recipientsArray = new String[recipients.size()];
Iterator<Recipient> iterator = recipients.iterator(); Iterator<Recipient> iterator = recipients.iterator();

View file

@ -71,7 +71,7 @@ public class GroupReceiver {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
byte[] id = group.getId().toByteArray(); byte[] id = group.getId().toByteArray();
database.create(id, message.getSource(), group.getName(), group.getMembersList(), database.create(id, group.getName(), group.getMembersList(),
group.getAvatar(), message.getRelay()); group.getAvatar(), message.getRelay());
storeMessage(masterSecret, message, group); storeMessage(masterSecret, message, group);
@ -98,7 +98,7 @@ public class GroupReceiver {
if (addedMembers.size() > 0) { if (addedMembers.size() > 0) {
Set<String> unionMembers = new HashSet<String>(recordMembers); Set<String> unionMembers = new HashSet<String>(recordMembers);
unionMembers.addAll(messageMembers); unionMembers.addAll(messageMembers);
database.add(id, message.getSource(), new LinkedList<String>(unionMembers)); database.updateMembers(id, new LinkedList<String>(unionMembers));
group = group.toBuilder().clearMembers().addAllMembers(addedMembers).build(); group = group.toBuilder().clearMembers().addAllMembers(addedMembers).build();
} else { } else {

View file

@ -115,7 +115,7 @@ public class PushTransport extends BaseTransport {
if (GroupUtil.isEncodedGroup(destination)) { if (GroupUtil.isEncodedGroup(destination)) {
recipients = DatabaseFactory.getGroupDatabase(context) recipients = DatabaseFactory.getGroupDatabase(context)
.getGroupMembers(GroupUtil.getDecodedId(destination)); .getGroupMembers(GroupUtil.getDecodedId(destination), false);
} else { } else {
recipients = RecipientFactory.getRecipientsFromString(context, destination, false); recipients = RecipientFactory.getRecipientsFromString(context, destination, false);
} }

View file

@ -50,7 +50,7 @@ public class GroupUtil {
} }
if (title != null && !title.trim().isEmpty()) { if (title != null && !title.trim().isEmpty()) {
description += " Updated title to '" + title + "'."; description += " Title is now '" + title + "'.";
} }
return description; return description;