package org.thoughtcrime.securesms.util; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.sms.MessageSender; import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; public class GroupUtil { private static final String ENCODED_SIGNAL_GROUP_PREFIX = "__textsecure_group__!"; private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!"; private static final String TAG = GroupUtil.class.getSimpleName(); public static String getEncodedId(byte[] groupId, boolean mms) { return (mms ? ENCODED_MMS_GROUP_PREFIX : ENCODED_SIGNAL_GROUP_PREFIX) + Hex.toStringCondensed(groupId); } public static byte[] getDecodedId(String groupId) throws IOException { if (!isEncodedGroup(groupId)) { throw new IOException("Invalid encoding"); } return Hex.fromStringCondensed(groupId.split("!", 2)[1]); } public static boolean isEncodedGroup(@NonNull String groupId) { return groupId.startsWith(ENCODED_SIGNAL_GROUP_PREFIX) || groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); } public static boolean isMmsGroup(@NonNull String groupId) { return groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); } @WorkerThread public static Optional createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) { String encodedGroupId = groupRecipient.getAddress().toGroupString(); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); if (!groupDatabase.isActive(encodedGroupId)) { Log.w(TAG, "Group has already been left."); return Optional.absent(); } ByteString decodedGroupId; try { decodedGroupId = ByteString.copyFrom(getDecodedId(encodedGroupId)); } catch (IOException e) { Log.w(TAG, "Failed to decode group ID.", e); return Optional.absent(); } GroupContext groupContext = GroupContext.newBuilder() .setId(decodedGroupId) .setType(GroupContext.Type.QUIT) .build(); return Optional.of(new OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, null, Collections.emptyList(), Collections.emptyList())); } public static @NonNull GroupDescription getDescription(@NonNull Context context, @Nullable String encodedGroup) { if (encodedGroup == null) { return new GroupDescription(context, null); } try { GroupContext groupContext = GroupContext.parseFrom(Base64.decode(encodedGroup)); return new GroupDescription(context, groupContext); } catch (IOException e) { Log.w(TAG, e); return new GroupDescription(context, null); } } public static class GroupDescription { @NonNull private final Context context; @Nullable private final GroupContext groupContext; @Nullable private final List members; public GroupDescription(@NonNull Context context, @Nullable GroupContext groupContext) { this.context = context.getApplicationContext(); this.groupContext = groupContext; if (groupContext == null || groupContext.getMembersList().isEmpty()) { this.members = null; } else { this.members = new LinkedList<>(); for (String member : groupContext.getMembersList()) { this.members.add(Recipient.from(context, Address.fromExternal(context, member), true)); } } } public String toString(Recipient sender) { StringBuilder description = new StringBuilder(); description.append(context.getString(R.string.MessageRecord_s_updated_group, sender.toShortString())); if (groupContext == null) { return description.toString(); } String title = groupContext.getName(); if (members != null) { description.append("\n"); description.append(context.getResources().getQuantityString(R.plurals.GroupUtil_joined_the_group, members.size(), toString(members))); } if (title != null && !title.trim().isEmpty()) { if (members != null) description.append(" "); else description.append("\n"); description.append(context.getString(R.string.GroupUtil_group_name_is_now, title)); } return description.toString(); } public void addListener(RecipientModifiedListener listener) { if (this.members != null) { for (Recipient member : this.members) { member.addListener(listener); } } } private String toString(List recipients) { String result = ""; for (int i=0;i