From 7beab36c6ae65faef68f8265e5c1f35cf57ee252 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Thu, 20 Feb 2014 15:41:52 -0800 Subject: [PATCH] updates to conversation menu, and updating of group info // FREEBIE --- res/layout/group_create_activity.xml | 2 +- res/menu/conversation_push_group_options.xml | 12 + res/menu/conversation_secure_identity.xml | 4 - res/menu/conversation_secure_no_identity.xml | 4 - res/menu/conversation_secure_sms.xml | 13 ++ res/values/strings.xml | 7 +- .../securesms/ConversationActivity.java | 28 ++- .../securesms/GroupCreateActivity.java | 220 +++++++++++++++--- .../contacts/ContactPhotoFactory.java | 6 + .../securesms/database/GroupDatabase.java | 41 +++- .../recipients/RecipientFactory.java | 5 + .../recipients/RecipientProvider.java | 5 + .../util/SelectedRecipientsAdapter.java | 54 +++-- 13 files changed, 348 insertions(+), 53 deletions(-) create mode 100644 res/menu/conversation_push_group_options.xml create mode 100644 res/menu/conversation_secure_sms.xml diff --git a/res/layout/group_create_activity.xml b/res/layout/group_create_activity.xml index eb639ff82f..122d07f78f 100644 --- a/res/layout/group_create_activity.xml +++ b/res/layout/group_create_activity.xml @@ -25,7 +25,7 @@ android:layout_height="70dp" position="bottom_right" android:layout_marginRight="10dp" - android:src="@drawable/icon" + android:src="@drawable/ic_group_photo" android:contentDescription="@string/GroupCreateActivity_avatar_content_description" /> + + + + + + + diff --git a/res/menu/conversation_secure_identity.xml b/res/menu/conversation_secure_identity.xml index 3c6987f804..3b1c61c223 100644 --- a/res/menu/conversation_secure_identity.xml +++ b/res/menu/conversation_secure_identity.xml @@ -7,10 +7,6 @@ - - - \ No newline at end of file diff --git a/res/menu/conversation_secure_no_identity.xml b/res/menu/conversation_secure_no_identity.xml index 8f42b8226b..28fff63894 100644 --- a/res/menu/conversation_secure_no_identity.xml +++ b/res/menu/conversation_secure_no_identity.xml @@ -8,10 +8,6 @@ - - - \ No newline at end of file diff --git a/res/menu/conversation_secure_sms.xml b/res/menu/conversation_secure_sms.xml new file mode 100644 index 0000000000..be77684054 --- /dev/null +++ b/res/menu/conversation_secure_sms.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 28e6b53327..786a7454a0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -124,10 +124,12 @@ New Group + Update Group Group Name New MMS Group You have selected a contact that doesn\'t support TextSecure groups, so this group will be MMS. You\'re not registered for using the data channel, so TextSecure groups are disabled. + You\'re not the owner of this group, so you cannot edit the title or picture. An unexpected error happened that has made group creation fail. You need at least one person in your group! One of the members of your group has a number that can\'t be read correctly. Please fix or remove that contact and try again. @@ -563,7 +565,8 @@ To - + Add member + You don\'t currently have any identity keys in your trust database. @@ -725,6 +728,8 @@ Add attachment + Update Group + Leave Group Add contact info Delete thread diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index f4088b6088..c72d03f645 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -75,6 +75,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.protocol.Tag; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.KeyCachingService; @@ -132,6 +133,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi private static final int PICK_VIDEO = 3; private static final int PICK_AUDIO = 4; private static final int PICK_CONTACT_INFO = 5; + private static final int GROUP_EDIT = 6; private MasterSecret masterSecret; private RecipientsPanel recipientsPanel; @@ -234,6 +236,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi case PICK_CONTACT_INFO: addContactInfo(data.getData()); break; + case GROUP_EDIT: + this.recipients = RecipientFactory.getRecipientsForIds(this, String.valueOf(getRecipients().getPrimaryRecipient().getRecipientId()), false); + initializeTitleBar(); + break; } } @@ -242,13 +248,18 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi MenuInflater inflater = this.getSupportMenuInflater(); menu.clear(); + boolean pushRegistered = TextSecurePreferences.isPushRegistered(this); + if (isSingleConversation() && isEncryptedConversation) { if (isAuthenticatedConversation) { inflater.inflate(R.menu.conversation_secure_identity, menu); } else { inflater.inflate(R.menu.conversation_secure_no_identity, menu); } - } else if (isSingleConversation()) { + if (!pushRegistered) { + inflater.inflate(R.menu.conversation_secure_sms, menu); + } + } else if (isSingleConversation() && !pushRegistered) { inflater.inflate(R.menu.conversation_insecure, menu); } @@ -264,6 +275,8 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi } else { menu.findItem(R.id.menu_distribution_conversation).setChecked(true); } + } else { + inflater.inflate(R.menu.conversation_push_group_options, menu); } } @@ -286,12 +299,25 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; + case R.id.menu_edit_group: handleEditPushGroup(); return true; + case R.id.menu_leave: handleLeavePushGroup(); return true; case android.R.id.home: handleReturnToConversationList(); return true; } return false; } + private void handleLeavePushGroup() { + Toast.makeText(getApplicationContext(), "not yet implemented", Toast.LENGTH_SHORT).show(); + } + + private void handleEditPushGroup() { + Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class); + intent.putExtra(GroupCreateActivity.MASTER_SECRET_EXTRA, masterSecret); + intent.putExtra(GroupCreateActivity.GROUP_RECIPIENT_EXTRA, recipients); + startActivityForResult(intent, GROUP_EDIT); + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { if (isEncryptedConversation) { diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index 4aed1b6033..f753c84516 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms; import android.app.Activity; +import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -11,6 +12,7 @@ import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.util.Pair; @@ -28,12 +30,14 @@ import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.components.PushRecipientsPanel; import org.thoughtcrime.securesms.contacts.ContactAccessor; +import org.thoughtcrime.securesms.contacts.RecipientsEditor; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.RecipientProvider; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; @@ -49,6 +53,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.directory.NotInDirectoryException; import org.whispersystems.textsecure.util.InvalidNumberException; +import org.whispersystems.textsecure.util.PhoneNumberFormatter; import java.io.ByteArrayOutputStream; import java.io.File; @@ -70,8 +75,9 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv private final static String TAG = GroupCreateActivity.class.getSimpleName(); - - public static final String MASTER_SECRET_EXTRA = "master_secret"; + public static final String GROUP_RECIPIENT_EXTRA = "group_recipient"; + public static final String GROUP_THREAD_EXTRA = "group_thread"; + public static final String MASTER_SECRET_EXTRA = "master_secret"; private final DynamicTheme dynamicTheme = new DynamicTheme(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); @@ -80,17 +86,24 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv private static final int PICK_CONTACT = 1; private static final int PICK_AVATAR = 2; - public static final int AVATAR_SIZE = 210; + public static final int AVATAR_SIZE = 210; private EditText groupName; private ListView lv; private PushRecipientsPanel recipientsPanel; private ImageView avatar; private TextView creatingText; + private ProgressDialog pd; + + private Recipients groupRecipient = null; + private long groupThread = -1; + private byte[] groupId = null; + private Set existingContacts = null; + private String existingTitle = null; + private Bitmap existingAvatarBmp = null; private MasterSecret masterSecret; private Bitmap avatarBmp; - private Set selectedContacts; @Override @@ -124,10 +137,9 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv private void disableWhisperGroupUi(int reasonResId) { View pushDisabled = findViewById(R.id.push_disabled); pushDisabled.setVisibility(View.VISIBLE); - ((TextView)findViewById(R.id.push_disabled_reason)).setText(reasonResId); + ((TextView) findViewById(R.id.push_disabled_reason)).setText(reasonResId); avatar.setEnabled(false); groupName.setEnabled(false); - getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title); } private void enableWhisperGroupUi() { @@ -135,7 +147,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv avatar.setEnabled(true); groupName.setEnabled(true); final CharSequence groupNameText = groupName.getText(); - if (groupNameText.length() > 0) + if (groupNameText != null && groupNameText.length() > 0) getSupportActionBar().setTitle(groupNameText); else getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title); @@ -155,8 +167,12 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv } private void addSelectedContact(Recipient contact) { - selectedContacts.add(contact); - if (!isActiveInDirectory(this, contact)) disableWhisperGroupUi(R.string.GroupCreateActivity_contacts_dont_support_push); + if (!selectedContacts.contains(contact) && (existingContacts == null || !existingContacts.contains(contact))) + selectedContacts.add(contact); + if (!isActiveInDirectory(this, contact)) { + disableWhisperGroupUi(R.string.GroupCreateActivity_contacts_dont_support_push); + getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title); + } } private void addAllSelectedContacts(Collection contacts) { @@ -177,6 +193,23 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv } private void initializeResources() { + groupRecipient = getIntent().getParcelableExtra(GROUP_RECIPIENT_EXTRA); + groupThread = getIntent().getLongExtra(GROUP_THREAD_EXTRA, -1); + if (groupRecipient != null) { + final String encodedGroupId = groupRecipient.getPrimaryRecipient().getNumber(); + if (encodedGroupId != null) { + try { + groupId = GroupUtil.getDecodedId(encodedGroupId); + } catch (IOException ioe) { + Log.w(TAG, "Couldn't decode the encoded groupId passed in via intent", ioe); + groupId = null; + } + if (groupId != null) { + new FillExistingGroupInfoAsyncTask().execute(); + } + } + } + masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA); lv = (ListView) findViewById(R.id.selected_contacts_list); @@ -192,14 +225,18 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void afterTextChanged(Editable editable) { - if (editable.length() > 0) - getSupportActionBar().setTitle(getString(R.string.GroupCreateActivity_actionbar_title) + ": " + editable.toString()); - else + if (editable.length() > 0) { + final int prefixResId = (groupId != null) + ? R.string.GroupCreateActivity_actionbar_update_title + : R.string.GroupCreateActivity_actionbar_title; + getSupportActionBar().setTitle(getString(prefixResId) + ": " + editable.toString()); + } else { getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title); + } } }); - SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this, android.R.id.text1, new ArrayList()); + SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this, android.R.id.text1, new ArrayList()); adapter.setOnRecipientDeletedListener(new SelectedRecipientsAdapter.OnRecipientDeletedListener() { @Override public void onRecipientDeleted(Recipient recipient) { @@ -235,6 +272,8 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv startActivityForResult(photoPickerIntent, PICK_AVATAR); } }); + + ((RecipientsEditor)findViewById(R.id.recipients_text)).setHint(R.string.recipients_panel__add_member); } private Uri getTempUri() { @@ -276,7 +315,8 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv finish(); return true; case R.id.menu_create_group: - handleGroupCreate(); + if (groupId == null) handleGroupCreate(); + else handleGroupUpdate(); return true; } @@ -297,6 +337,75 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv } } + private static List recipientsToNormalizedStrings(Collection recipients, String localNumber) { + final List e164numbers = new ArrayList(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 handleGroupUpdate() { + Log.i(TAG, "Updating group info."); + GroupDatabase db = DatabaseFactory.getGroupDatabase(this); + final String localNumber = TextSecurePreferences.getLocalNumber(this); + List e164numbers = recipientsToNormalizedStrings(selectedContacts, localNumber); + if (selectedContacts.size() > 0) { + db.add(groupId, localNumber, e164numbers); + GroupContext context = GroupContext.newBuilder() + .setId(ByteString.copyFrom(groupId)) + .setType(GroupContext.Type.ADD) + .addAllMembers(e164numbers) + .build(); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(this, groupRecipient, context, null); + try { + MessageSender.send(this, masterSecret, outgoingMessage, groupThread); + } catch (MmsException me) { + Log.w(TAG, "MmsException encountered when trying to add members to group.", me); + } + } + + GroupContext.Builder builder = GroupContext.newBuilder() + .setId(ByteString.copyFrom(groupId)) + .setType(GroupContext.Type.MODIFY); + boolean shouldSendUpdate = false; + final String title = groupName.getText().toString(); + if (existingTitle == null || (groupName.getText() != null && !existingTitle.equals(title))) { + builder.setName(title); + db.updateTitle(groupId, title); + shouldSendUpdate = true; + } + byte[] avatarBytes = null; + if (existingAvatarBmp == null || !existingAvatarBmp.equals(avatarBmp)) { + if (avatarBmp != null) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + avatarBmp.compress(Bitmap.CompressFormat.PNG, 100, stream); + avatarBytes = stream.toByteArray(); + } + db.updateAvatar(groupId, avatarBytes); + shouldSendUpdate = true; + } + + if (shouldSendUpdate) { + GroupContext context = builder.build(); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(this, groupRecipient, context, avatarBytes); + try { + MessageSender.send(this, masterSecret, outgoingMessage, groupThread); + } catch (MmsException me) { + Log.w(TAG, "MmsException encountered when trying to add members to group.", me); + } + } + + RecipientFactory.clearCache(groupRecipient.getPrimaryRecipient()); + + setResult(RESULT_OK, getIntent()); + finish(); + } + private void enableWhisperGroupCreatingUi() { findViewById(R.id.group_details_layout).setVisibility(View.GONE); findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE); @@ -315,7 +424,12 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter)lv.getAdapter(); adapter.clear(); for (Recipient contact : selectedContacts) { - adapter.add(contact); + adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, true)); + } + if (existingContacts != null) { + for (Recipient contact : existingContacts) { + adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, false)); + } } adapter.notifyDataSetChanged(); } @@ -336,7 +450,8 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv Recipient recipient = RecipientFactory.getRecipientsFromString(this, numberData.number, false) .getPrimaryRecipient(); - if (!selectedContacts.contains(recipient)) { + if (!selectedContacts.contains(recipient) + && (existingContacts == null || !existingContacts.contains(recipient))) { addSelectedContact(recipient); } } catch (RecipientFormattingException e) { @@ -347,14 +462,8 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv syncAdapterWithSelectedContacts(); break; case PICK_AVATAR: - if(resultCode == RESULT_OK) { - new DecodeCropAndSetAsyncTask().execute(); - break; - } else { - Log.i(TAG, "Avatar selection result was not RESULT_OK."); - } } } @@ -411,6 +520,14 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv ThreadDatabase.DistributionTypes.CONVERSATION); } + private static ArrayList setToArrayList(Set set) { + ArrayList arrayList = new ArrayList(set.size()); + for (T item : set) { + arrayList.add(item); + } + return arrayList; + } + private List getE164Numbers(Set recipients) throws InvalidNumberException { @@ -455,10 +572,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, resultThread.longValue()); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); - ArrayList selectedContactsList = new ArrayList(selectedContacts.size()); - for (Recipient recipient : selectedContacts) { - selectedContactsList.add(recipient); - } + ArrayList selectedContactsList = setToArrayList(selectedContacts); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, new Recipients(selectedContactsList)); startActivity(intent); finish(); @@ -525,4 +639,58 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv super.onProgressUpdate(values); } } + + private class FillExistingGroupInfoAsyncTask extends AsyncTask { + + @Override + protected void onPreExecute() { + pd = new ProgressDialog(GroupCreateActivity.this); + pd.setTitle("Loading group details..."); + pd.setMessage("Please wait."); + pd.setCancelable(false); + pd.setIndeterminate(true); + pd.show(); + } + + @Override + protected Boolean doInBackground(Void... voids) { + final GroupDatabase db = DatabaseFactory.getGroupDatabase(GroupCreateActivity.this); + final Recipients recipients = db.getGroupMembers(groupId); + if (recipients != null) { + final List recipientList = recipients.getRecipientsList(); + if (recipientList != null) { + if (existingContacts == null) + existingContacts = new HashSet(recipientList.size()); + existingContacts.addAll(recipientList); + } + } + final GroupDatabase.Reader groupReader = db.getGroup(groupId); + GroupDatabase.GroupRecord group = groupReader.getNext(); + if (group != null) { + existingTitle = group.getTitle(); + final byte[] existingAvatar = group.getAvatar(); + if (existingAvatar != null) { + existingAvatarBmp = BitmapUtil.getCircleCroppedBitmap( + BitmapFactory.decodeByteArray(existingAvatar, 0, existingAvatar.length)); + } + return (group.getOwner() != null && group.getOwner().equals(TextSecurePreferences.getLocalNumber(GroupCreateActivity.this))); + } + return null; + } + + @Override + protected void onPostExecute(Boolean isOwner) { + super.onPostExecute(isOwner); + + if (pd != null) pd.dismiss(); + if (existingTitle != null) groupName.setText(existingTitle); + if (existingAvatarBmp != null) avatar.setImageBitmap(existingAvatarBmp); + if (existingContacts != null) syncAdapterWithSelectedContacts(); + if (!isOwner) { + disableWhisperGroupUi(R.string.GroupCreateActivity_you_dont_own_this_group); + getSupportActionBar().setTitle(getString(R.string.GroupCreateActivity_actionbar_update_title) + + (TextUtils.isEmpty(existingTitle) ? "" : ": " + existingTitle)); + } + } + } } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java b/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java index 036cb0789e..f120fdf82e 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java @@ -9,6 +9,7 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.LRUCache; import java.io.InputStream; @@ -73,6 +74,11 @@ public class ContactPhotoFactory { localUserContactPhotoCache.clear(); } + public static void clearCache(Recipient recipient) { + if (localUserContactPhotoCache.containsKey(recipient.getContactUri())) + localUserContactPhotoCache.remove(recipient.getContactUri()); + } + private static Bitmap getContactPhoto(Context context, Uri uri) { InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri); diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 858c0ab557..a30f8effc6 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -4,6 +4,7 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; import android.util.Log; @@ -21,6 +22,8 @@ import org.whispersystems.textsecure.util.Util; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -138,6 +141,13 @@ public class GroupDatabase extends Database { new String[] {GroupUtil.getEncodedId(groupId), source}); } + public void updateTitle(byte[] groupId, String title) { + ContentValues contentValues = new ContentValues(); + contentValues.put(TITLE, title); + databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", + new String[] {GroupUtil.getEncodedId(groupId)}); + } + public void updateAvatar(byte[] groupId, Bitmap avatar) { updateAvatar(groupId, BitmapUtil.toByteArray(avatar)); } @@ -198,6 +208,28 @@ public class GroupDatabase extends Database { } } + public String getOwner(byte[] id) { + Cursor cursor = null; + + try { + SQLiteDatabase readableDatabase = databaseHelper.getReadableDatabase(); + if (readableDatabase == null) + return null; + + cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {OWNER}, + GROUP_ID + " = ?", + new String[] {GroupUtil.getEncodedId(id)}, + null, null, null); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndexOrThrow(OWNER)); + } + return null; + } finally { + if (cursor != null) + cursor.close(); + } + } + public byte[] allocateGroupId() { try { byte[] groupId = new byte[16]; @@ -224,6 +256,7 @@ public class GroupDatabase extends Database { return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)), cursor.getString(cursor.getColumnIndexOrThrow(TITLE)), + cursor.getString(cursor.getColumnIndexOrThrow(OWNER)), cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR)), cursor.getLong(cursor.getColumnIndexOrThrow(AVATAR_ID)), @@ -242,6 +275,7 @@ public class GroupDatabase extends Database { private final String id; private final String title; + private final String owner; private final List members; private final byte[] avatar; private final long avatarId; @@ -249,12 +283,13 @@ public class GroupDatabase extends Database { private final String avatarContentType; private final String relay; - public GroupRecord(String id, String title, String members, byte[] avatar, + public GroupRecord(String id, String title, String owner, String members, byte[] avatar, long avatarId, byte[] avatarKey, String avatarContentType, String relay) { this.id = id; this.title = title; + this.owner = owner; this.members = Util.split(members, ","); this.avatar = avatar; this.avatarId = avatarId; @@ -275,6 +310,10 @@ public class GroupDatabase extends Database { return title; } + public String getOwner() { + return owner; + } + public List getMembers() { return members; } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java index aac8f8a172..60c1c1839b 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java @@ -138,4 +138,9 @@ public class RecipientFactory { provider.clearCache(); } + public static void clearCache(Recipient recipient) { + ContactPhotoFactory.clearCache(recipient); + provider.clearCache(recipient); + } + } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index 0ddb9f9359..24b7f19f2a 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -116,6 +116,11 @@ public class RecipientProvider { recipientCache.clear(); } + public void clearCache(Recipient recipient) { + if (recipientCache.containsKey(recipient.getRecipientId())) + recipientCache.remove(recipient.getRecipientId()); + } + private RecipientDetails getRecipientDetails(Context context, String number) { Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION, diff --git a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java index 52f9572214..0536472a8b 100644 --- a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java +++ b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java @@ -12,18 +12,17 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.Recipient; import java.util.ArrayList; -import java.util.List; -public class SelectedRecipientsAdapter extends ArrayAdapter { +public class SelectedRecipientsAdapter extends ArrayAdapter { - private ArrayList recipients; + private ArrayList recipients; private OnRecipientDeletedListener onRecipientDeletedListener; public SelectedRecipientsAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); } - public SelectedRecipientsAdapter(Context context, int resource, ArrayList recipients) { + public SelectedRecipientsAdapter(Context context, int resource, ArrayList recipients) { super(context, resource, recipients); this.recipients = recipients; } @@ -41,7 +40,9 @@ public class SelectedRecipientsAdapter extends ArrayAdapter { } - Recipient p = getItem(position); + final RecipientWrapper rw = getItem(position); + final Recipient p = rw.getRecipient(); + final boolean modifiable = rw.isModifiable(); if (p != null) { @@ -53,20 +54,25 @@ public class SelectedRecipientsAdapter extends ArrayAdapter { name.setText(p.getName()); } if (phone != null) { - phone.setText(p.getNumber()); } if (delete != null) { - delete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (onRecipientDeletedListener != null) { - onRecipientDeletedListener.onRecipientDeleted(recipients.get(position)); + if (modifiable) { + delete.setVisibility(View.VISIBLE); + delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (onRecipientDeletedListener != null) { + onRecipientDeletedListener.onRecipientDeleted(recipients.get(position).getRecipient()); + } + recipients.remove(position); + SelectedRecipientsAdapter.this.notifyDataSetChanged(); } - recipients.remove(position); - SelectedRecipientsAdapter.this.notifyDataSetChanged(); - } - }); + }); + } else { + delete.setVisibility(View.INVISIBLE); + delete.setOnClickListener(null); + } } } @@ -80,4 +86,22 @@ public class SelectedRecipientsAdapter extends ArrayAdapter { public interface OnRecipientDeletedListener { public void onRecipientDeleted(Recipient recipient); } + + public static class RecipientWrapper { + private final Recipient recipient; + private final boolean modifiable; + + public RecipientWrapper(final Recipient recipient, final boolean modifiable) { + this.recipient = recipient; + this.modifiable = modifiable; + } + + public Recipient getRecipient() { + return recipient; + } + + public boolean isModifiable() { + return modifiable; + } + } } \ No newline at end of file