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.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.providers.SingleUseBlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; public class GroupManager { public static @NonNull GroupActionResult createGroup(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Set members, @Nullable Bitmap avatar, @Nullable String name, boolean mms) { final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); final String groupId = GroupUtil.getEncodedId(groupDatabase.allocateGroupId(), mms); final Set
memberAddresses = getMemberAddresses(members); memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null); if (!mms) { groupDatabase.updateAvatar(groupId, avatarBytes); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Address.fromSerialized(groupId), true); return sendGroupUpdate(context, masterSecret, groupId, memberAddresses, name, avatarBytes); } else { Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); return new GroupActionResult(groupRecipient, threadId); } } public static GroupActionResult updateGroup(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull String groupId, @NonNull Set members, @Nullable Bitmap avatar, @Nullable String name) throws InvalidNumberException { final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); final Set
memberAddresses = getMemberAddresses(members); final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses)); groupDatabase.updateTitle(groupId, name); groupDatabase.updateAvatar(groupId, avatarBytes); if (!GroupUtil.isMmsGroup(groupId)) { return sendGroupUpdate(context, masterSecret, groupId, memberAddresses, name, avatarBytes); } else { Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); return new GroupActionResult(groupRecipient, threadId); } } private static GroupActionResult sendGroupUpdate(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull String groupId, @NonNull Set
members, @Nullable String groupName, @Nullable byte[] avatar) { try { Attachment avatarAttachment = null; Address groupAddress = Address.fromSerialized(groupId); Recipient groupRecipient = Recipient.from(context, groupAddress, false); List numbers = new LinkedList<>(); for (Address member : members) { numbers.add(member.serialize()); } GroupContext.Builder groupContextBuilder = GroupContext.newBuilder() .setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupId))) .setType(GroupContext.Type.UPDATE) .addAllMembers(numbers); if (groupName != null) groupContextBuilder.setName(groupName); GroupContext groupContext = groupContextBuilder.build(); if (avatar != null) { Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar); avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false); } OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0); long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false, null); return new GroupActionResult(groupRecipient, threadId); } catch (IOException e) { throw new AssertionError(e); } } private static Set
getMemberAddresses(Collection recipients) { final Set
results = new HashSet<>(); for (Recipient recipient : recipients) { results.add(recipient.getAddress()); } return results; } public static class GroupActionResult { private Recipient groupRecipient; private long threadId; public GroupActionResult(Recipient groupRecipient, long threadId) { this.groupRecipient = groupRecipient; this.threadId = threadId; } public Recipient getGroupRecipient() { return groupRecipient; } public long getThreadId() { return threadId; } } }