parent
75483299dc
commit
d05097a6fd
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.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 BaseAdapter {
|
||||||
|
@NonNull private Context context;
|
||||||
|
@Nullable private OnRecipientDeletedListener onRecipientDeletedListener;
|
||||||
|
@NonNull private List<RecipientWrapper> recipients;
|
||||||
|
|
||||||
|
public SelectedRecipientsAdapter(@NonNull Context context) {
|
||||||
|
this(context, Collections.<Recipient>emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
import java.util.ArrayList;
|
public SelectedRecipientsAdapter(@NonNull Context context,
|
||||||
|
@NonNull Collection<Recipient> existingRecipients)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
this.recipients = wrapExistingMembers(existingRecipients);
|
||||||
|
}
|
||||||
|
|
||||||
public class SelectedRecipientsAdapter extends ArrayAdapter<SelectedRecipientsAdapter.RecipientWrapper> {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ArrayList<RecipientWrapper> recipients;
|
public Optional<RecipientWrapper> find(@NonNull Recipient recipient) {
|
||||||
private OnRecipientDeletedListener onRecipientDeletedListener;
|
RecipientWrapper found = null;
|
||||||
|
for (RecipientWrapper wrapper : recipients) {
|
||||||
|
if (wrapper.getRecipient().equals(recipient)) found = wrapper;
|
||||||
|
}
|
||||||
|
return Optional.fromNullable(found);
|
||||||
|
}
|
||||||
|
|
||||||
public SelectedRecipientsAdapter(Context context, int textViewResourceId) {
|
public void remove(@NonNull Recipient recipient) {
|
||||||
super(context, textViewResourceId);
|
Optional<RecipientWrapper> match = find(recipient);
|
||||||
|
if (match.isPresent()) {
|
||||||
|
recipients.remove(match.get());
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SelectedRecipientsAdapter(Context context, int resource, ArrayList<RecipientWrapper> recipients) {
|
public Set<Recipient> getRecipients() {
|
||||||
super(context, resource, recipients);
|
final Set<Recipient> recipientSet = new HashSet<>(recipients.size());
|
||||||
this.recipients = recipients;
|
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;
|
}
|
||||||
|
|
||||||
if (v == null) {
|
|
||||||
|
|
||||||
LayoutInflater vi;
|
|
||||||
vi = LayoutInflater.from(getContext());
|
|
||||||
v = vi.inflate(R.layout.selected_recipient_list_item, null);
|
|
||||||
|
|
||||||
|
public boolean hasNonPushMembers() {
|
||||||
|
for (RecipientWrapper wrapper : recipients) {
|
||||||
|
if (!wrapper.isPush()) return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final RecipientWrapper rw = getItem(position);
|
@Override
|
||||||
final Recipient p = rw.getRecipient();
|
public Object getItem(int position) {
|
||||||
final boolean modifiable = rw.isModifiable();
|
return recipients.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
if (p != null) {
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
TextView name = (TextView) v.findViewById(R.id.name);
|
@Override
|
||||||
TextView phone = (TextView) v.findViewById(R.id.phone);
|
public View getView(final int position, View v, final ViewGroup parent) {
|
||||||
ImageButton delete = (ImageButton) v.findViewById(R.id.delete);
|
if (v == null) {
|
||||||
|
v = LayoutInflater.from(context).inflate(R.layout.selected_recipient_list_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (name != null) {
|
final RecipientWrapper rw = (RecipientWrapper)getItem(position);
|
||||||
name.setText(p.getName());
|
final Recipient p = rw.getRecipient();
|
||||||
}
|
final boolean modifiable = rw.isModifiable();
|
||||||
if (phone != null) {
|
|
||||||
phone.setText(p.getNumber());
|
TextView name = (TextView) v.findViewById(R.id.name);
|
||||||
}
|
TextView phone = (TextView) v.findViewById(R.id.phone);
|
||||||
if (delete != null) {
|
ImageButton delete = (ImageButton) v.findViewById(R.id.delete);
|
||||||
if (modifiable) {
|
|
||||||
delete.setVisibility(View.VISIBLE);
|
name.setText(p.getName());
|
||||||
delete.setOnClickListener(new View.OnClickListener() {
|
phone.setText(p.getNumber());
|
||||||
@Override
|
delete.setVisibility(modifiable ? View.VISIBLE : View.GONE);
|
||||||
public void onClick(View view) {
|
delete.setOnClickListener(new View.OnClickListener() {
|
||||||
if (onRecipientDeletedListener != null) {
|
@Override
|
||||||
onRecipientDeletedListener.onRecipientDeleted(recipients.get(position).getRecipient());
|
public void onClick(View view) {
|
||||||
}
|
if (onRecipientDeletedListener != null) {
|
||||||
recipients.remove(position);
|
onRecipientDeletedListener.onRecipientDeleted(recipients.get(position).getRecipient());
|
||||||
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) {
|
|
||||||
this.recipient = recipient;
|
public RecipientWrapper(final @NonNull 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue