Refactor group logic (no visual changes)

Closes #4480
// FREEBIE
This commit is contained in:
Jake McGinty 2015-11-05 10:41:43 -08:00 committed by Moxie Marlinspike
parent 75483299dc
commit d05097a6fd
6 changed files with 508 additions and 452 deletions

View File

@ -21,16 +21,12 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -44,58 +40,53 @@ import android.widget.Toast;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.SimpleTarget;
import com.google.protobuf.ByteString;
import com.soundcloud.android.crop.Crop; 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.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.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.NotInDirectoryException; import org.thoughtcrime.securesms.database.NotInDirectoryException;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.TextSecureDirectory; import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.database.ThreadDatabase; 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.mms.RoundedCorners;
import org.thoughtcrime.securesms.providers.SingleUseBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; 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.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; 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.api.util.InvalidNumberException;
import org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
/** /**
* Activity to create and update groups * Activity to create and update groups
* *
* @author Jake McGinty * @author Jake McGinty
*/ */
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity { public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
implements OnRecipientDeletedListener,
RecipientsPanelChangedListener
{
private final static String TAG = GroupCreateActivity.class.getSimpleName(); private final static String TAG = GroupCreateActivity.class.getSimpleName();
@ -106,25 +97,16 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private static final int PICK_CONTACT = 1; 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 EditText groupName;
private ListView lv; private ListView lv;
private PushRecipientsPanel recipientsPanel; private ImageView avatar;
private ImageView avatar; private TextView creatingText;
private TextView creatingText; private MasterSecret masterSecret;
private Bitmap avatarBmp;
private Recipient groupRecipient = null; @NonNull private Optional<GroupData> groupToUpdate = Optional.absent();
private long groupThread = -1;
private byte[] groupId = null;
private Set<Recipient> existingContacts = null;
private String existingTitle = null;
private Bitmap existingAvatarBmp = null;
private MasterSecret masterSecret;
private Bitmap avatarBmp;
private Set<Recipient> selectedContacts;
@Override @Override
protected void onPreCreate() { protected void onPreCreate() {
@ -137,10 +119,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
setContentView(R.layout.group_create_activity); setContentView(R.layout.group_create_activity);
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
selectedContacts = new HashSet<>();
initializeResources(); initializeResources();
initializeExistingGroup();
} }
@Override @Override
@ -148,17 +130,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
super.onResume(); super.onResume();
dynamicTheme.onResume(this); dynamicTheme.onResume(this);
dynamicLanguage.onResume(this); dynamicLanguage.onResume(this);
getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title); updateViewState();
if (!TextSecurePreferences.isPushRegistered(this)) {
disableWhisperGroupUi(R.string.GroupCreateActivity_you_dont_support_push);
}
} }
private boolean whisperGroupUiEnabled() { private boolean isSignalGroup() {
return groupName.isEnabled() && avatar.isEnabled(); return TextSecurePreferences.isPushRegistered(this) && !getAdapter().hasNonPushMembers();
} }
private void disableWhisperGroupUi(int reasonResId) { private void disableSignalGroupViews(int reasonResId) {
View pushDisabled = findViewById(R.id.push_disabled); View pushDisabled = findViewById(R.id.push_disabled);
pushDisabled.setVisibility(View.VISIBLE); pushDisabled.setVisibility(View.VISIBLE);
((TextView) findViewById(R.id.push_disabled_reason)).setText(reasonResId); ((TextView) findViewById(R.id.push_disabled_reason)).setText(reasonResId);
@ -166,46 +145,44 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
groupName.setEnabled(false); groupName.setEnabled(false);
} }
private void enableWhisperGroupUi() { private void enableSignalGroupViews() {
findViewById(R.id.push_disabled).setVisibility(View.GONE); findViewById(R.id.push_disabled).setVisibility(View.GONE);
avatar.setEnabled(true); avatar.setEnabled(true);
groupName.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 { } else {
enableSignalGroupViews();
getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title); getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_title);
} }
} }
private static boolean isActiveInDirectory(Context context, Recipient recipient) { private static boolean isActiveInDirectory(Context context, Recipient recipient) {
try { try {
if (!TextSecureDirectory.getInstance(context).isSecureTextSupported(Util.canonicalizeNumber(context, recipient.getNumber()))) { return TextSecureDirectory.getInstance(context)
return false; .isSecureTextSupported(Util.canonicalizeNumber(context, recipient.getNumber()));
} } catch (NotInDirectoryException | InvalidNumberException e) {
} catch (NotInDirectoryException e) {
return false;
} catch (InvalidNumberException e) {
return false; return false;
} }
return true;
} }
private void addSelectedContact(Recipient contact) { private void addSelectedContact(@NonNull Recipient contact) {
final boolean isPushUser = isActiveInDirectory(this, contact); final boolean isPushUser = isActiveInDirectory(this, contact);
if (existingContacts != null && !isPushUser) { if (groupToUpdate.isPresent() && !isPushUser) {
Toast.makeText(getApplicationContext(), Toast.makeText(this, R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group, Toast.LENGTH_LONG).show();
R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group,
Toast.LENGTH_LONG).show();
return; return;
} }
if (!selectedContacts.contains(contact) && (existingContacts == null || !existingContacts.contains(contact))) getAdapter().add(contact, isPushUser);
selectedContacts.add(contact); updateViewState();
if (!isPushUser) {
disableWhisperGroupUi(R.string.GroupCreateActivity_contacts_dont_support_push);
getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title);
}
} }
private void addAllSelectedContacts(Collection<Recipient> contacts) { private void addAllSelectedContacts(Collection<Recipient> 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() { private void initializeResources() {
groupRecipient = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(GROUP_RECIPIENT_EXTRA, -1), true); RecipientsEditor recipientsEditor = ViewUtil.findById(this, R.id.recipients_text);
groupThread = getIntent().getLongExtra(GROUP_THREAD_EXTRA, -1); PushRecipientsPanel recipientsPanel = ViewUtil.findById(this, R.id.recipients);
if (groupRecipient != null) { lv = ViewUtil.findById(this, R.id.selected_contacts_list);
final String encodedGroupId = groupRecipient.getNumber(); avatar = ViewUtil.findById(this, R.id.avatar);
if (encodedGroupId != null) { groupName = ViewUtil.findById(this, R.id.group_name);
try { creatingText = ViewUtil.findById(this, R.id.creating_group_text);
groupId = GroupUtil.getDecodedId(encodedGroupId); SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this);
} catch (IOException ioe) { adapter.setOnRecipientDeletedListener(this);
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<SelectedRecipientsAdapter.RecipientWrapper>());
adapter.setOnRecipientDeletedListener(new SelectedRecipientsAdapter.OnRecipientDeletedListener() {
@Override
public void onRecipientDeleted(Recipient recipient) {
removeSelectedContact(recipient);
}
});
lv.setAdapter(adapter); lv.setAdapter(adapter);
recipientsEditor.setHint(R.string.recipients_panel__add_member);
recipientsPanel.setPanelChangeListener(new PushRecipientsPanel.RecipientsPanelChangedListener() { recipientsPanel.setPanelChangeListener(this);
@Override findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener());
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());
avatar.setOnClickListener(new View.OnClickListener() { avatar.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
Crop.pickImage(GroupCreateActivity.this); 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 @Override
@ -316,65 +245,58 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
finish(); finish();
return true; return true;
case R.id.menu_create_group: case R.id.menu_create_group:
if (groupId == null) handleGroupCreate(); if (groupToUpdate.isPresent()) handleGroupUpdate();
else handleGroupUpdate(); else handleGroupCreate();
return true; return true;
} }
return false; 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() { private void handleGroupCreate() {
if (selectedContacts.size() < 1) { if (getAdapter().getCount() < 1) {
Log.i(TAG, getString(R.string.GroupCreateActivity_contacts_no_members)); Log.i(TAG, getString(R.string.GroupCreateActivity_contacts_no_members));
Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_no_members, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_no_members, Toast.LENGTH_SHORT).show();
return; return;
} }
if (whisperGroupUiEnabled()) { if (isSignalGroup()) {
enableWhisperGroupProgressUi(false); new CreateSignalGroupTask(this, masterSecret, avatarBmp, getGroupName(), getAdapter().getRecipients()).execute();
new CreateWhisperGroupAsyncTask().execute();
} else { } else {
new CreateMmsGroupAsyncTask().execute(); new CreateMmsGroupTask(this, getAdapter().getRecipients()).execute();
} }
} }
private void handleGroupUpdate() { private void handleGroupUpdate() {
if (whisperGroupUiEnabled()) { new UpdateSignalGroupTask(this, masterSecret, groupToUpdate.get().id, avatarBmp,
enableWhisperGroupProgressUi(true); getGroupName(), getAdapter().getRecipients()).execute();
}
new UpdateWhisperGroupAsyncTask().execute();
} }
private void enableWhisperGroupProgressUi(boolean isGroupUpdate) { private void handleOpenConversation(long threadId, Recipients recipients) {
findViewById(R.id.group_details_layout).setVisibility(View.GONE); Intent intent = new Intent(this, ConversationActivity.class);
findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
findViewById(R.id.menu_create_group).setVisibility(View.GONE); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
if (groupName.getText() != null) { intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds());
final int titleResId = isGroupUpdate startActivity(intent);
? R.string.GroupCreateActivity_updating_group finish();
: R.string.GroupCreateActivity_creating_group;
creatingText.setText(getString(titleResId, groupName.getText().toString()));
}
} }
private void disableWhisperGroupProgressUi() { private SelectedRecipientsAdapter getAdapter() {
findViewById(R.id.group_details_layout).setVisibility(View.VISIBLE); return (SelectedRecipientsAdapter)lv.getAdapter();
findViewById(R.id.creating_group_layout).setVisibility(View.GONE);
findViewById(R.id.menu_create_group).setVisibility(View.VISIBLE);
} }
private void syncAdapterWithSelectedContacts() { private @Nullable String getGroupName() {
SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter)lv.getAdapter(); return groupName.getText() != null ? groupName.getText().toString() : null;
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();
} }
@Override @Override
@ -389,15 +311,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
case PICK_CONTACT: case PICK_CONTACT:
List<String> selected = data.getStringArrayListExtra("contacts"); List<String> selected = data.getStringArrayListExtra("contacts");
for (String contact : selected) { for (String contact : selected) {
Recipient recipient = RecipientFactory.getRecipientsFromString(this, contact, false).getPrimaryRecipient(); final Recipient recipient = RecipientFactory.getRecipientsFromString(this, contact, false).getPrimaryRecipient();
if (recipient != null) addSelectedContact(recipient);
if (!selectedContacts.contains(recipient) &&
(existingContacts == null || !existingContacts.contains(recipient)) &&
recipient != null) {
addSelectedContact(recipient);
}
} }
syncAdapterWithSelectedContacts();
break; break;
case Crop.REQUEST_PICK: case Crop.REQUEST_PICK:
@ -407,7 +323,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
Glide.with(this).load(Crop.getOutput(data)).asBitmap().skipMemoryCache(true) Glide.with(this).load(Crop.getOutput(data)).asBitmap().skipMemoryCache(true)
.centerCrop().override(AVATAR_SIZE, AVATAR_SIZE) .centerCrop().override(AVATAR_SIZE, AVATAR_SIZE)
.into(new SimpleTarget<Bitmap>() { .into(new SimpleTarget<Bitmap>() {
@Override public void onResourceReady(Bitmap resource, @Override
public void onResourceReady(Bitmap resource,
GlideAnimation<? super Bitmap> glideAnimation) GlideAnimation<? super Bitmap> glideAnimation)
{ {
avatarBmp = resource; avatarBmp = resource;
@ -423,121 +340,36 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class); Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
if (existingContacts != null) intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, if (groupToUpdate.isPresent()) intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE,
ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY); ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
startActivityForResult(intent, PICK_CONTACT); startActivityForResult(intent, PICK_CONTACT);
} }
} }
private Pair<Long, Recipients> handleCreatePushGroup(String groupName, byte[] avatar, private static class CreateMmsGroupTask extends AsyncTask<Void,Void,Long> {
Set<Recipient> members) private GroupCreateActivity activity;
throws InvalidNumberException, MmsException private Set<Recipient> members;
{
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
byte[] groupId = groupDatabase.allocateGroupId();
Set<String> memberE164Numbers = getE164Numbers(members);
memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this)); public CreateMmsGroupTask(GroupCreateActivity activity, Set<Recipient> members) {
this.activity = activity;
groupDatabase.create(groupId, groupName, new LinkedList<String>(memberE164Numbers), null, null); this.members = members;
groupDatabase.updateAvatar(groupId, avatar);
return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
}
private Pair<Long, Recipients> handleUpdatePushGroup(byte[] groupId, String groupName,
byte[] avatar, Set<Recipient> members)
throws InvalidNumberException, MmsException
{
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
Set<String> memberE164Numbers = getE164Numbers(members);
memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));
for (String number : memberE164Numbers)
Log.w(TAG, "Updating: " + number);
groupDatabase.updateMembers(groupId, new LinkedList<String>(memberE164Numbers));
groupDatabase.updateTitle(groupId, groupName);
groupDatabase.updateAvatar(groupId, avatar);
return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
}
private Pair<Long, Recipients> handlePushOperation(byte[] groupId, String groupName,
@Nullable byte[] avatar,
Set<String> 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);
} }
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<Recipient> members) {
Recipients recipients = RecipientFactory.getRecipientsFor(this, new LinkedList<>(members), false);
return DatabaseFactory.getThreadDatabase(this)
.getThreadIdFor(recipients,
ThreadDatabase.DistributionTypes.CONVERSATION);
}
private static <T> ArrayList<T> setToArrayList(Set<T> set) {
ArrayList<T> arrayList = new ArrayList<T>(set.size());
for (T item : set) {
arrayList.add(item);
}
return arrayList;
}
private Set<String> getE164Numbers(Set<Recipient> recipients)
throws InvalidNumberException
{
Set<String> results = new HashSet<String>();
for (Recipient recipient : recipients) {
results.add(Util.canonicalizeNumber(this, recipient.getNumber()));
}
return results;
}
private class CreateMmsGroupAsyncTask extends AsyncTask<Void,Void,Long> {
@Override @Override
protected Long doInBackground(Void... voids) { protected Long doInBackground(Void... avoid) {
return handleCreateMmsGroup(selectedContacts); Recipients recipients = RecipientFactory.getRecipientsFor(activity, members, false);
return DatabaseFactory.getThreadDatabase(activity)
.getThreadIdFor(recipients, ThreadDatabase.DistributionTypes.CONVERSATION);
} }
@Override @Override
protected void onPostExecute(Long resultThread) { protected void onPostExecute(Long resultThread) {
if (resultThread > -1) { if (resultThread > -1) {
Intent intent = new Intent(GroupCreateActivity.this, ConversationActivity.class); activity.handleOpenConversation(resultThread,
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, resultThread.longValue()); RecipientFactory.getRecipientsFor(activity, members, true));
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
ArrayList<Recipient> selectedContactsList = setToArrayList(selectedContacts);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, RecipientFactory.getRecipientsFor(GroupCreateActivity.this, selectedContactsList, true).getIds());
startActivity(intent);
finish();
} else { } else {
Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show(); Toast.makeText(activity, R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show();
finish(); activity.finish();
} }
} }
@ -547,145 +379,167 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
} }
} }
private class UpdateWhisperGroupAsyncTask extends AsyncTask<Void,Void,Pair<Long,Recipients>> { private abstract static class SignalGroupTask extends AsyncTask<Void,Void,Optional<GroupActionResult>> {
private long RES_BAD_NUMBER = -2; protected GroupCreateActivity activity;
private long RES_MMS_EXCEPTION = -3; protected MasterSecret masterSecret;
@Override protected Bitmap avatar;
protected Pair<Long, Recipients> doInBackground(Void... params) { protected Set<Recipient> members;
byte[] avatarBytes = null; protected String name;
final Bitmap bitmap;
if (avatarBmp == null) bitmap = existingAvatarBmp;
else bitmap = avatarBmp;
if (bitmap != null) { public SignalGroupTask(GroupCreateActivity activity,
ByteArrayOutputStream stream = new ByteArrayOutputStream(); MasterSecret masterSecret,
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); Bitmap avatar,
avatarBytes = stream.toByteArray(); String name,
Set<Recipient> 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<GroupActionResult> 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<Recipient> members) {
super(activity, masterSecret, avatar, name, members);
}
@Override
protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
try { try {
Set<Recipient> unionContacts = new HashSet<Recipient>(selectedContacts); return Optional.of(GroupManager.createGroup(activity, masterSecret, members, avatar, name));
unionContacts.addAll(existingContacts);
return handleUpdatePushGroup(groupId, name, avatarBytes, unionContacts);
} catch (MmsException e) {
Log.w(TAG, e);
return new Pair<Long,Recipients>(RES_MMS_EXCEPTION, null);
} catch (InvalidNumberException e) { } catch (InvalidNumberException e) {
Log.w(TAG, e); return Optional.absent();
return new Pair<Long,Recipients>(RES_BAD_NUMBER, null);
} }
} }
@Override @Override
protected void onPostExecute(Pair<Long, Recipients> groupInfo) { protected void onPostExecute(Optional<GroupActionResult> result) {
final long threadId = groupInfo.first; if (result.isPresent() && result.get().getThreadId() > -1) {
final Recipients recipients = groupInfo.second; if (!activity.isFinishing()) {
if (threadId > -1) { activity.handleOpenConversation(result.get().getThreadId(), result.get().getGroupRecipient());
Intent intent = getIntent(); }
intent.putExtra(GROUP_THREAD_EXTRA, threadId); } else {
intent.putExtra(GROUP_RECIPIENT_EXTRA, recipients.getIds()); super.onPostExecute(result);
setResult(RESULT_OK, intent); Toast.makeText(activity.getApplicationContext(),
finish(); R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show();
} 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();
} }
} }
} }
private class CreateWhisperGroupAsyncTask extends AsyncTask<Void,Void,Pair<Long,Recipients>> { private static class UpdateSignalGroupTask extends SignalGroupTask {
private long RES_BAD_NUMBER = -2; private byte[] groupId;
private long RES_MMS_EXCEPTION = -3;
public UpdateSignalGroupTask(GroupCreateActivity activity,
MasterSecret masterSecret, byte[] groupId, Bitmap avatar, String name,
Set<Recipient> members)
{
super(activity, masterSecret, avatar, name, members);
this.groupId = groupId;
}
@Override @Override
protected Pair<Long,Recipients> doInBackground(Void... voids) { protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
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;
try { try {
return handleCreatePushGroup(name, avatarBytes, selectedContacts); return Optional.of(GroupManager.updateGroup(activity, masterSecret, groupId, members, avatar, name));
} catch (MmsException e) {
Log.w(TAG, e);
return new Pair<Long,Recipients>(RES_MMS_EXCEPTION, null);
} catch (InvalidNumberException e) { } catch (InvalidNumberException e) {
Log.w(TAG, e); return Optional.absent();
return new Pair<Long,Recipients>(RES_BAD_NUMBER, null);
} }
} }
@Override @Override
protected void onPostExecute(Pair<Long,Recipients> groupInfo) { protected void onPostExecute(Optional<GroupActionResult> result) {
super.onPostExecute(groupInfo); if (result.isPresent() && result.get().getThreadId() > -1) {
final long threadId = groupInfo.first; if (!activity.isFinishing()) {
final Recipients recipients = groupInfo.second; Intent intent = activity.getIntent();
if (threadId > -1) { intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId());
Intent intent = new Intent(GroupCreateActivity.this, ConversationActivity.class); intent.putExtra(GROUP_RECIPIENT_EXTRA, result.get().getGroupRecipient().getIds());
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); activity.setResult(RESULT_OK, intent);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); activity.finish();
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds()); }
startActivity(intent); } else {
finish(); super.onPostExecute(result);
} else if (threadId == RES_BAD_NUMBER) { Toast.makeText(activity.getApplicationContext(),
Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show(); 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();
} }
} }
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
} }
private class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask<Void,Void,Void> { private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask<byte[],Void,Optional<GroupData>> {
private GroupCreateActivity activity;
public FillExistingGroupInfoAsyncTask() { public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) {
super(GroupCreateActivity.this, super(activity,
R.string.GroupCreateActivity_loading_group_details, R.string.GroupCreateActivity_loading_group_details,
R.string.please_wait); R.string.please_wait);
this.activity = activity;
} }
@Override @Override
protected Void doInBackground(Void... voids) { protected Optional<GroupData> doInBackground(byte[]... groupIds) {
final GroupDatabase db = DatabaseFactory.getGroupDatabase(GroupCreateActivity.this); final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity);
final Recipients recipients = db.getGroupMembers(groupId, false); final Recipients recipients = db.getGroupMembers(groupIds[0], false);
if (recipients != null) { final GroupRecord group = db.getGroup(groupIds[0]);
final List<Recipient> recipientList = recipients.getRecipientsList(); final Set<Recipient> existingContacts = new HashSet<>(recipients.getRecipientsList().size());
if (recipientList != null) { existingContacts.addAll(recipients.getRecipientsList());
if (existingContacts == null)
existingContacts = new HashSet<>(recipientList.size());
existingContacts.addAll(recipientList);
}
}
GroupDatabase.GroupRecord group = db.getGroup(groupId);
if (group != null) { if (group != null) {
existingTitle = group.getTitle(); return Optional.of(new GroupData(groupIds[0],
final byte[] existingAvatar = group.getAvatar(); existingContacts,
if (existingAvatar != null) { BitmapUtil.fromByteArray(group.getAvatar()),
existingAvatarBmp = BitmapFactory.decodeByteArray(existingAvatar, 0, existingAvatar.length); group.getTitle()));
} } else {
return Optional.absent();
} }
return null;
} }
@Override @Override
protected void onPostExecute(Void aVoid) { protected void onPostExecute(Optional<GroupData> group) {
super.onPostExecute(aVoid); super.onPostExecute(group);
if (existingTitle != null) groupName.setText(existingTitle); if (group.isPresent() && !activity.isFinishing()) {
if (existingAvatarBmp != null) avatar.setImageBitmap(existingAvatarBmp); activity.groupToUpdate = group;
if (existingContacts != null) syncAdapterWithSelectedContacts();
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<Recipient> recipients;
Bitmap avatar;
String name;
public GroupData(byte[] id, Set<Recipient> recipients, Bitmap avatar, String name) {
this.id = id;
this.recipients = recipients;
this.avatar = avatar;
this.name = name;
} }
} }
} }

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.database; package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -8,6 +9,8 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -67,7 +70,8 @@ public class GroupDatabase extends Database {
super(context, databaseHelper); 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 + " = ?", Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(groupId)}, new String[] {GroupUtil.getEncodedId(groupId)},
null, null, null); null, null, null);
@ -92,7 +96,7 @@ public class GroupDatabase extends Database {
return new Reader(cursor); return new Reader(cursor);
} }
public Recipients getGroupMembers(byte[] groupId, boolean includeSelf) { public @NonNull Recipients getGroupMembers(byte[] groupId, boolean includeSelf) {
String localNumber = TextSecurePreferences.getLocalNumber(context); String localNumber = TextSecurePreferences.getLocalNumber(context);
List<String> members = getCurrentMembers(groupId); List<String> members = getCurrentMembers(groupId);
List<Recipient> recipients = new LinkedList<>(); List<Recipient> recipients = new LinkedList<>();
@ -248,7 +252,7 @@ public class GroupDatabase extends Database {
this.cursor = cursor; this.cursor = cursor;
} }
public GroupRecord getNext() { public @Nullable GroupRecord getNext() {
if (cursor == null || !cursor.moveToNext()) { if (cursor == null || !cursor.moveToNext()) {
return null; return null;
} }

View File

@ -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<Recipient> 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<String> 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<String> getE164Numbers(Context context, Collection<Recipient> recipients)
throws InvalidNumberException
{
final Set<String> 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<Recipient> members,
@Nullable Bitmap avatar,
@Nullable String name)
throws InvalidNumberException
{
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final Set<String> 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<String> 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;
}
}
}

View File

@ -20,11 +20,11 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase; import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
@ -40,7 +40,7 @@ public class RecipientFactory {
return getRecipientsForIds(context, Util.split(recipientIds, " "), asynchronous); return getRecipientsForIds(context, Util.split(recipientIds, " "), asynchronous);
} }
public static Recipients getRecipientsFor(Context context, List<Recipient> recipients, boolean asynchronous) { public static @NonNull Recipients getRecipientsFor(Context context, Collection<Recipient> recipients, boolean asynchronous) {
long[] ids = new long[recipients.size()]; long[] ids = new long[recipients.size()];
int i = 0; int i = 0;

View File

@ -11,6 +11,7 @@ import android.graphics.YuvImage;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -140,12 +141,18 @@ public class BitmapUtil {
return new ByteArrayInputStream(thumbnailBytes.toByteArray()); 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(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray(); 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, public static byte[] createFromNV21(@NonNull final byte[] data,
final int width, final int width,
final int height, final int height,

View File

@ -1,107 +1,165 @@
package org.thoughtcrime.securesms.util; package org.thoughtcrime.securesms.util;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.BaseAdapter;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient; 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<SelectedRecipientsAdapter.RecipientWrapper> { public class SelectedRecipientsAdapter extends BaseAdapter {
@NonNull private Context context;
@Nullable private OnRecipientDeletedListener onRecipientDeletedListener;
@NonNull private List<RecipientWrapper> recipients;
private ArrayList<RecipientWrapper> recipients; public SelectedRecipientsAdapter(@NonNull Context context) {
private OnRecipientDeletedListener onRecipientDeletedListener; this(context, Collections.<Recipient>emptyList());
public SelectedRecipientsAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
} }
public SelectedRecipientsAdapter(Context context, int resource, ArrayList<RecipientWrapper> recipients) { public SelectedRecipientsAdapter(@NonNull Context context,
super(context, resource, recipients); @NonNull Collection<Recipient> existingRecipients)
this.recipients = recipients; {
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<RecipientWrapper> 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<RecipientWrapper> match = find(recipient);
if (match.isPresent()) {
recipients.remove(match.get());
notifyDataSetChanged();
}
}
public Set<Recipient> getRecipients() {
final Set<Recipient> recipientSet = new HashSet<>(recipients.size());
for (RecipientWrapper wrapper : recipients) {
recipientSet.add(wrapper.getRecipient());
}
return recipientSet;
} }
@Override @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) { if (v == null) {
v = LayoutInflater.from(context).inflate(R.layout.selected_recipient_list_item, parent, false);
LayoutInflater vi;
vi = LayoutInflater.from(getContext());
v = vi.inflate(R.layout.selected_recipient_list_item, null);
} }
final RecipientWrapper rw = getItem(position); final RecipientWrapper rw = (RecipientWrapper)getItem(position);
final Recipient p = rw.getRecipient(); final Recipient p = rw.getRecipient();
final boolean modifiable = rw.isModifiable(); 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); name.setText(p.getName());
TextView phone = (TextView) v.findViewById(R.id.phone); phone.setText(p.getNumber());
ImageButton delete = (ImageButton) v.findViewById(R.id.delete); delete.setVisibility(modifiable ? View.VISIBLE : View.GONE);
delete.setOnClickListener(new View.OnClickListener() {
if (name != null) { @Override
name.setText(p.getName()); public void onClick(View view) {
} if (onRecipientDeletedListener != null) {
if (phone != null) { onRecipientDeletedListener.onRecipientDeleted(recipients.get(position).getRecipient());
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);
} }
} }
} });
return v; return v;
} }
public void setOnRecipientDeletedListener(OnRecipientDeletedListener listener) { private static List<RecipientWrapper> wrapExistingMembers(Collection<Recipient> recipients) {
final LinkedList<RecipientWrapper> wrapperList = new LinkedList<>();
for (Recipient recipient : recipients) {
wrapperList.add(new RecipientWrapper(recipient, false, true));
}
return wrapperList;
}
public void setOnRecipientDeletedListener(@Nullable OnRecipientDeletedListener listener) {
onRecipientDeletedListener = listener; onRecipientDeletedListener = listener;
} }
public interface OnRecipientDeletedListener { public interface OnRecipientDeletedListener {
public void onRecipientDeleted(Recipient recipient); void onRecipientDeleted(Recipient recipient);
} }
public static class RecipientWrapper { public static class RecipientWrapper {
private final Recipient recipient; private final Recipient recipient;
private final boolean modifiable; private final boolean modifiable;
private final boolean push;
public RecipientWrapper(final Recipient recipient, final boolean modifiable) { public RecipientWrapper(final @NonNull Recipient recipient,
this.recipient = recipient; final boolean modifiable,
final boolean push)
{
this.recipient = recipient;
this.modifiable = modifiable; this.modifiable = modifiable;
this.push = push;
} }
public Recipient getRecipient() { public @NonNull Recipient getRecipient() {
return recipient; return recipient;
} }
public boolean isModifiable() { public boolean isModifiable() {
return modifiable; return modifiable;
} }
public boolean isPush() {
return push;
}
} }
} }