diff --git a/res/values/strings.xml b/res/values/strings.xml
index 786a7454a0..d3f9d849a7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -78,6 +78,8 @@
Invalid recipient!
Calls Not Supported
This device does not appear to support dial actions.
+ Leave group?
+ Are you sure you want to leave this group?
Message details
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index c72d03f645..ce74e0b9f4 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -54,6 +54,7 @@ import android.widget.Toast;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
+import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.components.EmojiDrawer;
import org.thoughtcrime.securesms.components.EmojiToggle;
@@ -65,11 +66,13 @@ import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
+import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter;
import org.thoughtcrime.securesms.mms.MediaTooLargeException;
import org.thoughtcrime.securesms.mms.MmsSendHelper;
+import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
@@ -92,14 +95,19 @@ import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
+import org.whispersystems.textsecure.directory.Directory;
+import org.whispersystems.textsecure.directory.NotInDirectoryException;
+import org.whispersystems.textsecure.push.PushMessageProtos;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
+import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
@@ -108,6 +116,9 @@ import java.util.List;
import ws.com.google.android.mms.MmsException;
+import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
+import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
+
/**
* Activity for displaying a message thread, as well as
* composing/sending a new message into that thread.
@@ -190,6 +201,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
initializeSecurity();
initializeTitleBar();
+ initializeEnabledCheck();
initializeMmsEnabledCheck();
initializeIme();
calculateCharactersRemaining();
@@ -275,7 +287,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
} else {
menu.findItem(R.id.menu_distribution_conversation).setChecked(true);
}
- } else {
+ } else if (isActiveGroup()) {
inflater.inflate(R.menu.conversation_push_group_options, menu);
}
}
@@ -307,17 +319,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
return false;
}
- private void handleLeavePushGroup() {
- Toast.makeText(getApplicationContext(), "not yet implemented", Toast.LENGTH_SHORT).show();
- }
-
- private void handleEditPushGroup() {
- Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class);
- intent.putExtra(GroupCreateActivity.MASTER_SECRET_EXTRA, masterSecret);
- intent.putExtra(GroupCreateActivity.GROUP_RECIPIENT_EXTRA, recipients);
- startActivityForResult(intent, GROUP_EDIT);
- }
-
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (isEncryptedConversation) {
@@ -425,6 +426,57 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
builder.show();
}
+ private void handleLeavePushGroup() {
+ if (getRecipients() == null) {
+ Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.ConversationActivity_leave_group));
+ builder.setIcon(android.R.drawable.ic_dialog_info);
+ builder.setCancelable(true);
+ builder.setMessage(getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group));
+ builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ Context self = ConversationActivity.this;
+ byte[] groupId = GroupUtil.getDecodedId(getRecipients().getPrimaryRecipient().getNumber());
+ DatabaseFactory.getGroupDatabase(self).setActive(groupId, false);
+
+ GroupContext context = GroupContext.newBuilder()
+ .setId(ByteString.copyFrom(groupId))
+ .setType(GroupContext.Type.QUIT)
+ .build();
+
+ OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(self, getRecipients(),
+ context, null);
+ MessageSender.send(self, masterSecret, outgoingMessage, threadId);
+ initializeEnabledCheck();
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ Toast.makeText(ConversationActivity.this, "Error leaving group....", Toast.LENGTH_LONG);
+ } catch (MmsException e) {
+ Log.w(TAG, e);
+ Toast.makeText(ConversationActivity.this, "Error leaving group...", Toast.LENGTH_LONG);
+ }
+ }
+ });
+
+ builder.setNegativeButton(R.string.no, null);
+ builder.show();
+ }
+
+ private void handleEditPushGroup() {
+ Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class);
+ intent.putExtra(GroupCreateActivity.MASTER_SECRET_EXTRA, masterSecret);
+ intent.putExtra(GroupCreateActivity.GROUP_RECIPIENT_EXTRA, recipients);
+ startActivityForResult(intent, GROUP_EDIT);
+ }
+
+
private void handleDistributionBroadcastEnabled(MenuItem item) {
distributionType = ThreadDatabase.DistributionTypes.BROADCAST;
item.setChecked(true);
@@ -502,7 +554,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
private void handleAddAttachment() {
- if (this.isMmsEnabled) {
+ if (this.isMmsEnabled || isPushDestination()) {
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.TextSecure_Light_Dialog));
builder.setIcon(R.drawable.ic_dialog_attach);
builder.setTitle(R.string.ConversationActivity_add_attachment);
@@ -577,6 +629,12 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
}
+ private void initializeEnabledCheck() {
+ boolean enabled = !(isGroupConversation() && !isActiveGroup());
+ composeText.setEnabled(enabled);
+ sendButton.setEnabled(enabled);
+ }
+
private void initializeDraftFromDatabase() {
new AsyncTask>() {
@Override
@@ -882,6 +940,20 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
return getRecipients() != null && getRecipients().isSingleRecipient() && !getRecipients().isGroupRecipient();
}
+ private boolean isActiveGroup() {
+ if (!isGroupConversation()) return false;
+
+ try {
+ byte[] groupId = GroupUtil.getDecodedId(getRecipients().getPrimaryRecipient().getNumber());
+ GroupRecord record = DatabaseFactory.getGroupDatabase(this).getGroup(groupId);
+
+ return record.isActive();
+ } catch (IOException e) {
+ Log.w("ConversationActivity", e);
+ return false;
+ }
+ }
+
private boolean isGroupConversation() {
return getRecipients() != null &&
(!getRecipients().isSingleRecipient() || getRecipients().isGroupRecipient());
@@ -891,6 +963,27 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
return getRecipients().isGroupRecipient();
}
+ private boolean isPushDestination() {
+ try {
+ if (!TextSecurePreferences.isPushRegistered(this))
+ return false;
+
+ if (isPushGroupConversation())
+ return true;
+
+ String number = getRecipients().getPrimaryRecipient().getNumber();
+ String e164number = org.thoughtcrime.securesms.util.Util.canonicalizeNumber(this, number);
+
+ return Directory.getInstance(this).isActiveNumber(e164number);
+ } catch (InvalidNumberException e) {
+ Log.w(TAG, e);
+ return false;
+ } catch (NotInDirectoryException e) {
+ Log.w(TAG, e);
+ return false;
+ }
+ }
+
private Recipients getRecipients() {
try {
if (isExistingConversation()) return this.recipients;
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index 9f1b014bb7..6bd9daacf9 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -642,7 +642,7 @@ public class DatabaseFactory {
}
if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) {
- db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, owner TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, avatar_relay TEXT, timestamp INTEGER);");
+ db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, owner TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, avatar_relay TEXT, timestamp INTEGER, active INTEGER DEFAULT 1);");
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);");
db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;");
db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
index 5dcd78b7e0..f843868b0d 100644
--- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
@@ -16,14 +16,11 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -43,6 +40,7 @@ public class GroupDatabase extends Database {
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
private static final String AVATAR_RELAY = "avatar_relay";
private static final String TIMESTAMP = "timestamp";
+ private static final String ACTIVE = "active";
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME +
@@ -56,7 +54,8 @@ public class GroupDatabase extends Database {
AVATAR_KEY + " BLOB, " +
AVATAR_CONTENT_TYPE + " TEXT, " +
AVATAR_RELAY + " TEXT, " +
- TIMESTAMP + " INTEGER);";
+ TIMESTAMP + " INTEGER, " +
+ ACTIVE + " INTEGER DEFAULT 1);";
public static final String[] CREATE_INDEXS = {
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
@@ -125,6 +124,7 @@ public class GroupDatabase extends Database {
contentValues.put(AVATAR_RELAY, relay);
contentValues.put(TIMESTAMP, System.currentTimeMillis());
+ contentValues.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
}
@@ -173,6 +173,7 @@ public class GroupDatabase extends Database {
ContentValues contents = new ContentValues();
contents.put(MEMBERS, Util.join(concatenatedMembers, ","));
+ contents.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(id)});
@@ -211,6 +212,18 @@ public class GroupDatabase extends Database {
}
}
+ public boolean isActive(byte[] id) {
+ GroupRecord record = getGroup(id);
+ return record != null && record.isActive();
+ }
+
+ public void setActive(byte[] id, boolean active) {
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ACTIVE, active ? 1 : 0);
+ database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {GroupUtil.getEncodedId(id)});
+ }
+
public String getOwner(byte[] id) {
Cursor cursor = null;
@@ -265,7 +278,8 @@ public class GroupDatabase extends Database {
cursor.getLong(cursor.getColumnIndexOrThrow(AVATAR_ID)),
cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR_KEY)),
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_CONTENT_TYPE)),
- cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_RELAY)));
+ cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_RELAY)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(ACTIVE)) == 1);
}
public void close() {
@@ -285,10 +299,11 @@ public class GroupDatabase extends Database {
private final byte[] avatarKey;
private final String avatarContentType;
private final String relay;
+ private final boolean active;
public GroupRecord(String id, String title, String owner, String members, byte[] avatar,
long avatarId, byte[] avatarKey, String avatarContentType,
- String relay)
+ String relay, boolean active)
{
this.id = id;
this.title = title;
@@ -299,6 +314,7 @@ public class GroupDatabase extends Database {
this.avatarKey = avatarKey;
this.avatarContentType = avatarContentType;
this.relay = relay;
+ this.active = active;
}
public byte[] getId() {
@@ -340,5 +356,9 @@ public class GroupDatabase extends Database {
public String getRelay() {
return relay;
}
+
+ public boolean isActive() {
+ return active;
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java
index a4f6552a34..2db297828d 100644
--- a/src/org/thoughtcrime/securesms/database/PushDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java
@@ -33,6 +33,7 @@ public class PushDatabase extends Database {
ContentValues values = new ContentValues();
values.put(TYPE, message.getType());
values.put(SOURCE, message.getSource());
+ values.put(DEVICE_ID, message.getSourceDevice());
values.put(BODY, Base64.encodeBytes(message.getBody()));
values.put(TIMESTAMP, message.getTimestampMillis());
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index a196af93a8..67f5973ea0 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -86,6 +86,8 @@ public abstract class MessageRecord extends DisplayRecord {
public SpannableString getDisplayBody() {
if (isGroupUpdate()) {
return emphasisAdded(GroupUtil.getDescription(getBody().getBody()));
+ } else if (isGroupQuit() && isOutgoing()) {
+ return emphasisAdded("You have left the group.");
} else if (isGroupQuit()) {
return emphasisAdded(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString()));
}
diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java
index a87b0403d5..c67e2cde2e 100644
--- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java
@@ -59,7 +59,7 @@ public class ThreadRecord extends DisplayRecord {
} else if (isGroupUpdate()) {
return emphasisAdded(GroupUtil.getDescription(getBody().getBody()));
} else if (isGroupQuit()) {
- return emphasisAdded(getRecipients().toShortString() + " left the group.");
+ return emphasisAdded("Someone left the group.");
} else if (isKeyExchange()) {
return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message));
} else if (SmsDatabase.Types.isFailedDecryptType(type)) {
diff --git a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
index 93f643b712..2211cbb577 100644
--- a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
+++ b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
@@ -61,6 +61,11 @@ public class GcmIntentService extends GCMBaseIntentService {
if (Util.isEmpty(data))
return;
+ if (!TextSecurePreferences.isPushRegistered(context)) {
+ Log.w("GcmIntentService", "Not push registered!");
+ return;
+ }
+
String sessionKey = TextSecurePreferences.getSignalingKey(context);
IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey);
IncomingPushMessage message = encryptedMessage.getIncomingPushMessage();
diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java
index 57435fd5b8..332b4c07e6 100644
--- a/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java
+++ b/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java
@@ -24,12 +24,14 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
this.group = group;
- PduPart part = new PduPart();
- part.setData(avatar);
- part.setContentType(ContentType.IMAGE_PNG.getBytes());
- part.setContentId((System.currentTimeMillis()+"").getBytes());
- part.setName(("Image" + System.currentTimeMillis()).getBytes());
- body.addPart(part);
+ if (avatar != null) {
+ PduPart part = new PduPart();
+ part.setData(avatar);
+ part.setContentType(ContentType.IMAGE_PNG.getBytes());
+ part.setContentId((System.currentTimeMillis()+"").getBytes());
+ part.setName(("Image" + System.currentTimeMillis()).getBytes());
+ body.addPart(part);
+ }
}
@Override
diff --git a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
index fe926b8838..fa35126451 100644
--- a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
@@ -8,32 +8,19 @@ public class OutgoingTextMessage {
private final Recipients recipients;
private final String message;
- private final int groupAction;
- private final String groupActionArguments;
public OutgoingTextMessage(Recipient recipient, String message) {
this(new Recipients(recipient), message);
}
public OutgoingTextMessage(Recipients recipients, String message) {
- this.recipients = recipients;
- this.message = message;
- this.groupAction = -1;
- this.groupActionArguments = null;
- }
-
- public OutgoingTextMessage(Recipient recipient, int groupAction, String groupActionArguments) {
- this.recipients = new Recipients(recipient);
- this.groupAction = groupAction;
- this.groupActionArguments = groupActionArguments;
- this.message = "";
+ this.recipients = recipients;
+ this.message = message;
}
protected OutgoingTextMessage(OutgoingTextMessage base, String body) {
- this.recipients = base.getRecipients();
- this.groupAction = base.getGroupAction();
- this.groupActionArguments = base.getGroupActionArguments();
- this.message = body;
+ this.recipients = base.getRecipients();
+ this.message = body;
}
public String getMessageBody() {
@@ -75,12 +62,4 @@ public class OutgoingTextMessage {
public OutgoingTextMessage withBody(String body) {
return new OutgoingTextMessage(this, body);
}
-
- public int getGroupAction() {
- return groupAction;
- }
-
- public String getGroupActionArguments() {
- return groupActionArguments;
- }
}
diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java
index 1809193506..cca3513048 100644
--- a/src/org/thoughtcrime/securesms/util/GroupUtil.java
+++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java
@@ -41,9 +41,9 @@ public class GroupUtil {
try {
String description = "";
- GroupContext context = GroupContext.parseFrom(Base64.decode(encodedGroup));
- List members = context.getMembersList();
- String title = context.getName();
+ GroupContext context = GroupContext.parseFrom(Base64.decode(encodedGroup));
+ List members = context.getMembersList();
+ String title = context.getName();
if (!members.isEmpty()) {
description += org.whispersystems.textsecure.util.Util.join(members, ", ") + " joined the group.";