diff --git a/res/layout/group_create_activity.xml b/res/layout/group_create_activity.xml index eb639ff82..122d07f78 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 3c6987f80..3b1c61c22 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 8f42b8226..28fff6389 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 000000000..be7768405 --- /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 28e6b5332..786a7454a 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 f4088b608..c72d03f64 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 4aed1b603..f753c8451 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 036cb0789..f120fdf82 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 858c0ab55..a30f8effc 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 aac8f8a17..60c1c1839 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 0ddb9f935..24b7f19f2 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 52f957221..0536472a8 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