From d05097a6fd19357ead136882eba7e1edd0a03e13 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Thu, 5 Nov 2015 10:41:43 -0800 Subject: [PATCH] Refactor group logic (no visual changes) Closes #4480 // FREEBIE --- .../securesms/GroupCreateActivity.java | 636 +++++++----------- .../securesms/database/GroupDatabase.java | 10 +- .../securesms/groups/GroupManager.java | 133 ++++ .../recipients/RecipientFactory.java | 4 +- .../securesms/util/BitmapUtil.java | 9 +- .../util/SelectedRecipientsAdapter.java | 168 +++-- 6 files changed, 508 insertions(+), 452 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/groups/GroupManager.java diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index 493e59eb07..911667d163 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -21,16 +21,12 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.Editable; -import android.text.TextWatcher; import android.util.Log; -import android.util.Pair; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -44,58 +40,53 @@ import android.widget.Toast; import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; -import com.google.protobuf.ByteString; import com.soundcloud.android.crop.Crop; -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.attachments.UriAttachment; import org.thoughtcrime.securesms.components.PushRecipientsPanel; -import org.thoughtcrime.securesms.contacts.ContactsCursorLoader; +import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener; import org.thoughtcrime.securesms.contacts.RecipientsEditor; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.NotInDirectoryException; -import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.TextSecureDirectory; import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; +import org.thoughtcrime.securesms.groups.GroupManager; +import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; import org.thoughtcrime.securesms.mms.RoundedCorners; -import org.thoughtcrime.securesms.providers.SingleUseBlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; +import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.ViewUtil; +import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.util.InvalidNumberException; -import org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; -import ws.com.google.android.mms.ContentType; -import ws.com.google.android.mms.MmsException; - - /** * Activity to create and update groups * * @author Jake McGinty */ -public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { +public class GroupCreateActivity extends PassphraseRequiredActionBarActivity + implements OnRecipientDeletedListener, + RecipientsPanelChangedListener +{ private final static String TAG = GroupCreateActivity.class.getSimpleName(); @@ -106,25 +97,16 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private static final int PICK_CONTACT = 1; - private static final int PICK_AVATAR = 2; public static final int AVATAR_SIZE = 210; - private EditText groupName; - private ListView lv; - private PushRecipientsPanel recipientsPanel; - private ImageView avatar; - private TextView creatingText; + private EditText groupName; + private ListView lv; + private ImageView avatar; + private TextView creatingText; + private MasterSecret masterSecret; + private Bitmap avatarBmp; - private Recipient 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; + @NonNull private Optional groupToUpdate = Optional.absent(); @Override protected void onPreCreate() { @@ -137,10 +119,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { this.masterSecret = masterSecret; setContentView(R.layout.group_create_activity); + //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - selectedContacts = new HashSet<>(); initializeResources(); + initializeExistingGroup(); } @Override @@ -148,17 +130,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { super.onResume(); dynamicTheme.onResume(this); dynamicLanguage.onResume(this); - getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title); - if (!TextSecurePreferences.isPushRegistered(this)) { - disableWhisperGroupUi(R.string.GroupCreateActivity_you_dont_support_push); - } + updateViewState(); } - private boolean whisperGroupUiEnabled() { - return groupName.isEnabled() && avatar.isEnabled(); + private boolean isSignalGroup() { + return TextSecurePreferences.isPushRegistered(this) && !getAdapter().hasNonPushMembers(); } - private void disableWhisperGroupUi(int reasonResId) { + private void disableSignalGroupViews(int reasonResId) { View pushDisabled = findViewById(R.id.push_disabled); pushDisabled.setVisibility(View.VISIBLE); ((TextView) findViewById(R.id.push_disabled_reason)).setText(reasonResId); @@ -166,46 +145,44 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { groupName.setEnabled(false); } - private void enableWhisperGroupUi() { + private void enableSignalGroupViews() { findViewById(R.id.push_disabled).setVisibility(View.GONE); avatar.setEnabled(true); groupName.setEnabled(true); - final CharSequence groupNameText = groupName.getText(); - if (groupNameText != null && groupNameText.length() > 0) { - getSupportActionBar().setTitle(groupNameText); + } + + @SuppressWarnings("ConstantConditions") + private void updateViewState() { + if (!TextSecurePreferences.isPushRegistered(this)) { + disableSignalGroupViews(R.string.GroupCreateActivity_you_dont_support_push); + getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title); + } else if (getAdapter().hasNonPushMembers()) { + disableSignalGroupViews(R.string.GroupCreateActivity_contacts_dont_support_push); + getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title); } else { + enableSignalGroupViews(); getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title); } } private static boolean isActiveInDirectory(Context context, Recipient recipient) { try { - if (!TextSecureDirectory.getInstance(context).isSecureTextSupported(Util.canonicalizeNumber(context, recipient.getNumber()))) { - return false; - } - } catch (NotInDirectoryException e) { - return false; - } catch (InvalidNumberException e) { + return TextSecureDirectory.getInstance(context) + .isSecureTextSupported(Util.canonicalizeNumber(context, recipient.getNumber())); + } catch (NotInDirectoryException | InvalidNumberException e) { return false; } - return true; } - private void addSelectedContact(Recipient contact) { + private void addSelectedContact(@NonNull Recipient contact) { final boolean isPushUser = isActiveInDirectory(this, contact); - if (existingContacts != null && !isPushUser) { - Toast.makeText(getApplicationContext(), - R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group, - Toast.LENGTH_LONG).show(); + if (groupToUpdate.isPresent() && !isPushUser) { + Toast.makeText(this, R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group, Toast.LENGTH_LONG).show(); return; } - if (!selectedContacts.contains(contact) && (existingContacts == null || !existingContacts.contains(contact))) - selectedContacts.add(contact); - if (!isPushUser) { - disableWhisperGroupUi(R.string.GroupCreateActivity_contacts_dont_support_push); - getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title); - } + getAdapter().add(contact, isPushUser); + updateViewState(); } private void addAllSelectedContacts(Collection contacts) { @@ -214,88 +191,40 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { } } - private void removeSelectedContact(Recipient contact) { - selectedContacts.remove(contact); - if (!isActiveInDirectory(this, contact)) { - for (Recipient recipient : selectedContacts) { - if (!isActiveInDirectory(this, recipient)) - return; - } - enableWhisperGroupUi(); - } - } - private void initializeResources() { - groupRecipient = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(GROUP_RECIPIENT_EXTRA, -1), true); - groupThread = getIntent().getLongExtra(GROUP_THREAD_EXTRA, -1); - if (groupRecipient != null) { - final String encodedGroupId = groupRecipient.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(); - } - } - } - - lv = (ListView) findViewById(R.id.selected_contacts_list); - avatar = (ImageView) findViewById(R.id.avatar); - groupName = (EditText) findViewById(R.id.group_name); - creatingText = (TextView) findViewById(R.id.creating_group_text); - recipientsPanel = (PushRecipientsPanel) findViewById(R.id.recipients); - - groupName.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } - @Override - public void afterTextChanged(Editable editable) { - final int prefixResId = (groupId != null) - ? R.string.GroupCreateActivity_actionbar_update_title - : R.string.GroupCreateActivity_actionbar_title; - if (editable.length() > 0) { - getSupportActionBar().setTitle(getString(prefixResId) + ": " + editable.toString()); - } else { - getSupportActionBar().setTitle(prefixResId); - } - } - }); - - SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this, android.R.id.text1, new ArrayList()); - adapter.setOnRecipientDeletedListener(new SelectedRecipientsAdapter.OnRecipientDeletedListener() { - @Override - public void onRecipientDeleted(Recipient recipient) { - removeSelectedContact(recipient); - } - }); + RecipientsEditor recipientsEditor = ViewUtil.findById(this, R.id.recipients_text); + PushRecipientsPanel recipientsPanel = ViewUtil.findById(this, R.id.recipients); + lv = ViewUtil.findById(this, R.id.selected_contacts_list); + avatar = ViewUtil.findById(this, R.id.avatar); + groupName = ViewUtil.findById(this, R.id.group_name); + creatingText = ViewUtil.findById(this, R.id.creating_group_text); + SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this); + adapter.setOnRecipientDeletedListener(this); lv.setAdapter(adapter); - - recipientsPanel.setPanelChangeListener(new PushRecipientsPanel.RecipientsPanelChangedListener() { - @Override - public void onRecipientsPanelUpdate(Recipients recipients) { - Log.i(TAG, "onRecipientsPanelUpdate received."); - if (recipients != null) { - addAllSelectedContacts(recipients.getRecipientsList()); - syncAdapterWithSelectedContacts(); - } - } - }); - (findViewById(R.id.contacts_button)).setOnClickListener(new AddRecipientButtonListener()); - + recipientsEditor.setHint(R.string.recipients_panel__add_member); + recipientsPanel.setPanelChangeListener(this); + findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener()); avatar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Crop.pickImage(GroupCreateActivity.this); } }); + } - ((RecipientsEditor)findViewById(R.id.recipients_text)).setHint(R.string.recipients_panel__add_member); + private void initializeExistingGroup() { + final String encodedGroupId = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(GROUP_RECIPIENT_EXTRA, -1), true) + .getNumber(); + byte[] groupId; + 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(this).execute(groupId); + } } @Override @@ -316,65 +245,58 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { finish(); return true; case R.id.menu_create_group: - if (groupId == null) handleGroupCreate(); - else handleGroupUpdate(); + if (groupToUpdate.isPresent()) handleGroupUpdate(); + else handleGroupCreate(); return true; } return false; } + @Override + public void onRecipientDeleted(Recipient recipient) { + getAdapter().remove(recipient); + updateViewState(); + } + + @Override + public void onRecipientsPanelUpdate(Recipients recipients) { + if (recipients != null) addAllSelectedContacts(recipients.getRecipientsList()); + } + private void handleGroupCreate() { - if (selectedContacts.size() < 1) { + if (getAdapter().getCount() < 1) { Log.i(TAG, getString(R.string.GroupCreateActivity_contacts_no_members)); Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_no_members, Toast.LENGTH_SHORT).show(); return; } - if (whisperGroupUiEnabled()) { - enableWhisperGroupProgressUi(false); - new CreateWhisperGroupAsyncTask().execute(); + if (isSignalGroup()) { + new CreateSignalGroupTask(this, masterSecret, avatarBmp, getGroupName(), getAdapter().getRecipients()).execute(); } else { - new CreateMmsGroupAsyncTask().execute(); + new CreateMmsGroupTask(this, getAdapter().getRecipients()).execute(); } } private void handleGroupUpdate() { - if (whisperGroupUiEnabled()) { - enableWhisperGroupProgressUi(true); - } - new UpdateWhisperGroupAsyncTask().execute(); + new UpdateSignalGroupTask(this, masterSecret, groupToUpdate.get().id, avatarBmp, + getGroupName(), getAdapter().getRecipients()).execute(); } - private void enableWhisperGroupProgressUi(boolean isGroupUpdate) { - findViewById(R.id.group_details_layout).setVisibility(View.GONE); - findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE); - findViewById(R.id.menu_create_group).setVisibility(View.GONE); - if (groupName.getText() != null) { - final int titleResId = isGroupUpdate - ? R.string.GroupCreateActivity_updating_group - : R.string.GroupCreateActivity_creating_group; - creatingText.setText(getString(titleResId, groupName.getText().toString())); - } + private void handleOpenConversation(long threadId, Recipients recipients) { + Intent intent = new Intent(this, ConversationActivity.class); + intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); + intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); + intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds()); + startActivity(intent); + finish(); } - private void disableWhisperGroupProgressUi() { - findViewById(R.id.group_details_layout).setVisibility(View.VISIBLE); - findViewById(R.id.creating_group_layout).setVisibility(View.GONE); - findViewById(R.id.menu_create_group).setVisibility(View.VISIBLE); + private SelectedRecipientsAdapter getAdapter() { + return (SelectedRecipientsAdapter)lv.getAdapter(); } - private void syncAdapterWithSelectedContacts() { - SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter)lv.getAdapter(); - adapter.clear(); - for (Recipient contact : selectedContacts) { - adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, true)); - } - if (existingContacts != null) { - for (Recipient contact : existingContacts) { - adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, false)); - } - } - adapter.notifyDataSetChanged(); + private @Nullable String getGroupName() { + return groupName.getText() != null ? groupName.getText().toString() : null; } @Override @@ -389,15 +311,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { case PICK_CONTACT: List selected = data.getStringArrayListExtra("contacts"); for (String contact : selected) { - Recipient recipient = RecipientFactory.getRecipientsFromString(this, contact, false).getPrimaryRecipient(); - - if (!selectedContacts.contains(recipient) && - (existingContacts == null || !existingContacts.contains(recipient)) && - recipient != null) { - addSelectedContact(recipient); - } + final Recipient recipient = RecipientFactory.getRecipientsFromString(this, contact, false).getPrimaryRecipient(); + if (recipient != null) addSelectedContact(recipient); } - syncAdapterWithSelectedContacts(); break; case Crop.REQUEST_PICK: @@ -407,7 +323,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { Glide.with(this).load(Crop.getOutput(data)).asBitmap().skipMemoryCache(true) .centerCrop().override(AVATAR_SIZE, AVATAR_SIZE) .into(new SimpleTarget() { - @Override public void onResourceReady(Bitmap resource, + @Override + public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { avatarBmp = resource; @@ -423,121 +340,36 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { @Override public void onClick(View v) { Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class); - if (existingContacts != null) intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, - ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY); + if (groupToUpdate.isPresent()) intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, + ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY); startActivityForResult(intent, PICK_CONTACT); } } - private Pair handleCreatePushGroup(String groupName, byte[] avatar, - Set members) - throws InvalidNumberException, MmsException - { - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this); - byte[] groupId = groupDatabase.allocateGroupId(); - Set memberE164Numbers = getE164Numbers(members); + private static class CreateMmsGroupTask extends AsyncTask { + private GroupCreateActivity activity; + private Set members; - memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this)); - - groupDatabase.create(groupId, groupName, new LinkedList(memberE164Numbers), null, null); - groupDatabase.updateAvatar(groupId, avatar); - - return handlePushOperation(groupId, groupName, avatar, memberE164Numbers); - } - - private Pair handleUpdatePushGroup(byte[] groupId, String groupName, - byte[] avatar, Set members) - throws InvalidNumberException, MmsException - { - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this); - Set memberE164Numbers = getE164Numbers(members); - memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this)); - - for (String number : memberE164Numbers) - Log.w(TAG, "Updating: " + number); - - groupDatabase.updateMembers(groupId, new LinkedList(memberE164Numbers)); - groupDatabase.updateTitle(groupId, groupName); - groupDatabase.updateAvatar(groupId, avatar); - - return handlePushOperation(groupId, groupName, avatar, memberE164Numbers); - } - - private Pair handlePushOperation(byte[] groupId, String groupName, - @Nullable byte[] avatar, - Set e164numbers) - throws InvalidNumberException - { - Attachment avatarAttachment = null; - String groupRecipientId = GroupUtil.getEncodedId(groupId); - Recipients groupRecipient = RecipientFactory.getRecipientsFromString(this, groupRecipientId, false); - - GroupContext context = GroupContext.newBuilder() - .setId(ByteString.copyFrom(groupId)) - .setType(GroupContext.Type.UPDATE) - .setName(groupName) - .addAllMembers(e164numbers) - .build(); - - if (avatar != null) { - Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar); - avatarAttachment = new UriAttachment(avatarUri, ContentType.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length); + public CreateMmsGroupTask(GroupCreateActivity activity, Set members) { + this.activity = activity; + this.members = members; } - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, context, avatarAttachment, System.currentTimeMillis()); - long threadId = MessageSender.send(this, masterSecret, outgoingMessage, -1, false); - - return new Pair<>(threadId, groupRecipient); - } - - private long handleCreateMmsGroup(Set members) { - Recipients recipients = RecipientFactory.getRecipientsFor(this, new LinkedList<>(members), false); - return DatabaseFactory.getThreadDatabase(this) - .getThreadIdFor(recipients, - 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 Set getE164Numbers(Set recipients) - throws InvalidNumberException - { - Set results = new HashSet(); - - for (Recipient recipient : recipients) { - results.add(Util.canonicalizeNumber(this, recipient.getNumber())); - } - - return results; - } - - private class CreateMmsGroupAsyncTask extends AsyncTask { - @Override - protected Long doInBackground(Void... voids) { - return handleCreateMmsGroup(selectedContacts); + protected Long doInBackground(Void... avoid) { + Recipients recipients = RecipientFactory.getRecipientsFor(activity, members, false); + return DatabaseFactory.getThreadDatabase(activity) + .getThreadIdFor(recipients, ThreadDatabase.DistributionTypes.CONVERSATION); } @Override protected void onPostExecute(Long resultThread) { if (resultThread > -1) { - Intent intent = new Intent(GroupCreateActivity.this, ConversationActivity.class); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, resultThread.longValue()); - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); - - ArrayList selectedContactsList = setToArrayList(selectedContacts); - intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, RecipientFactory.getRecipientsFor(GroupCreateActivity.this, selectedContactsList, true).getIds()); - startActivity(intent); - finish(); + activity.handleOpenConversation(resultThread, + RecipientFactory.getRecipientsFor(activity, members, true)); } else { - Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show(); - finish(); + Toast.makeText(activity, R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show(); + activity.finish(); } } @@ -547,145 +379,167 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { } } - private class UpdateWhisperGroupAsyncTask extends AsyncTask> { - private long RES_BAD_NUMBER = -2; - private long RES_MMS_EXCEPTION = -3; - @Override - protected Pair doInBackground(Void... params) { - byte[] avatarBytes = null; - final Bitmap bitmap; - if (avatarBmp == null) bitmap = existingAvatarBmp; - else bitmap = avatarBmp; + private abstract static class SignalGroupTask extends AsyncTask> { + protected GroupCreateActivity activity; + protected MasterSecret masterSecret; + protected Bitmap avatar; + protected Set members; + protected String name; - if (bitmap != null) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - avatarBytes = stream.toByteArray(); + public SignalGroupTask(GroupCreateActivity activity, + MasterSecret masterSecret, + Bitmap avatar, + String name, + Set members) + { + this.activity = activity; + this.masterSecret = masterSecret; + this.avatar = avatar; + this.name = name; + this.members = members; + } + + @Override + protected void onPreExecute() { + activity.findViewById(R.id.group_details_layout).setVisibility(View.GONE); + activity.findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE); + activity.findViewById(R.id.menu_create_group).setVisibility(View.GONE); + final int titleResId = activity.groupToUpdate.isPresent() + ? R.string.GroupCreateActivity_updating_group + : R.string.GroupCreateActivity_creating_group; + activity.creatingText.setText(activity.getString(titleResId, activity.getGroupName())); } - final String name = (groupName.getText() != null) ? groupName.getText().toString() : null; + + @Override + protected void onPostExecute(Optional groupActionResultOptional) { + if (activity.isFinishing()) return; + activity.findViewById(R.id.group_details_layout).setVisibility(View.VISIBLE); + activity.findViewById(R.id.creating_group_layout).setVisibility(View.GONE); + activity.findViewById(R.id.menu_create_group).setVisibility(View.VISIBLE); + } + } + + private static class CreateSignalGroupTask extends SignalGroupTask { + public CreateSignalGroupTask(GroupCreateActivity activity, MasterSecret masterSecret, Bitmap avatar, String name, Set members) { + super(activity, masterSecret, avatar, name, members); + } + + @Override + protected Optional doInBackground(Void... aVoid) { try { - Set unionContacts = new HashSet(selectedContacts); - unionContacts.addAll(existingContacts); - return handleUpdatePushGroup(groupId, name, avatarBytes, unionContacts); - } catch (MmsException e) { - Log.w(TAG, e); - return new Pair(RES_MMS_EXCEPTION, null); + return Optional.of(GroupManager.createGroup(activity, masterSecret, members, avatar, name)); } catch (InvalidNumberException e) { - Log.w(TAG, e); - return new Pair(RES_BAD_NUMBER, null); + return Optional.absent(); } } @Override - protected void onPostExecute(Pair groupInfo) { - final long threadId = groupInfo.first; - final Recipients recipients = groupInfo.second; - if (threadId > -1) { - Intent intent = getIntent(); - intent.putExtra(GROUP_THREAD_EXTRA, threadId); - intent.putExtra(GROUP_RECIPIENT_EXTRA, recipients.getIds()); - setResult(RESULT_OK, intent); - finish(); - } else if (threadId == RES_BAD_NUMBER) { - Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show(); - disableWhisperGroupProgressUi(); - } else if (threadId == RES_MMS_EXCEPTION) { - Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show(); - setResult(RESULT_CANCELED); - finish(); + protected void onPostExecute(Optional result) { + if (result.isPresent() && result.get().getThreadId() > -1) { + if (!activity.isFinishing()) { + activity.handleOpenConversation(result.get().getThreadId(), result.get().getGroupRecipient()); + } + } else { + super.onPostExecute(result); + Toast.makeText(activity.getApplicationContext(), + R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show(); } } } - private class CreateWhisperGroupAsyncTask extends AsyncTask> { - private long RES_BAD_NUMBER = -2; - private long RES_MMS_EXCEPTION = -3; + private static class UpdateSignalGroupTask extends SignalGroupTask { + private byte[] groupId; + + public UpdateSignalGroupTask(GroupCreateActivity activity, + MasterSecret masterSecret, byte[] groupId, Bitmap avatar, String name, + Set members) + { + super(activity, masterSecret, avatar, name, members); + this.groupId = groupId; + } @Override - protected Pair doInBackground(Void... voids) { - byte[] avatarBytes = null; - if (avatarBmp != null) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - avatarBmp.compress(Bitmap.CompressFormat.PNG, 100, stream); - avatarBytes = stream.toByteArray(); - } - final String name = (groupName.getText() != null) ? groupName.getText().toString() : null; + protected Optional doInBackground(Void... aVoid) { try { - return handleCreatePushGroup(name, avatarBytes, selectedContacts); - } catch (MmsException e) { - Log.w(TAG, e); - return new Pair(RES_MMS_EXCEPTION, null); + return Optional.of(GroupManager.updateGroup(activity, masterSecret, groupId, members, avatar, name)); } catch (InvalidNumberException e) { - Log.w(TAG, e); - return new Pair(RES_BAD_NUMBER, null); + return Optional.absent(); } } @Override - protected void onPostExecute(Pair groupInfo) { - super.onPostExecute(groupInfo); - final long threadId = groupInfo.first; - final Recipients recipients = groupInfo.second; - if (threadId > -1) { - Intent intent = new Intent(GroupCreateActivity.this, ConversationActivity.class); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); - intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds()); - startActivity(intent); - finish(); - } else if (threadId == RES_BAD_NUMBER) { - Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show(); - disableWhisperGroupProgressUi(); - } else if (threadId == RES_MMS_EXCEPTION) { - Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show(); - finish(); + protected void onPostExecute(Optional result) { + if (result.isPresent() && result.get().getThreadId() > -1) { + if (!activity.isFinishing()) { + Intent intent = activity.getIntent(); + intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId()); + intent.putExtra(GROUP_RECIPIENT_EXTRA, result.get().getGroupRecipient().getIds()); + activity.setResult(RESULT_OK, intent); + activity.finish(); + } + } else { + super.onPostExecute(result); + Toast.makeText(activity.getApplicationContext(), + R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show(); } } - - @Override - protected void onProgressUpdate(Void... values) { - super.onProgressUpdate(values); - } } - private class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask { + private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask> { + private GroupCreateActivity activity; - public FillExistingGroupInfoAsyncTask() { - super(GroupCreateActivity.this, + public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) { + super(activity, R.string.GroupCreateActivity_loading_group_details, R.string.please_wait); + this.activity = activity; } @Override - protected Void doInBackground(Void... voids) { - final GroupDatabase db = DatabaseFactory.getGroupDatabase(GroupCreateActivity.this); - final Recipients recipients = db.getGroupMembers(groupId, false); - if (recipients != null) { - final List recipientList = recipients.getRecipientsList(); - if (recipientList != null) { - if (existingContacts == null) - existingContacts = new HashSet<>(recipientList.size()); - existingContacts.addAll(recipientList); - } - } - GroupDatabase.GroupRecord group = db.getGroup(groupId); + protected Optional doInBackground(byte[]... groupIds) { + final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity); + final Recipients recipients = db.getGroupMembers(groupIds[0], false); + final GroupRecord group = db.getGroup(groupIds[0]); + final Set existingContacts = new HashSet<>(recipients.getRecipientsList().size()); + existingContacts.addAll(recipients.getRecipientsList()); + if (group != null) { - existingTitle = group.getTitle(); - final byte[] existingAvatar = group.getAvatar(); - if (existingAvatar != null) { - existingAvatarBmp = BitmapFactory.decodeByteArray(existingAvatar, 0, existingAvatar.length); - } + return Optional.of(new GroupData(groupIds[0], + existingContacts, + BitmapUtil.fromByteArray(group.getAvatar()), + group.getTitle())); + } else { + return Optional.absent(); } - return null; } @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); + protected void onPostExecute(Optional group) { + super.onPostExecute(group); - if (existingTitle != null) groupName.setText(existingTitle); - if (existingAvatarBmp != null) avatar.setImageBitmap(existingAvatarBmp); - if (existingContacts != null) syncAdapterWithSelectedContacts(); + if (group.isPresent() && !activity.isFinishing()) { + activity.groupToUpdate = group; + + activity.groupName.setText(group.get().name); + if (group.get().avatar != null) activity.avatar.setImageBitmap(group.get().avatar); + SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(activity, group.get().recipients); + adapter.setOnRecipientDeletedListener(activity); + activity.lv.setAdapter(adapter); + } + } + } + + private static class GroupData { + byte[] id; + Set recipients; + Bitmap avatar; + String name; + + public GroupData(byte[] id, Set recipients, Bitmap avatar, String name) { + this.id = id; + this.recipients = recipients; + this.avatar = avatar; + this.name = name; } } } diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index cdc54aeec6..d11fbfa56b 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.database; +import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -8,6 +9,8 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.recipients.Recipient; @@ -67,7 +70,8 @@ public class GroupDatabase extends Database { super(context, databaseHelper); } - public GroupRecord getGroup(byte[] groupId) { + public @Nullable GroupRecord getGroup(byte[] groupId) { + @SuppressLint("Recycle") Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", new String[] {GroupUtil.getEncodedId(groupId)}, null, null, null); @@ -92,7 +96,7 @@ public class GroupDatabase extends Database { return new Reader(cursor); } - public Recipients getGroupMembers(byte[] groupId, boolean includeSelf) { + public @NonNull Recipients getGroupMembers(byte[] groupId, boolean includeSelf) { String localNumber = TextSecurePreferences.getLocalNumber(context); List members = getCurrentMembers(groupId); List recipients = new LinkedList<>(); @@ -248,7 +252,7 @@ public class GroupDatabase extends Database { this.cursor = cursor; } - public GroupRecord getNext() { + public @Nullable GroupRecord getNext() { if (cursor == null || !cursor.moveToNext()) { return null; } diff --git a/src/org/thoughtcrime/securesms/groups/GroupManager.java b/src/org/thoughtcrime/securesms/groups/GroupManager.java new file mode 100644 index 0000000000..c98c6c18b4 --- /dev/null +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -0,0 +1,133 @@ +package org.thoughtcrime.securesms.groups; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.protobuf.ByteString; + +import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.UriAttachment; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.AttachmentDatabase; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; +import org.thoughtcrime.securesms.providers.SingleUseBlobProvider; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.util.BitmapUtil; +import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.api.util.InvalidNumberException; +import org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +import ws.com.google.android.mms.ContentType; + +public class GroupManager { + public static @NonNull GroupActionResult createGroup(@NonNull Context context, + @NonNull MasterSecret masterSecret, + @NonNull Set members, + @Nullable Bitmap avatar, + @Nullable String name) + throws InvalidNumberException + { + final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); + final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); + final byte[] groupId = groupDatabase.allocateGroupId(); + final Set memberE164Numbers = getE164Numbers(context, members); + + memberE164Numbers.add(TextSecurePreferences.getLocalNumber(context)); + groupDatabase.create(groupId, name, new LinkedList<>(memberE164Numbers), null, null); + groupDatabase.updateAvatar(groupId, avatarBytes); + return sendGroupUpdate(context, masterSecret, groupId, memberE164Numbers, name, avatarBytes); + } + + private static Set getE164Numbers(Context context, Collection recipients) + throws InvalidNumberException + { + final Set results = new HashSet<>(); + for (Recipient recipient : recipients) { + results.add(Util.canonicalizeNumber(context, recipient.getNumber())); + } + + return results; + } + + public static GroupActionResult updateGroup(@NonNull Context context, + @NonNull MasterSecret masterSecret, + @NonNull byte[] groupId, + @NonNull Set members, + @Nullable Bitmap avatar, + @Nullable String name) + throws InvalidNumberException + { + final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); + final Set memberE164Numbers = getE164Numbers(context, members); + final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); + + memberE164Numbers.add(TextSecurePreferences.getLocalNumber(context)); + groupDatabase.updateMembers(groupId, new LinkedList<>(memberE164Numbers)); + groupDatabase.updateTitle(groupId, name); + groupDatabase.updateAvatar(groupId, avatarBytes); + + return sendGroupUpdate(context, masterSecret, groupId, memberE164Numbers, name, avatarBytes); + } + + private static GroupActionResult sendGroupUpdate(@NonNull Context context, + @NonNull MasterSecret masterSecret, + @NonNull byte[] groupId, + @NonNull Set e164numbers, + @Nullable String groupName, + @Nullable byte[] avatar) + { + Attachment avatarAttachment = null; + String groupRecipientId = GroupUtil.getEncodedId(groupId); + Recipients groupRecipient = RecipientFactory.getRecipientsFromString(context, groupRecipientId, false); + + GroupContext.Builder groupContextBuilder = GroupContext.newBuilder() + .setId(ByteString.copyFrom(groupId)) + .setType(GroupContext.Type.UPDATE) + .addAllMembers(e164numbers); + if (groupName != null) groupContextBuilder.setName(groupName); + GroupContext groupContext = groupContextBuilder.build(); + + if (avatar != null) { + Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar); + avatarAttachment = new UriAttachment(avatarUri, ContentType.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length); + } + + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis()); + long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false); + + return new GroupActionResult(groupRecipient, threadId); + } + + public static class GroupActionResult { + private Recipients groupRecipient; + private long threadId; + + public GroupActionResult(Recipients groupRecipient, long threadId) { + this.groupRecipient = groupRecipient; + this.threadId = threadId; + } + + public Recipients getGroupRecipient() { + return groupRecipient; + } + + public long getThreadId() { + return threadId; + } + } +} diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java index b4db393135..d2543bdca7 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java @@ -20,11 +20,11 @@ import android.content.Context; import android.support.annotation.NonNull; import android.text.TextUtils; -import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory; import org.thoughtcrime.securesms.database.CanonicalAddressDatabase; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libaxolotl.util.guava.Optional; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; @@ -40,7 +40,7 @@ public class RecipientFactory { return getRecipientsForIds(context, Util.split(recipientIds, " "), asynchronous); } - public static Recipients getRecipientsFor(Context context, List recipients, boolean asynchronous) { + public static @NonNull Recipients getRecipientsFor(Context context, Collection recipients, boolean asynchronous) { long[] ids = new long[recipients.size()]; int i = 0; diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java index 7fa5d2ed96..2371b3bc73 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -11,6 +11,7 @@ import android.graphics.YuvImage; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; @@ -140,12 +141,18 @@ public class BitmapUtil { return new ByteArrayInputStream(thumbnailBytes.toByteArray()); } - public static byte[] toByteArray(Bitmap bitmap) { + public static @Nullable byte[] toByteArray(@Nullable Bitmap bitmap) { + if (bitmap == null) return null; ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); return stream.toByteArray(); } + public static @Nullable Bitmap fromByteArray(@Nullable byte[] bytes) { + if (bytes == null) return null; + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + } + public static byte[] createFromNV21(@NonNull final byte[] data, final int width, final int height, diff --git a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java index 0536472a8b..a58565280a 100644 --- a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java +++ b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java @@ -1,107 +1,165 @@ package org.thoughtcrime.securesms.util; import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.TextView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.Recipient; +import org.whispersystems.libaxolotl.util.guava.Optional; -import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; -public class SelectedRecipientsAdapter extends ArrayAdapter { +public class SelectedRecipientsAdapter extends BaseAdapter { + @NonNull private Context context; + @Nullable private OnRecipientDeletedListener onRecipientDeletedListener; + @NonNull private List recipients; - private ArrayList recipients; - private OnRecipientDeletedListener onRecipientDeletedListener; - - public SelectedRecipientsAdapter(Context context, int textViewResourceId) { - super(context, textViewResourceId); + public SelectedRecipientsAdapter(@NonNull Context context) { + this(context, Collections.emptyList()); } - public SelectedRecipientsAdapter(Context context, int resource, ArrayList recipients) { - super(context, resource, recipients); - this.recipients = recipients; + public SelectedRecipientsAdapter(@NonNull Context context, + @NonNull Collection existingRecipients) + { + this.context = context; + this.recipients = wrapExistingMembers(existingRecipients); + } + + public void add(@NonNull Recipient recipient, boolean isPush) { + if (!find(recipient).isPresent()) { + RecipientWrapper wrapper = new RecipientWrapper(recipient, true, isPush); + this.recipients.add(0, wrapper); + notifyDataSetChanged(); + } + } + + public Optional find(@NonNull Recipient recipient) { + RecipientWrapper found = null; + for (RecipientWrapper wrapper : recipients) { + if (wrapper.getRecipient().equals(recipient)) found = wrapper; + } + return Optional.fromNullable(found); + } + + public void remove(@NonNull Recipient recipient) { + Optional match = find(recipient); + if (match.isPresent()) { + recipients.remove(match.get()); + notifyDataSetChanged(); + } + } + + public Set getRecipients() { + final Set recipientSet = new HashSet<>(recipients.size()); + for (RecipientWrapper wrapper : recipients) { + recipientSet.add(wrapper.getRecipient()); + } + return recipientSet; } @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { + public int getCount() { + return recipients.size(); + } - View v = convertView; + public boolean hasNonPushMembers() { + for (RecipientWrapper wrapper : recipients) { + if (!wrapper.isPush()) return true; + } + return false; + } + @Override + public Object getItem(int position) { + return recipients.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(final int position, View v, final ViewGroup parent) { if (v == null) { - - LayoutInflater vi; - vi = LayoutInflater.from(getContext()); - v = vi.inflate(R.layout.selected_recipient_list_item, null); - + v = LayoutInflater.from(context).inflate(R.layout.selected_recipient_list_item, parent, false); } - final RecipientWrapper rw = getItem(position); - final Recipient p = rw.getRecipient(); - final boolean modifiable = rw.isModifiable(); + final RecipientWrapper rw = (RecipientWrapper)getItem(position); + final Recipient p = rw.getRecipient(); + final boolean modifiable = rw.isModifiable(); - if (p != null) { + TextView name = (TextView) v.findViewById(R.id.name); + TextView phone = (TextView) v.findViewById(R.id.phone); + ImageButton delete = (ImageButton) v.findViewById(R.id.delete); - TextView name = (TextView) v.findViewById(R.id.name); - TextView phone = (TextView) v.findViewById(R.id.phone); - ImageButton delete = (ImageButton) v.findViewById(R.id.delete); - - if (name != null) { - name.setText(p.getName()); - } - if (phone != null) { - phone.setText(p.getNumber()); - } - if (delete != null) { - 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(); - } - }); - } else { - delete.setVisibility(View.INVISIBLE); - delete.setOnClickListener(null); + name.setText(p.getName()); + phone.setText(p.getNumber()); + delete.setVisibility(modifiable ? View.VISIBLE : View.GONE); + delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (onRecipientDeletedListener != null) { + onRecipientDeletedListener.onRecipientDeleted(recipients.get(position).getRecipient()); } } - } + }); return v; } - public void setOnRecipientDeletedListener(OnRecipientDeletedListener listener) { + private static List wrapExistingMembers(Collection recipients) { + final LinkedList wrapperList = new LinkedList<>(); + for (Recipient recipient : recipients) { + wrapperList.add(new RecipientWrapper(recipient, false, true)); + } + return wrapperList; + } + + public void setOnRecipientDeletedListener(@Nullable OnRecipientDeletedListener listener) { onRecipientDeletedListener = listener; } public interface OnRecipientDeletedListener { - public void onRecipientDeleted(Recipient recipient); + void onRecipientDeleted(Recipient recipient); } public static class RecipientWrapper { private final Recipient recipient; - private final boolean modifiable; + private final boolean modifiable; + private final boolean push; - public RecipientWrapper(final Recipient recipient, final boolean modifiable) { - this.recipient = recipient; + public RecipientWrapper(final @NonNull Recipient recipient, + final boolean modifiable, + final boolean push) + { + this.recipient = recipient; this.modifiable = modifiable; + this.push = push; } - public Recipient getRecipient() { + public @NonNull Recipient getRecipient() { return recipient; } public boolean isModifiable() { return modifiable; } + + public boolean isPush() { + return push; + } } } \ No newline at end of file