mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 10:32:39 +00:00
Support for multi-device.
1) In addition to the Recipient interface, there is now RecipientDevice. A Recipient can have multiple corresponding RecipientDevices. All addressing is done to a Recipient, but crypto sessions and transport delivery are done to RecipientDevice. 2) The Push transport handles the discovery and session setup of additional Recipient devices. 3) Some internal rejiggering of Groups.
This commit is contained in:
@@ -19,15 +19,10 @@ package org.thoughtcrime.securesms;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.ViewPager;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.ActionBarUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.ActionBar.Tab;
|
||||
import com.actionbarsherlock.app.ActionBar.TabListener;
|
||||
@@ -35,6 +30,14 @@ import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ActionBarUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
|
||||
/**
|
||||
* Activity container for selecting a list of contacts. Provides a tab frame for
|
||||
* contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity
|
||||
@@ -52,8 +55,6 @@ public class ContactSelectionActivity extends PassphraseRequiredSherlockFragment
|
||||
private ContactSelectionGroupsFragment groupsFragment;
|
||||
private ContactSelectionRecentFragment recentFragment;
|
||||
|
||||
private Recipients recipients;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
dynamicTheme.onCreate(this);
|
||||
@@ -97,12 +98,12 @@ public class ContactSelectionActivity extends PassphraseRequiredSherlockFragment
|
||||
}
|
||||
|
||||
private void handleSelectionFinished() {
|
||||
recipients = contactsFragment.getSelectedContacts();
|
||||
recipients.append(recentFragment.getSelectedContacts());
|
||||
recipients.append(groupsFragment.getSelectedContacts(this));
|
||||
List<ContactData> contacts = contactsFragment.getSelectedContacts();
|
||||
contacts.addAll(recentFragment.getSelectedContacts());
|
||||
contacts.addAll(groupsFragment.getSelectedContacts(this));
|
||||
|
||||
Intent resultIntent = getIntent();
|
||||
resultIntent.putExtra("recipients", this.recipients);
|
||||
resultIntent.putParcelableArrayListExtra("contacts", new ArrayList<ContactData>(contacts));
|
||||
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
|
||||
|
||||
@@ -84,23 +84,17 @@ public class ContactSelectionGroupsFragment extends SherlockListFragment
|
||||
this.getListView().setFocusable(true);
|
||||
}
|
||||
|
||||
public Recipients getSelectedContacts(Context context) {
|
||||
List<Recipient> recipientList = new LinkedList<Recipient>();
|
||||
public List<ContactData> getSelectedContacts(Context context) {
|
||||
List<ContactData> contacts = new LinkedList<ContactData>();
|
||||
|
||||
for (GroupData groupData : selectedGroups.values()) {
|
||||
List<ContactData> contactDataList = ContactAccessor.getInstance()
|
||||
.getGroupMembership(context, groupData.id);
|
||||
.getGroupMembership(context, groupData.id);
|
||||
|
||||
Log.w("GroupSelectionListActivity", "Got contacts in group: " + contactDataList.size());
|
||||
|
||||
for (ContactData contactData : contactDataList) {
|
||||
for (NumberData numberData : contactData.numbers) {
|
||||
recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
|
||||
}
|
||||
}
|
||||
contacts.addAll(contactDataList);
|
||||
}
|
||||
|
||||
return new Recipients(recipientList);
|
||||
return contacts;
|
||||
}
|
||||
|
||||
private void addGroup(GroupData groupData) {
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -96,16 +97,11 @@ public class ContactSelectionListFragment extends SherlockListFragment
|
||||
}
|
||||
|
||||
|
||||
public Recipients getSelectedContacts() {
|
||||
List<Recipient> recipientList = new LinkedList<Recipient>();
|
||||
public List<ContactData> getSelectedContacts() {
|
||||
List<ContactData> contacts = new LinkedList<ContactData>();
|
||||
contacts.addAll(selectedContacts.values());
|
||||
|
||||
for (ContactData contactData : selectedContacts.values()) {
|
||||
for (NumberData numberData : contactData.numbers) {
|
||||
recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
return new Recipients(recipientList);
|
||||
return contacts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -84,16 +84,11 @@ public class ContactSelectionRecentFragment extends SherlockListFragment
|
||||
this.getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
public Recipients getSelectedContacts() {
|
||||
List<Recipient> recipientList = new LinkedList<Recipient>();
|
||||
public List<ContactData> getSelectedContacts() {
|
||||
List<ContactData> contacts = new LinkedList<ContactData>();
|
||||
contacts.addAll(selectedContacts.values());
|
||||
|
||||
for (ContactData contactData : selectedContacts.values()) {
|
||||
for (NumberData numberData : contactData.numbers) {
|
||||
recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
return new Recipients(recipientList);
|
||||
return contacts;
|
||||
}
|
||||
|
||||
private void addSingleNumberContact(ContactData contactData) {
|
||||
|
||||
@@ -212,10 +212,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
|
||||
switch (reqCode) {
|
||||
case PICK_CONTACT:
|
||||
Recipients recipients = data.getParcelableExtra("recipients");
|
||||
List<ContactData> contacts = data.getParcelableArrayListExtra("contacts");
|
||||
|
||||
if (recipients != null)
|
||||
recipientsPanel.addRecipients(recipients);
|
||||
if (contacts != null)
|
||||
recipientsPanel.addContacts(contacts);
|
||||
|
||||
break;
|
||||
case PICK_IMAGE:
|
||||
@@ -912,7 +912,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
|
||||
threadId, attachmentManager.getSlideDeck(), body,
|
||||
distributionType, isEncryptedConversation && !forcePlaintext);
|
||||
} else if (recipients.isEmailRecipient() || !recipients.isSingleRecipient()) {
|
||||
} else if (recipients.isEmailRecipient() || !recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
|
||||
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
|
||||
threadId, new SlideDeck(), body, distributionType,
|
||||
isEncryptedConversation && !forcePlaintext);
|
||||
|
||||
@@ -347,6 +347,7 @@ public class ConversationItem extends LinearLayout {
|
||||
private void handleKeyExchangeClicked() {
|
||||
Intent intent = new Intent(context, ReceiveKeyActivity.class);
|
||||
intent.putExtra("recipient", messageRecord.getIndividualRecipient());
|
||||
intent.putExtra("recipient_device_id", messageRecord.getRecipientDeviceId());
|
||||
intent.putExtra("body", messageRecord.getBody().getBody());
|
||||
intent.putExtra("thread_id", messageRecord.getThreadId());
|
||||
intent.putExtra("message_id", messageRecord.getId());
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
@@ -45,6 +46,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -62,6 +64,7 @@ public class ReceiveKeyActivity extends Activity {
|
||||
private Button cancelButton;
|
||||
|
||||
private Recipient recipient;
|
||||
private int recipientDeviceId;
|
||||
private long threadId;
|
||||
private long messageId;
|
||||
|
||||
@@ -126,12 +129,14 @@ public class ReceiveKeyActivity extends Activity {
|
||||
}
|
||||
|
||||
private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle) {
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
|
||||
|
||||
if (message != null) {
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(this, masterSecret,
|
||||
recipient, message);
|
||||
recipientDevice, message);
|
||||
return processor.isTrusted(message);
|
||||
} else if (messageBundle != null) {
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipient);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice);
|
||||
return processor.isTrusted(messageBundle);
|
||||
}
|
||||
|
||||
@@ -162,6 +167,7 @@ public class ReceiveKeyActivity extends Activity {
|
||||
this.confirmButton = (Button) findViewById(R.id.ok_button);
|
||||
this.cancelButton = (Button) findViewById(R.id.cancel_button);
|
||||
this.recipient = getIntent().getParcelableExtra("recipient");
|
||||
this.recipientDeviceId = getIntent().getIntExtra("recipient_device_id", -1);
|
||||
this.threadId = getIntent().getLongExtra("thread_id", -1);
|
||||
this.messageId = getIntent().getLongExtra("message_id", -1);
|
||||
this.masterSecret = getIntent().getParcelableExtra("master_secret");
|
||||
@@ -190,7 +196,8 @@ public class ReceiveKeyActivity extends Activity {
|
||||
protected Void doInBackground(Void... params) {
|
||||
if (keyExchangeMessage != null) {
|
||||
try {
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipient, keyExchangeMessage);
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipientDevice, keyExchangeMessage);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||
.markAsProcessedKeyExchange(messageId);
|
||||
@@ -201,8 +208,9 @@ public class ReceiveKeyActivity extends Activity {
|
||||
}
|
||||
} else if (keyExchangeMessageBundle != null) {
|
||||
try {
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(ReceiveKeyActivity.this,
|
||||
masterSecret, recipient);
|
||||
masterSecret, recipientDevice);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessageBundle);
|
||||
|
||||
CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage();
|
||||
@@ -213,8 +221,8 @@ public class ReceiveKeyActivity extends Activity {
|
||||
.updateBundleMessageBody(masterSecret, messageId, messageBody);
|
||||
|
||||
DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId,
|
||||
threadId, recipient.getNumber(), messageBody,
|
||||
true, false);
|
||||
threadId, recipient.getNumber(), recipientDeviceId,
|
||||
messageBody, true, false);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
Log.w("ReceiveKeyActivity", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
|
||||
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -69,6 +70,14 @@ public class RecipientsPanel extends RelativeLayout {
|
||||
else recipientsText.append(number + ", ");
|
||||
}
|
||||
|
||||
public void addContacts(List<ContactAccessor.ContactData> contacts) {
|
||||
for (ContactAccessor.ContactData contact : contacts) {
|
||||
for (ContactAccessor.NumberData number : contact.numbers) {
|
||||
addRecipient(contact.name, number.number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addRecipients(Recipients recipients) {
|
||||
List<Recipient> recipientList = recipients.getRecipientsList();
|
||||
Iterator<Recipient> iterator = recipientList.iterator();
|
||||
|
||||
@@ -46,7 +46,9 @@ import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
@@ -78,11 +80,12 @@ public class DecryptingQueue {
|
||||
}
|
||||
|
||||
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
|
||||
long messageId, long threadId, String originator,
|
||||
long messageId, long threadId, String originator, int deviceId,
|
||||
String body, boolean isSecureMessage, boolean isKeyExchange)
|
||||
{
|
||||
DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId,
|
||||
originator, body, isSecureMessage, isKeyExchange);
|
||||
originator, deviceId, body,
|
||||
isSecureMessage, isKeyExchange);
|
||||
executor.execute(runnable);
|
||||
}
|
||||
|
||||
@@ -161,11 +164,13 @@ public class DecryptingQueue {
|
||||
long threadId = record.getThreadId();
|
||||
String body = record.getBody().getBody();
|
||||
String originator = record.getIndividualRecipient().getNumber();
|
||||
int originatorDeviceId = record.getRecipientDeviceId();
|
||||
boolean isSecureMessage = record.isSecure();
|
||||
boolean isKeyExchange = record.isKeyExchange();
|
||||
|
||||
scheduleDecryption(context, masterSecret, messageId, threadId,
|
||||
originator, body, isSecureMessage, isKeyExchange);
|
||||
originator, originatorDeviceId, body,
|
||||
isSecureMessage, isKeyExchange);
|
||||
}
|
||||
|
||||
private static class PushDecryptionWorkItem implements Runnable {
|
||||
@@ -186,15 +191,16 @@ public class DecryptingQueue {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
|
||||
|
||||
if (!Session.hasSession(context, masterSecret, recipient)) {
|
||||
if (!SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
|
||||
sendResult(PushReceiver.RESULT_NO_SESSION);
|
||||
return;
|
||||
}
|
||||
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
|
||||
byte[] plaintextBody = sessionCipher.decrypt(message.getBody());
|
||||
|
||||
message = message.withBody(plaintextBody);
|
||||
@@ -251,10 +257,11 @@ public class DecryptingQueue {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
try {
|
||||
String messageFrom = pdu.getFrom().getString();
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
byte[] ciphertextPduBytes = getEncryptedData();
|
||||
String messageFrom = pdu.getFrom().getString();
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
byte[] ciphertextPduBytes = getEncryptedData();
|
||||
|
||||
if (ciphertextPduBytes == null) {
|
||||
Log.w("DecryptingQueue", "No encoded PNG data found on parts.");
|
||||
@@ -272,7 +279,7 @@ public class DecryptingQueue {
|
||||
|
||||
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
|
||||
TextTransport transportDetails = new TextTransport();
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
|
||||
byte[] decodedCiphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
|
||||
|
||||
try {
|
||||
@@ -322,11 +329,13 @@ public class DecryptingQueue {
|
||||
private final MasterSecret masterSecret;
|
||||
private final String body;
|
||||
private final String originator;
|
||||
private final int deviceId;
|
||||
private final boolean isSecureMessage;
|
||||
private final boolean isKeyExchange;
|
||||
|
||||
public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId,
|
||||
String originator, String body, boolean isSecureMessage, boolean isKeyExchange)
|
||||
String originator, int deviceId, String body, boolean isSecureMessage,
|
||||
boolean isKeyExchange)
|
||||
{
|
||||
this.context = context;
|
||||
this.messageId = messageId;
|
||||
@@ -334,6 +343,7 @@ public class DecryptingQueue {
|
||||
this.masterSecret = masterSecret;
|
||||
this.body = body;
|
||||
this.originator = originator;
|
||||
this.deviceId = deviceId;
|
||||
this.isSecureMessage = isSecureMessage;
|
||||
this.isKeyExchange = isKeyExchange;
|
||||
}
|
||||
@@ -343,8 +353,9 @@ public class DecryptingQueue {
|
||||
String plaintextBody;
|
||||
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
|
||||
|
||||
if (!Session.hasSession(context, masterSecret, recipient)) {
|
||||
database.markAsNoSession(messageId);
|
||||
@@ -352,7 +363,7 @@ public class DecryptingQueue {
|
||||
}
|
||||
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
|
||||
byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes());
|
||||
byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext);
|
||||
|
||||
@@ -401,9 +412,10 @@ public class DecryptingQueue {
|
||||
private void handleKeyExchangeProcessing(String plaintxtBody) {
|
||||
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, originator, null, null);
|
||||
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, message);
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
|
||||
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message);
|
||||
|
||||
if (processor.isStale(message)) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
|
||||
@@ -420,6 +432,9 @@ public class DecryptingQueue {
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptingQueue", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("DecryptingQueue", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -70,8 +71,9 @@ public class KeyExchangeInitiator {
|
||||
identityKey.getPublicKey());
|
||||
|
||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
|
||||
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipient);
|
||||
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice);
|
||||
sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
|
||||
sessionRecordV2.save();
|
||||
|
||||
@@ -81,8 +83,9 @@ public class KeyExchangeInitiator {
|
||||
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
|
||||
Recipient recipient)
|
||||
{
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
return
|
||||
new SessionRecordV2(context, masterSecret, recipient)
|
||||
new SessionRecordV2(context, masterSecret, recipientDevice)
|
||||
.hasPendingKeyExchange();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
|
||||
public abstract class KeyExchangeProcessor {
|
||||
|
||||
@@ -34,9 +36,13 @@ public abstract class KeyExchangeProcessor {
|
||||
throws InvalidMessageException;
|
||||
|
||||
public static KeyExchangeProcessor createFor(Context context, MasterSecret masterSecret,
|
||||
Recipient recipient, KeyExchangeMessage message)
|
||||
RecipientDevice recipientDevice,
|
||||
KeyExchangeMessage message)
|
||||
{
|
||||
if (message.isLegacy()) return new KeyExchangeProcessorV1(context, masterSecret, recipient);
|
||||
else return new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
if (message.isLegacy()) {
|
||||
return new KeyExchangeProcessorV1(context, masterSecret, recipientDevice.getRecipient());
|
||||
} else {
|
||||
return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV1;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
@@ -16,6 +17,7 @@ import org.whispersystems.textsecure.crypto.KeyPair;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipient;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV1;
|
||||
@@ -32,17 +34,17 @@ import java.security.SecureRandom;
|
||||
|
||||
public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||
|
||||
private Context context;
|
||||
private Recipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
private LocalKeyRecord localKeyRecord;
|
||||
private RemoteKeyRecord remoteKeyRecord;
|
||||
private SessionRecordV1 sessionRecord;
|
||||
private Context context;
|
||||
private CanonicalRecipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
private LocalKeyRecord localKeyRecord;
|
||||
private RemoteKeyRecord remoteKeyRecord;
|
||||
private SessionRecordV1 sessionRecord;
|
||||
|
||||
public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
this.context = context;
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
|
||||
this.context = context;
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
this.remoteKeyRecord = new RemoteKeyRecord(context, recipient);
|
||||
this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
||||
@@ -55,7 +57,8 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||
}
|
||||
|
||||
public boolean isTrusted(IdentityKey identityKey) {
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
|
||||
recipient.getRecipientId(),
|
||||
identityKey);
|
||||
}
|
||||
|
||||
@@ -80,8 +83,13 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||
|
||||
@Override
|
||||
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId) {
|
||||
KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message;
|
||||
int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId());
|
||||
KeyExchangeMessageV1 message = (KeyExchangeMessageV1) _message;
|
||||
int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId());
|
||||
|
||||
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
|
||||
this.recipient.getRecipientId()+"",
|
||||
true).getPrimaryRecipient();
|
||||
|
||||
message.getRemoteKey().setId(initiateKeyId);
|
||||
|
||||
if (needsResponseFromUs()) {
|
||||
@@ -113,7 +121,7 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||
|
||||
if (message.hasIdentityKey()) {
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
.saveIdentity(masterSecret, recipient.getRecipientId(), message.getIdentityKey());
|
||||
}
|
||||
|
||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
||||
@@ -130,7 +138,7 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||
|
||||
public LocalKeyRecord initializeRecordFor(Context context,
|
||||
MasterSecret masterSecret,
|
||||
Recipient recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
Log.w("KeyExchangeProcessorV1", "Initializing local key pairs...");
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
@@ -24,6 +25,7 @@ import org.whispersystems.textsecure.crypto.ratchet.RatchetingSession;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.Medium;
|
||||
@@ -37,15 +39,16 @@ import org.whispersystems.textsecure.util.Medium;
|
||||
public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
|
||||
private Context context;
|
||||
private Recipient recipient;
|
||||
private RecipientDevice recipientDevice;
|
||||
private MasterSecret masterSecret;
|
||||
private SessionRecordV2 sessionRecord;
|
||||
|
||||
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
|
||||
{
|
||||
this.context = context;
|
||||
this.recipient = recipient;
|
||||
this.recipientDevice = recipientDevice;
|
||||
this.masterSecret = masterSecret;
|
||||
this.sessionRecord = new SessionRecordV2(context, masterSecret, recipient);
|
||||
this.sessionRecord = new SessionRecordV2(context, masterSecret, recipientDevice);
|
||||
}
|
||||
|
||||
public boolean isTrusted(PreKeyWhisperMessage message) {
|
||||
@@ -57,7 +60,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
}
|
||||
|
||||
public boolean isTrusted(IdentityKey identityKey) {
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
|
||||
recipientDevice.getRecipientId(),
|
||||
identityKey);
|
||||
}
|
||||
|
||||
@@ -80,7 +84,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
|
||||
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
|
||||
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId) && Session.hasSession(context, masterSecret, recipient)) {
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId) && SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
|
||||
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
|
||||
return;
|
||||
}
|
||||
@@ -97,7 +101,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
|
||||
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
||||
sessionRecord.save();
|
||||
|
||||
if (preKeyId != Medium.MAX_VALUE) {
|
||||
@@ -105,7 +109,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
}
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, theirIdentityKey);
|
||||
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
|
||||
@@ -129,7 +133,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
|
||||
|
||||
broadcastSecurityUpdateEvent(context, threadId);
|
||||
}
|
||||
@@ -140,6 +144,10 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
{
|
||||
try {
|
||||
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)_message;
|
||||
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
|
||||
String.valueOf(recipientDevice.getRecipientId()),
|
||||
false)
|
||||
.getPrimaryRecipient();
|
||||
|
||||
Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence());
|
||||
|
||||
@@ -197,11 +205,11 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
ourIdentityKey, message.getIdentityKey());
|
||||
|
||||
sessionRecord.setSessionVersion(message.getVersion());
|
||||
Session.clearV1SessionFor(context, recipient);
|
||||
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
|
||||
|
||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
||||
|
||||
|
||||
@@ -644,6 +644,9 @@ 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, timestamp INTEGER);");
|
||||
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;");
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
|
||||
@@ -6,11 +6,18 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,7 +35,7 @@ public class GroupDatabase extends Database {
|
||||
private static final String AVATAR_ID = "avatar_id";
|
||||
private static final String AVATAR_KEY = "avatar_key";
|
||||
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
|
||||
private static final String RELAY = "relay";
|
||||
private static final String AVATAR_RELAY = "avatar_relay";
|
||||
private static final String TIMESTAMP = "timestamp";
|
||||
|
||||
public static final String CREATE_TABLE =
|
||||
@@ -42,6 +49,7 @@ public class GroupDatabase extends Database {
|
||||
AVATAR_ID + " INTEGER, " +
|
||||
AVATAR_KEY + " BLOB, " +
|
||||
AVATAR_CONTENT_TYPE + " TEXT, " +
|
||||
AVATAR_RELAY + " TEXT, " +
|
||||
TIMESTAMP + " INTEGER);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
@@ -52,19 +60,36 @@ public class GroupDatabase extends Database {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Reader getGroup(String groupId) {
|
||||
public Reader getGroup(byte[] groupId) {
|
||||
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
|
||||
new String[] {groupId}, null, null, null);
|
||||
new String[] {GroupUtil.getEncodedId(groupId)},
|
||||
null, null, null);
|
||||
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
||||
public Recipients getGroupMembers(byte[] groupId) {
|
||||
List<String> members = getCurrentMembers(groupId);
|
||||
List<Recipient> recipients = new LinkedList<Recipient>();
|
||||
|
||||
for (String member : members) {
|
||||
try {
|
||||
recipients.addAll(RecipientFactory.getRecipientsFromString(context, member, true)
|
||||
.getRecipientsList());
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("GroupDatabase", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new Recipients(recipients);
|
||||
}
|
||||
|
||||
public void create(byte[] groupId, String owner, String title,
|
||||
List<String> members, AttachmentPointer avatar,
|
||||
String relay)
|
||||
{
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(GROUP_ID, Hex.toString(groupId));
|
||||
contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
|
||||
contentValues.put(OWNER, owner);
|
||||
contentValues.put(TITLE, title);
|
||||
contentValues.put(MEMBERS, Util.join(members, ","));
|
||||
@@ -75,7 +100,7 @@ public class GroupDatabase extends Database {
|
||||
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
|
||||
}
|
||||
|
||||
contentValues.put(RELAY, relay);
|
||||
contentValues.put(AVATAR_RELAY, relay);
|
||||
contentValues.put(TIMESTAMP, System.currentTimeMillis());
|
||||
|
||||
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
|
||||
@@ -93,14 +118,15 @@ public class GroupDatabase extends Database {
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
|
||||
GROUP_ID + " = ? AND " + OWNER + " = ?",
|
||||
new String[] {Hex.toString(groupId), source});
|
||||
new String[] {GroupUtil.getEncodedId(groupId), source});
|
||||
}
|
||||
|
||||
public void updateAvatar(String groupId, Bitmap avatar) {
|
||||
public void updateAvatar(byte[] groupId, Bitmap avatar) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(AVATAR, BitmapUtil.toByteArray(avatar));
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupId});
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
||||
new String[] {GroupUtil.getEncodedId(groupId)});
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +198,7 @@ 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(RELAY)));
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_RELAY)));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
@@ -206,8 +232,12 @@ public class GroupDatabase extends Database {
|
||||
this.relay = relay;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
public byte[] getId() {
|
||||
try {
|
||||
return GroupUtil.getDecodedId(id);
|
||||
} catch (IOException ioe) {
|
||||
throw new AssertionError(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
|
||||
@@ -67,12 +67,10 @@ public class IdentityDatabase extends Database {
|
||||
}
|
||||
|
||||
public boolean isValidIdentity(MasterSecret masterSecret,
|
||||
Recipient recipient,
|
||||
long recipientId,
|
||||
IdentityKey theirIdentity)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
String number = recipient.getNumber();
|
||||
long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
Cursor cursor = null;
|
||||
|
||||
@@ -114,11 +112,9 @@ public class IdentityDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void saveIdentity(MasterSecret masterSecret, Recipient recipient, IdentityKey identityKey)
|
||||
public void saveIdentity(MasterSecret masterSecret, long recipientId, IdentityKey identityKey)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String number = recipient.getNumber();
|
||||
long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
|
||||
String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
|
||||
|
||||
@@ -109,6 +109,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
READ + " INTEGER DEFAULT 0, " + MESSAGE_ID + " TEXT, " + SUBJECT + " TEXT, " +
|
||||
SUBJECT_CHARSET + " INTEGER, " + BODY + " TEXT, " + PART_COUNT + " INTEGER, " +
|
||||
CONTENT_TYPE + " TEXT, " + CONTENT_LOCATION + " TEXT, " + ADDRESS + " TEXT, " +
|
||||
ADDRESS_DEVICE_ID + " INTEGER, " +
|
||||
EXPIRY + " INTEGER, " + MESSAGE_CLASS + " TEXT, " + MESSAGE_TYPE + " INTEGER, " +
|
||||
MMS_VERSION + " INTEGER, " + MESSAGE_SIZE + " INTEGER, " + PRIORITY + " INTEGER, " +
|
||||
READ_REPORT + " INTEGER, " + REPORT_ALLOWED + " INTEGER, " + RESPONSE_STATUS + " INTEGER, " +
|
||||
@@ -131,7 +132,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
CONTENT_LOCATION, EXPIRY, MESSAGE_CLASS, MESSAGE_TYPE, MMS_VERSION,
|
||||
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
|
||||
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS
|
||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID
|
||||
};
|
||||
|
||||
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
|
||||
@@ -788,6 +789,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
|
||||
long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
|
||||
String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION));
|
||||
@@ -807,8 +809,9 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
|
||||
|
||||
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||
dateSent, dateReceived, threadId, contentLocationBytes,
|
||||
messageSize, expiry, status, transactionIdBytes, mailbox);
|
||||
addressDeviceId, dateSent, dateReceived, threadId,
|
||||
contentLocationBytes, messageSize, expiry, status,
|
||||
transactionIdBytes, mailbox);
|
||||
}
|
||||
|
||||
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
|
||||
@@ -818,6 +821,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
|
||||
DisplayRecord.Body body = getBody(cursor);
|
||||
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
@@ -825,29 +829,26 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(masterSecret, id);
|
||||
|
||||
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||
dateSent, dateReceived, threadId, body,
|
||||
addressDeviceId, dateSent, dateReceived, threadId, body,
|
||||
slideDeck, partCount, box);
|
||||
}
|
||||
|
||||
private Recipients getRecipientsFor(String address) {
|
||||
try {
|
||||
if (Util.isEmpty(address) || address.equals("insert-address-token")) {
|
||||
return new Recipients(new Recipient("Unknown", "Unknown", null,
|
||||
ContactPhotoFactory.getDefaultContactPhoto(context)));
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
|
||||
|
||||
if (recipients == null || recipients.isEmpty()) {
|
||||
return new Recipients(new Recipient("Unknown", "Unknown", null,
|
||||
ContactPhotoFactory.getDefaultContactPhoto(context)));
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
|
||||
return recipients;
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("MmsDatabase", e);
|
||||
return new Recipients(new Recipient("Unknown", "Unknown", null,
|
||||
ContactPhotoFactory.getDefaultContactPhoto(context)));
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ public interface MmsSmsColumns {
|
||||
public static final String THREAD_ID = "thread_id";
|
||||
public static final String READ = "read";
|
||||
public static final String BODY = "body";
|
||||
public static final String ADDRESS = "address";
|
||||
public static final String ADDRESS = "address";
|
||||
public static final String ADDRESS_DEVICE_ID = "address_device_id";
|
||||
|
||||
|
||||
public static class Types {
|
||||
|
||||
@@ -42,7 +42,7 @@ public class MmsSmsDatabase extends Database {
|
||||
public Cursor getConversation(long threadId) {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
|
||||
MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.SUBJECT,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
|
||||
@@ -64,7 +64,7 @@ public class MmsSmsDatabase extends Database {
|
||||
public Cursor getConversationSnippet(long threadId) {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
|
||||
MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.SUBJECT,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
|
||||
@@ -81,7 +81,7 @@ public class MmsSmsDatabase extends Database {
|
||||
|
||||
public Cursor getUnread() {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.READ, SmsDatabase.TYPE,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.STATUS,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
@@ -108,7 +108,7 @@ public class MmsSmsDatabase extends Database {
|
||||
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
@@ -117,7 +117,7 @@ public class MmsSmsDatabase extends Database {
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
@@ -139,6 +139,7 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.BODY);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
||||
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
||||
@@ -154,6 +155,7 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(MmsSmsColumns.ID);
|
||||
smsColumnsPresent.add(MmsSmsColumns.BODY);
|
||||
smsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
||||
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||
smsColumnsPresent.add(MmsSmsColumns.READ);
|
||||
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
||||
smsColumnsPresent.add(SmsDatabase.TYPE);
|
||||
|
||||
@@ -18,11 +18,12 @@ public class PushDatabase extends Database {
|
||||
public static final String ID = "_id";
|
||||
public static final String TYPE = "type";
|
||||
public static final String SOURCE = "source";
|
||||
public static final String DEVICE_ID = "device_id";
|
||||
public static final String BODY = "body";
|
||||
public static final String TIMESTAMP = "timestamp";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
TYPE + " INTEGER, " + SOURCE + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);";
|
||||
TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);";
|
||||
|
||||
public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
@@ -64,10 +65,11 @@ public class PushDatabase extends Database {
|
||||
|
||||
int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
|
||||
String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE));
|
||||
int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID));
|
||||
byte[] body = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY)));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
||||
|
||||
return new IncomingPushMessage(type, source, body, timestamp);
|
||||
return new IncomingPushMessage(type, source, deviceId, body, timestamp);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
public static final String SERVICE_CENTER = "service_center";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " integer PRIMARY KEY, " +
|
||||
THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + PERSON + " INTEGER, " + DATE_RECEIVED + " INTEGER, " +
|
||||
DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
|
||||
THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " +
|
||||
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
|
||||
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
|
||||
SUBJECT + " TEXT, " + BODY + " TEXT, " + SERVICE_CENTER + " TEXT);";
|
||||
|
||||
@@ -76,7 +76,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
};
|
||||
|
||||
private static final String[] MESSAGE_PROJECTION = new String[] {
|
||||
ID, THREAD_ID, ADDRESS, PERSON,
|
||||
ID, THREAD_ID, ADDRESS, ADDRESS_DEVICE_ID, PERSON,
|
||||
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
|
||||
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
||||
PROTOCOL, READ, STATUS, TYPE,
|
||||
@@ -257,19 +257,39 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
}
|
||||
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
Recipients recipients = new Recipients(recipient);
|
||||
String groupId = message.getGroupId();
|
||||
Recipients recipients;
|
||||
|
||||
try {
|
||||
recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), true);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("SmsDatabase", e);
|
||||
recipients = new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
|
||||
Recipients groupRecipients;
|
||||
|
||||
try {
|
||||
if (message.getGroupId() == null) {
|
||||
groupRecipients = null;
|
||||
} else {
|
||||
groupRecipients = RecipientFactory.getRecipientsFromString(context, message.getGroupId(), true);
|
||||
}
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("SmsDatabase", e);
|
||||
groupRecipients = null;
|
||||
}
|
||||
|
||||
boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
|
||||
message.isSecureMessage() || message.isKeyExchange();
|
||||
|
||||
long threadId;
|
||||
|
||||
if (groupId == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdForGroup(groupId);
|
||||
if (groupRecipients == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
|
||||
|
||||
ContentValues values = new ContentValues(6);
|
||||
values.put(ADDRESS, message.getSender());
|
||||
values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
|
||||
values.put(DATE_RECEIVED, System.currentTimeMillis());
|
||||
values.put(DATE_SENT, message.getSentTimestampMillis());
|
||||
values.put(PROTOCOL, message.getProtocol());
|
||||
@@ -468,6 +488,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
public SmsMessageRecord getCurrent() {
|
||||
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||||
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID));
|
||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
||||
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED));
|
||||
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
|
||||
@@ -478,6 +499,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
|
||||
return new SmsMessageRecord(context, messageId, body, recipients,
|
||||
recipients.getPrimaryRecipient(),
|
||||
addressDeviceId,
|
||||
dateSent, dateReceived, type,
|
||||
threadId, status);
|
||||
}
|
||||
@@ -487,15 +509,13 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
|
||||
|
||||
if (recipients == null || recipients.isEmpty()) {
|
||||
return new Recipients(new Recipient("Unknown", "Unknown", null,
|
||||
ContactPhotoFactory.getDefaultContactPhoto(context)));
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
|
||||
return recipients;
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("EncryptingSmsDatabase", e);
|
||||
return new Recipients(new Recipient("Unknown", "Unknown", null,
|
||||
ContactPhotoFactory.getDefaultContactPhoto(context)));
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,13 +70,11 @@ public class ThreadDatabase extends Database {
|
||||
}
|
||||
|
||||
private long[] getRecipientIds(Recipients recipients) {
|
||||
Set<Long> recipientSet = new HashSet<Long>();
|
||||
Set<Long> recipientSet = new HashSet<Long>();
|
||||
List<Recipient> recipientList = recipients.getRecipientsList();
|
||||
|
||||
for (Recipient recipient : recipientList) {
|
||||
// String number = NumberUtil.filterNumber(recipient.getNumber());
|
||||
String number = recipient.getNumber();
|
||||
recipientSet.add(Long.valueOf(DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number)));
|
||||
recipientSet.add(recipient.getRecipientId());
|
||||
}
|
||||
|
||||
long[] recipientArray = new long[recipientSet.size()];
|
||||
|
||||
@@ -41,12 +41,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||
private final ListenableFutureTask<SlideDeck> slideDeck;
|
||||
|
||||
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||
Recipient individualRecipient, long dateSent, long dateReceived,
|
||||
long threadId, Body body, ListenableFutureTask<SlideDeck> slideDeck,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived, long threadId, Body body,
|
||||
ListenableFutureTask<SlideDeck> slideDeck,
|
||||
int partCount, long mailbox)
|
||||
{
|
||||
super(context, id, body, recipients, individualRecipient, dateSent, dateReceived,
|
||||
threadId, DELIVERY_STATUS_NONE, mailbox);
|
||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.partCount = partCount;
|
||||
|
||||
@@ -45,18 +45,20 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
public static final int DELIVERY_STATUS_FAILED = 3;
|
||||
|
||||
private final Recipient individualRecipient;
|
||||
private final long id;
|
||||
private final int deliveryStatus;
|
||||
private final int recipientDeviceId;
|
||||
private final long id;
|
||||
private final int deliveryStatus;
|
||||
|
||||
public MessageRecord(Context context, long id, Body body, Recipients recipients,
|
||||
Recipient individualRecipient,
|
||||
long dateSent, long dateReceived,
|
||||
long threadId, int deliveryStatus,
|
||||
long type)
|
||||
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived,
|
||||
long threadId, int deliveryStatus,
|
||||
long type)
|
||||
{
|
||||
super(context, body, recipients, dateSent, dateReceived, threadId, type);
|
||||
this.id = id;
|
||||
this.individualRecipient = individualRecipient;
|
||||
this.recipientDeviceId = recipientDeviceId;
|
||||
this.deliveryStatus = deliveryStatus;
|
||||
}
|
||||
|
||||
@@ -121,6 +123,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return individualRecipient;
|
||||
}
|
||||
|
||||
public int getRecipientDeviceId() {
|
||||
return recipientDeviceId;
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ public class NotificationMmsMessageRecord extends MessageRecord {
|
||||
private final byte[] transactionId;
|
||||
|
||||
public NotificationMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||
Recipient individualRecipient,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived, long threadId,
|
||||
byte[] contentLocation, long messageSize, long expiry,
|
||||
int status, byte[] transactionId, long mailbox)
|
||||
{
|
||||
super(context, id, new Body("", true), recipients, individualRecipient, dateSent, dateReceived,
|
||||
threadId, DELIVERY_STATUS_NONE, mailbox);
|
||||
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
|
||||
@@ -38,12 +38,13 @@ public class SmsMessageRecord extends MessageRecord {
|
||||
public SmsMessageRecord(Context context, long id,
|
||||
Body body, Recipients recipients,
|
||||
Recipient individualRecipient,
|
||||
int recipientDeviceId,
|
||||
long dateSent, long dateReceived,
|
||||
long type, long threadId,
|
||||
int status)
|
||||
{
|
||||
super(context, id, body, recipients, individualRecipient, dateSent, dateReceived,
|
||||
threadId, getGenericDeliveryStatus(status), type);
|
||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type);
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
@@ -27,12 +29,16 @@ public class IncomingMediaMessage {
|
||||
|
||||
public IncomingMediaMessage(MasterSecret masterSecret, String localNumber,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent,
|
||||
String groupId)
|
||||
PushMessageContent messageContent)
|
||||
{
|
||||
this.headers = new PduHeaders();
|
||||
this.body = new PduBody();
|
||||
this.groupId = groupId;
|
||||
|
||||
if (messageContent.hasGroup()) {
|
||||
this.groupId = GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray());
|
||||
} else {
|
||||
this.groupId = null;
|
||||
}
|
||||
|
||||
this.headers.setEncodedStringValue(new EncodedStringValue(message.getSource()), PduHeaders.FROM);
|
||||
this.headers.appendEncodedStringValue(new EncodedStringValue(localNumber), PduHeaders.TO);
|
||||
|
||||
@@ -296,7 +296,7 @@ public class MessageNotifier {
|
||||
recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("MessageNotifier", e);
|
||||
recipient = new Recipient("Unknown", "Unknown", null, ContactPhotoFactory.getDefaultContactPhoto(context));
|
||||
recipient = Recipient.getUnknownRecipient(context);
|
||||
}
|
||||
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false);
|
||||
|
||||
@@ -23,15 +23,16 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
|
||||
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipient;
|
||||
import org.whispersystems.textsecure.util.FutureTaskListener;
|
||||
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
public class Recipient implements Parcelable, CanonicalRecipient {
|
||||
|
||||
public static final Parcelable.Creator<Recipient> CREATOR = new Parcelable.Creator<Recipient>() {
|
||||
public Recipient createFromParcel(Parcel in) {
|
||||
@@ -43,18 +44,21 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
}
|
||||
};
|
||||
|
||||
private final String number;
|
||||
private final HashSet<RecipientModifiedListener> listeners = new HashSet<RecipientModifiedListener>();
|
||||
|
||||
private final String number;
|
||||
private final long recipientId;
|
||||
|
||||
private String name;
|
||||
private Bitmap contactPhoto;
|
||||
private Uri contactUri;
|
||||
|
||||
public Recipient(String number, Bitmap contactPhoto,
|
||||
ListenableFutureTask<RecipientDetails> future)
|
||||
Recipient(String number, Bitmap contactPhoto, long recipientId,
|
||||
ListenableFutureTask<RecipientDetails> future)
|
||||
{
|
||||
this.number = number;
|
||||
this.contactPhoto = contactPhoto;
|
||||
this.recipientId = recipientId;
|
||||
|
||||
future.setListener(new FutureTaskListener<RecipientDetails>() {
|
||||
@Override
|
||||
@@ -82,8 +86,9 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
});
|
||||
}
|
||||
|
||||
public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) {
|
||||
Recipient(String name, String number, long recipientId, Uri contactUri, Bitmap contactPhoto) {
|
||||
this.number = number;
|
||||
this.recipientId = recipientId;
|
||||
this.contactUri = contactUri;
|
||||
this.name = name;
|
||||
this.contactPhoto = contactPhoto;
|
||||
@@ -92,6 +97,7 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
public Recipient(Parcel in) {
|
||||
this.number = in.readString();
|
||||
this.name = in.readString();
|
||||
this.recipientId = in.readLong();
|
||||
this.contactUri = (Uri)in.readParcelable(null);
|
||||
this.contactPhoto = (Bitmap)in.readParcelable(null);
|
||||
}
|
||||
@@ -112,6 +118,14 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public boolean isGroupRecipient() {
|
||||
return GroupUtil.isEncodedGroup(number);
|
||||
}
|
||||
|
||||
// public void updateAsynchronousContent(RecipientDetails result) {
|
||||
// if (result != null) {
|
||||
// Recipient.this.name.set(result.name);
|
||||
@@ -136,6 +150,7 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
public synchronized void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(number);
|
||||
dest.writeString(name);
|
||||
dest.writeLong(recipientId);
|
||||
dest.writeParcelable(contactUri, 0);
|
||||
dest.writeParcelable(contactPhoto, 0);
|
||||
}
|
||||
@@ -148,11 +163,12 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
|
||||
return contactPhoto;
|
||||
}
|
||||
|
||||
public long getCanonicalAddress(Context context) {
|
||||
return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(getNumber());
|
||||
public static Recipient getUnknownRecipient(Context context) {
|
||||
return new Recipient("Unknown", "Unknown", -1, null, ContactPhotoFactory.getDefaultContactPhoto(context));
|
||||
}
|
||||
|
||||
public static interface RecipientModifiedListener {
|
||||
public void onModified(Recipient recipient);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
@@ -51,7 +52,8 @@ public class RecipientFactory {
|
||||
}
|
||||
|
||||
private static Recipient getRecipientForNumber(Context context, String number, boolean asynchronous) {
|
||||
return provider.getRecipient(context, number, asynchronous);
|
||||
long recipientId = CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(number);
|
||||
return provider.getRecipient(context, recipientId, asynchronous);
|
||||
}
|
||||
|
||||
public static Recipients getRecipientsFromString(Context context, String rawText, boolean asynchronous)
|
||||
@@ -81,17 +83,16 @@ public class RecipientFactory {
|
||||
return getRecipientsFromString(context, message.getSource(), asynchronous);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("RecipientFactory", e);
|
||||
return new Recipients(new Recipient("Unknown", "Unknown", null,
|
||||
ContactPhotoFactory.getDefaultContactPhoto(context)));
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
}
|
||||
}
|
||||
|
||||
private static Recipient getRecipientFromProviderId(Context context, String recipientId, boolean asynchronous) {
|
||||
if (recipientId.startsWith("g_")) {
|
||||
return provider.getGroupRecipient(context, recipientId, asynchronous);
|
||||
} else {
|
||||
String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId);
|
||||
return getRecipientForNumber(context, number, asynchronous);
|
||||
try {
|
||||
return provider.getRecipient(context, Long.parseLong(recipientId), asynchronous);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w("RecipientFactory", e);
|
||||
return Recipient.getUnknownRecipient(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ public class RecipientFactory {
|
||||
if (hasBracketedNumber(recipient))
|
||||
return getRecipientForNumber(context, parseBracketedNumber(recipient), asynchronous);
|
||||
|
||||
if (NumberUtil.isValidSmsOrEmail(recipient))
|
||||
if (NumberUtil.isValidSmsOrEmailOrGroup(recipient))
|
||||
return getRecipientForNumber(context, recipient, asynchronous);
|
||||
|
||||
throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted.");
|
||||
|
||||
@@ -27,12 +27,15 @@ import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
@@ -41,8 +44,8 @@ import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class RecipientProvider {
|
||||
|
||||
private static final Map<String,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<String,Recipient>(1000));
|
||||
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
|
||||
private static final Map<Long,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<Long,Recipient>(1000));
|
||||
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
|
||||
|
||||
private static final String[] CALLER_ID_PROJECTION = new String[] {
|
||||
PhoneLookup.DISPLAY_NAME,
|
||||
@@ -50,58 +53,45 @@ public class RecipientProvider {
|
||||
PhoneLookup._ID,
|
||||
};
|
||||
|
||||
public Recipient getRecipient(Context context, String number, boolean asynchronous) {
|
||||
Recipient cachedRecipient = recipientCache.get(number);
|
||||
public Recipient getRecipient(Context context, long recipientId, boolean asynchronous) {
|
||||
Recipient cachedRecipient = recipientCache.get(recipientId);
|
||||
|
||||
if (cachedRecipient != null) return cachedRecipient;
|
||||
else if (asynchronous) return getAsynchronousRecipient(context, number);
|
||||
else return getSynchronousRecipient(context, number);
|
||||
else if (asynchronous) return getAsynchronousRecipient(context, recipientId);
|
||||
else return getSynchronousRecipient(context, recipientId);
|
||||
}
|
||||
|
||||
public Recipient getGroupRecipient(Context context, String groupId, boolean asynchronous) {
|
||||
Recipient cachedRecipient = recipientCache.get(groupId);
|
||||
|
||||
if (cachedRecipient != null) return cachedRecipient;
|
||||
else if (asynchronous) return getAsynchronousGroupRecipient(context, groupId);
|
||||
else return getSynchronousGroupRecipient(context, groupId);
|
||||
}
|
||||
|
||||
private Recipient getSynchronousRecipient(Context context, String number) {
|
||||
private Recipient getSynchronousRecipient(Context context, long recipientId) {
|
||||
Log.w("RecipientProvider", "Cache miss [SYNC]!");
|
||||
RecipientDetails details = getRecipientDetails(context, number);
|
||||
|
||||
Recipient recipient;
|
||||
RecipientDetails details;
|
||||
|
||||
String number = CanonicalAddressDatabase.getInstance(context).getAddressFromId(String.valueOf(recipientId));
|
||||
|
||||
if (GroupUtil.isEncodedGroup(number)) details = getGroupRecipientDetails(context, number);
|
||||
else details = getRecipientDetails(context, number);
|
||||
|
||||
if (details != null) {
|
||||
recipient = new Recipient(details.name, number, details.contactUri, details.avatar);
|
||||
recipient = new Recipient(details.name, number, recipientId, details.contactUri, details.avatar);
|
||||
} else {
|
||||
recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context));
|
||||
recipient = new Recipient(null, number, recipientId, null, ContactPhotoFactory.getDefaultContactPhoto(context));
|
||||
}
|
||||
|
||||
recipientCache.put(number, recipient);
|
||||
recipientCache.put(recipientId, recipient);
|
||||
return recipient;
|
||||
}
|
||||
|
||||
private Recipient getSynchronousGroupRecipient(Context context, String groupId) {
|
||||
RecipientDetails details = getGroupRecipientDetails(context, groupId);
|
||||
Recipient recipient;
|
||||
|
||||
if (details != null) {
|
||||
recipient = new Recipient(details.name, groupId, details.contactUri, details.avatar);
|
||||
} else {
|
||||
recipient = new Recipient(null, groupId, null, ContactPhotoFactory.getDefaultContactPhoto(context));
|
||||
}
|
||||
|
||||
recipientCache.put(groupId, recipient);
|
||||
return recipient;
|
||||
}
|
||||
|
||||
private Recipient getAsynchronousRecipient(final Context context, final String number) {
|
||||
private Recipient getAsynchronousRecipient(final Context context, final long recipientId) {
|
||||
Log.w("RecipientProvider", "Cache miss [ASYNC]!");
|
||||
|
||||
final String number = CanonicalAddressDatabase.getInstance(context).getAddressFromId(String.valueOf(recipientId));
|
||||
|
||||
Callable<RecipientDetails> task = new Callable<RecipientDetails>() {
|
||||
@Override
|
||||
public RecipientDetails call() throws Exception {
|
||||
return getRecipientDetails(context, number);
|
||||
if (GroupUtil.isEncodedGroup(number)) return getGroupRecipientDetails(context, number);
|
||||
else return getRecipientDetails(context, number);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,26 +99,8 @@ public class RecipientProvider {
|
||||
|
||||
asyncRecipientResolver.submit(future);
|
||||
|
||||
Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), future);
|
||||
recipientCache.put(number, recipient);
|
||||
|
||||
return recipient;
|
||||
}
|
||||
|
||||
private Recipient getAsynchronousGroupRecipient(final Context context, final String groupId) {
|
||||
Callable<RecipientDetails> task = new Callable<RecipientDetails>() {
|
||||
@Override
|
||||
public RecipientDetails call() throws Exception {
|
||||
return getGroupRecipientDetails(context, groupId);
|
||||
}
|
||||
};
|
||||
|
||||
ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<RecipientDetails>(task, null);
|
||||
|
||||
asyncRecipientResolver.submit(future);
|
||||
|
||||
Recipient recipient = new Recipient(groupId, ContactPhotoFactory.getDefaultContactPhoto(context), future);
|
||||
recipientCache.put(groupId, recipient);
|
||||
Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), recipientId, future);
|
||||
recipientCache.put(recipientId, recipient);
|
||||
|
||||
return recipient;
|
||||
}
|
||||
@@ -159,24 +131,31 @@ public class RecipientProvider {
|
||||
}
|
||||
|
||||
private RecipientDetails getGroupRecipientDetails(Context context, String groupId) {
|
||||
GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(context).getGroup(groupId.substring(2));
|
||||
GroupDatabase.GroupRecord record;
|
||||
|
||||
try {
|
||||
if ((record = reader.getNext()) != null) {
|
||||
byte[] avatarBytes = record.getAvatar();
|
||||
Bitmap avatar;
|
||||
GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(context)
|
||||
.getGroup(GroupUtil.getDecodedId(groupId));
|
||||
|
||||
if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultContactPhoto(context);
|
||||
else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length);
|
||||
GroupDatabase.GroupRecord record;
|
||||
|
||||
return new RecipientDetails(record.getTitle(), null, avatar);
|
||||
try {
|
||||
if ((record = reader.getNext()) != null) {
|
||||
byte[] avatarBytes = record.getAvatar();
|
||||
Bitmap avatar;
|
||||
|
||||
if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultContactPhoto(context);
|
||||
else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length);
|
||||
|
||||
return new RecipientDetails(record.getTitle(), null, avatar);
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
Log.w("RecipientProvider", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getContactPhoto(Context context, Uri uri) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Parcelable;
|
||||
import android.util.Patterns;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -89,6 +90,10 @@ public class Recipients implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isGroupRecipient() {
|
||||
return isSingleRecipient() && GroupUtil.isEncodedGroup(recipients.get(0).getNumber());
|
||||
}
|
||||
|
||||
// public Recipients getSecureSessionRecipients(Context context) {
|
||||
// List<Recipient> secureRecipients = new LinkedList<Recipient>();
|
||||
//
|
||||
|
||||
@@ -33,7 +33,7 @@ public class AvatarDownloader {
|
||||
if (!SendReceiveService.DOWNLOAD_AVATAR_ACTION.equals(intent.getAction()))
|
||||
return;
|
||||
|
||||
String groupId = intent.getStringExtra("group_id");
|
||||
byte[] groupId = intent.getByteArrayExtra("group_id");
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
GroupDatabase.Reader reader = database.getGroup(groupId);
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
@@ -32,6 +34,7 @@ import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
@@ -106,9 +109,10 @@ public class PushReceiver {
|
||||
}
|
||||
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSource(), null, null);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
|
||||
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
|
||||
|
||||
if (processor.isTrusted(preKeyExchange)) {
|
||||
processor.processKeyExchangeMessage(preKeyExchange);
|
||||
@@ -135,6 +139,9 @@ public class PushReceiver {
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,10 +158,10 @@ public class PushReceiver {
|
||||
handleReceivedGroupMessage(masterSecret, message, messageContent, secure);
|
||||
} else if (messageContent.getAttachmentsCount() > 0) {
|
||||
Log.w("PushReceiver", "Received push media message...");
|
||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure, null);
|
||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
|
||||
} else {
|
||||
Log.w("PushReceiver", "Received push text message...");
|
||||
handleReceivedTextMessage(masterSecret, message, messageContent, secure, null);
|
||||
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
|
||||
}
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
@@ -207,30 +214,28 @@ public class PushReceiver {
|
||||
if (group.hasAvatar()) {
|
||||
Intent intent = new Intent(context, SendReceiveService.class);
|
||||
intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION);
|
||||
intent.putExtra("group_id", group.getId().toByteArray());
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
String groupId = "g_" + Hex.toString(group.getId().toByteArray());
|
||||
|
||||
if (messageContent.getAttachmentsCount() > 0) {
|
||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure, groupId);
|
||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
|
||||
} else if (messageContent.hasBody()) {
|
||||
handleReceivedTextMessage(masterSecret, message, messageContent, secure, groupId);
|
||||
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReceivedMediaMessage(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent,
|
||||
boolean secure, String groupId)
|
||||
boolean secure)
|
||||
{
|
||||
|
||||
try {
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber,
|
||||
message, messageContent,
|
||||
groupId);
|
||||
message, messageContent);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId;
|
||||
|
||||
@@ -255,9 +260,10 @@ public class PushReceiver {
|
||||
private void handleReceivedTextMessage(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent,
|
||||
boolean secure, String groupId)
|
||||
boolean secure)
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String groupId = messageContent.hasGroup() ? GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray()) : null;
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(message, "", groupId);
|
||||
|
||||
if (secure) {
|
||||
|
||||
@@ -32,6 +32,8 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
@@ -46,6 +48,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -80,8 +83,9 @@ public class SmsReceiver {
|
||||
if (masterSecret != null) {
|
||||
DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
|
||||
messageAndThreadId.second,
|
||||
message.getSender(), message.getMessageBody(),
|
||||
message.isSecureMessage(), message.isKeyExchange());
|
||||
message.getSender(), message.getSenderDeviceId(),
|
||||
message.getMessageBody(), message.isSecureMessage(),
|
||||
message.isKeyExchange());
|
||||
}
|
||||
|
||||
return messageAndThreadId;
|
||||
@@ -106,8 +110,9 @@ public class SmsReceiver {
|
||||
Log.w("SmsReceiver", "Processing prekey message...");
|
||||
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
|
||||
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
|
||||
@@ -142,6 +147,9 @@ public class SmsReceiver {
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
}
|
||||
|
||||
return storeStandardMessage(masterSecret, message);
|
||||
@@ -152,9 +160,10 @@ public class SmsReceiver {
|
||||
{
|
||||
if (masterSecret != null && TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
|
||||
KeyExchangeMessage exchangeMessage = KeyExchangeMessage.createFor(message.getMessageBody());
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, exchangeMessage);
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, exchangeMessage);
|
||||
|
||||
if (processor.isStale(exchangeMessage)) {
|
||||
message.setStale(true);
|
||||
@@ -175,6 +184,9 @@ public class SmsReceiver {
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,11 +46,11 @@ public class SmsSender {
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret, Intent intent) {
|
||||
if (intent.getAction().equals(SendReceiveService.SEND_SMS_ACTION)) {
|
||||
if (SendReceiveService.SEND_SMS_ACTION.equals(intent.getAction())) {
|
||||
handleSendMessage(masterSecret, intent);
|
||||
} else if (intent.getAction().equals(SendReceiveService.SENT_SMS_ACTION)) {
|
||||
} else if (SendReceiveService.SENT_SMS_ACTION.equals(intent.getAction())) {
|
||||
handleSentMessage(intent);
|
||||
} else if (intent.getAction().equals(SendReceiveService.DELIVERED_SMS_ACTION)) {
|
||||
} else if (SendReceiveService.DELIVERED_SMS_ACTION.equals(intent.getAction())) {
|
||||
handleDeliveredMessage(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Parcelable;
|
||||
import android.telephony.SmsMessage;
|
||||
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -24,6 +25,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
|
||||
private final String message;
|
||||
private final String sender;
|
||||
private final int senderDeviceId;
|
||||
private final int protocol;
|
||||
private final String serviceCenterAddress;
|
||||
private final boolean replyPathPresent;
|
||||
@@ -34,6 +36,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
public IncomingTextMessage(SmsMessage message) {
|
||||
this.message = message.getDisplayMessageBody();
|
||||
this.sender = message.getDisplayOriginatingAddress();
|
||||
this.senderDeviceId = RecipientDevice.DEFAULT_DEVICE_ID;
|
||||
this.protocol = message.getProtocolIdentifier();
|
||||
this.serviceCenterAddress = message.getServiceCenterAddress();
|
||||
this.replyPathPresent = message.isReplyPathPresent();
|
||||
@@ -45,6 +48,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
public IncomingTextMessage(IncomingPushMessage message, String encodedBody, String groupId) {
|
||||
this.message = encodedBody;
|
||||
this.sender = message.getSource();
|
||||
this.senderDeviceId = message.getSourceDevice();
|
||||
this.protocol = 31337;
|
||||
this.serviceCenterAddress = "GCM";
|
||||
this.replyPathPresent = true;
|
||||
@@ -56,6 +60,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
public IncomingTextMessage(Parcel in) {
|
||||
this.message = in.readString();
|
||||
this.sender = in.readString();
|
||||
this.senderDeviceId = in.readInt();
|
||||
this.protocol = in.readInt();
|
||||
this.serviceCenterAddress = in.readString();
|
||||
this.replyPathPresent = (in.readInt() == 1);
|
||||
@@ -67,6 +72,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
public IncomingTextMessage(IncomingTextMessage base, String newBody) {
|
||||
this.message = newBody;
|
||||
this.sender = base.getSender();
|
||||
this.senderDeviceId = base.getSenderDeviceId();
|
||||
this.protocol = base.getProtocol();
|
||||
this.serviceCenterAddress = base.getServiceCenterAddress();
|
||||
this.replyPathPresent = base.isReplyPathPresent();
|
||||
@@ -84,6 +90,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
|
||||
this.message = body.toString();
|
||||
this.sender = fragments.get(0).getSender();
|
||||
this.senderDeviceId = fragments.get(0).getSenderDeviceId();
|
||||
this.protocol = fragments.get(0).getProtocol();
|
||||
this.serviceCenterAddress = fragments.get(0).getServiceCenterAddress();
|
||||
this.replyPathPresent = fragments.get(0).isReplyPathPresent();
|
||||
@@ -112,6 +119,10 @@ public class IncomingTextMessage implements Parcelable {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public int getSenderDeviceId() {
|
||||
return senderDeviceId;
|
||||
}
|
||||
|
||||
public int getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
@@ -149,6 +160,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(message);
|
||||
out.writeString(sender);
|
||||
out.writeInt(senderDeviceId);
|
||||
out.writeInt(protocol);
|
||||
out.writeString(serviceCenterAddress);
|
||||
out.writeInt(replyPathPresent ? 1 : 0);
|
||||
|
||||
@@ -29,9 +29,12 @@ import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -153,12 +156,18 @@ public class MmsTransport {
|
||||
}
|
||||
|
||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
|
||||
TextTransport transportDetails = new TextTransport();
|
||||
Recipient recipient = new Recipient(null, recipientString, null, null);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);
|
||||
try {
|
||||
TextTransport transportDetails = new TextTransport();
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
|
||||
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);
|
||||
|
||||
return transportDetails.getEncodedMessage(ciphertextMessage.serialize());
|
||||
return transportDetails.getEncodedMessage(ciphertextMessage.serialize());
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("MmsTransport", e);
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInconsistentResponse(SendReq message, SendConf response) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.util.Log;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
@@ -30,22 +31,24 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.AttachmentCipher;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.push.MismatchedDevices;
|
||||
import org.whispersystems.textsecure.push.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecure.push.OutgoingPushMessage;
|
||||
import org.whispersystems.textsecure.push.OutgoingPushMessageList;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.push.PushAddress;
|
||||
import org.whispersystems.textsecure.push.PushAttachmentData;
|
||||
import org.whispersystems.textsecure.push.PushAttachmentPointer;
|
||||
import org.whispersystems.textsecure.push.PushBody;
|
||||
import org.whispersystems.textsecure.push.PushDestination;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.RateLimitException;
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
@@ -70,79 +73,70 @@ public class PushTransport extends BaseTransport {
|
||||
|
||||
public void deliver(SmsMessageRecord message) throws IOException {
|
||||
try {
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
Recipient recipient = message.getIndividualRecipient();
|
||||
long threadId = message.getThreadId();
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
PushDestination destination = PushDestination.create(context, localNumber,
|
||||
recipient.getNumber());
|
||||
Recipient recipient = message.getIndividualRecipient();
|
||||
long threadId = message.getThreadId();
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
byte[] plaintext = PushMessageContent.newBuilder()
|
||||
.setBody(message.getBody().getBody())
|
||||
.build().toByteArray();
|
||||
|
||||
String plaintextBody = message.getBody().getBody();
|
||||
byte[] plaintext = PushMessageContent.newBuilder().setBody(plaintextBody).build().toByteArray();
|
||||
PushBody pushBody = getEncryptedMessage(socket, threadId, recipient, destination, plaintext);
|
||||
|
||||
socket.sendMessage(destination, pushBody);
|
||||
deliver(socket, recipient, threadId, plaintext);
|
||||
|
||||
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType(), true));
|
||||
|
||||
} catch (UnregisteredUserException e) {
|
||||
Log.w("PushTransport", e);
|
||||
destroySessions(e.getAddresses());
|
||||
//TODO We should probably remove the user from the directory?
|
||||
// destroySessions(message.getIndividualRecipient());
|
||||
throw new IOException("Not push registered after all.");
|
||||
} catch (RateLimitException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Rate limit exceeded.");
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Badly formatted number.");
|
||||
}
|
||||
}
|
||||
|
||||
public void deliver(SendReq message, List<PushDestination> destinations, long threadId)
|
||||
throws IOException
|
||||
{
|
||||
public void deliver(SendReq message, long threadId) throws IOException {
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
byte[] plaintext = getPlaintextMessage(socket, message);
|
||||
String destination = message.getTo()[0].getString();
|
||||
|
||||
Recipients recipients;
|
||||
|
||||
try {
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
String messageBody = PartParser.getMessageText(message.getBody());
|
||||
List<PushBody> pushBodies = new LinkedList<PushBody>();
|
||||
|
||||
for (PushDestination destination : destinations) {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination.getNumber(), false);
|
||||
List<PushAttachmentPointer> attachments = getPushAttachmentPointers(socket, message.getBody());
|
||||
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
|
||||
|
||||
if (messageBody != null) {
|
||||
builder.setBody(messageBody);
|
||||
}
|
||||
|
||||
for (PushAttachmentPointer attachment : attachments) {
|
||||
PushMessageContent.AttachmentPointer.Builder attachmentBuilder =
|
||||
PushMessageContent.AttachmentPointer.newBuilder();
|
||||
|
||||
attachmentBuilder.setId(attachment.getId());
|
||||
attachmentBuilder.setContentType(attachment.getContentType());
|
||||
attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey()));
|
||||
|
||||
builder.addAttachments(attachmentBuilder.build());
|
||||
}
|
||||
|
||||
byte[] plaintext = builder.build().toByteArray();
|
||||
PushBody pushBody = getEncryptedMessage(socket, threadId, recipients.getPrimaryRecipient(), destination, plaintext);
|
||||
|
||||
pushBodies.add(pushBody);
|
||||
if (GroupUtil.isEncodedGroup(destination)) {
|
||||
recipients = DatabaseFactory.getGroupDatabase(context)
|
||||
.getGroupMembers(GroupUtil.getDecodedId(destination));
|
||||
} else {
|
||||
recipients = RecipientFactory.getRecipientsFromString(context, destination, false);
|
||||
}
|
||||
|
||||
socket.sendMessage(destinations, pushBodies);
|
||||
|
||||
} catch (UnregisteredUserException e) {
|
||||
Log.w("PushTransport", e);
|
||||
destroySessions(e.getAddresses());
|
||||
throw new IOException("No push registered after all.");
|
||||
} catch (RateLimitException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Rate limit exceeded.");
|
||||
for (Recipient recipient : recipients.getRecipientsList()) {
|
||||
deliver(socket, recipient, threadId, plaintext);
|
||||
}
|
||||
} catch (UnregisteredUserException uue) {
|
||||
// TODO: We should probably remove the user from the directory?
|
||||
throw new IOException(uue);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Bad destination!");
|
||||
throw new IOException(e);
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deliver(PushServiceSocket socket, Recipient recipient, long threadId, byte[] plaintext)
|
||||
throws IOException, InvalidNumberException
|
||||
{
|
||||
for (int i=0;i<3;i++) {
|
||||
try {
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId,
|
||||
recipient, plaintext);
|
||||
socket.sendMessage(messages);
|
||||
|
||||
return;
|
||||
} catch (MismatchedDevicesException mde) {
|
||||
Log.w("PushTransport", mde);
|
||||
handleMismatchedDevices(socket, threadId, recipient, mde.getMismatchedDevices());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,23 +164,108 @@ public class PushTransport extends BaseTransport {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId, Recipient recipient,
|
||||
PushDestination pushDestination, byte[] plaintext)
|
||||
throws IOException
|
||||
private void handleMismatchedDevices(PushServiceSocket socket, long threadId,
|
||||
Recipient recipient,
|
||||
MismatchedDevices mismatchedDevices)
|
||||
throws InvalidNumberException, IOException
|
||||
{
|
||||
if (!SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
try {
|
||||
PreKeyEntity preKey = socket.getPreKey(pushDestination);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
try {
|
||||
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
|
||||
long recipientId = recipient.getRecipientId();
|
||||
|
||||
for (int extraDeviceId : mismatchedDevices.getExtraDevices()) {
|
||||
PushAddress address = PushAddress.create(context, recipientId, e164number, extraDeviceId);
|
||||
SessionRecordV2.delete(context, address);
|
||||
}
|
||||
|
||||
for (int missingDeviceId : mismatchedDevices.getMissingDevices()) {
|
||||
PushAddress address = PushAddress.create(context, recipientId, e164number, missingDeviceId);
|
||||
PreKeyEntity preKey = socket.getPreKey(address);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, address);
|
||||
|
||||
processor.processKeyExchangeMessage(preKey, threadId);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPlaintextMessage(PushServiceSocket socket, SendReq message) throws IOException {
|
||||
String messageBody = PartParser.getMessageText(message.getBody());
|
||||
List<PushAttachmentPointer> attachments = getPushAttachmentPointers(socket, message.getBody());
|
||||
|
||||
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
|
||||
|
||||
if (GroupUtil.isEncodedGroup(message.getTo()[0].getString())) {
|
||||
PushMessageContent.GroupContext.Builder groupBuilder =
|
||||
PushMessageContent.GroupContext.newBuilder();
|
||||
|
||||
groupBuilder.setType(PushMessageContent.GroupContext.Type.DELIVER);
|
||||
groupBuilder.setId(ByteString.copyFrom(GroupUtil.getDecodedId(message.getTo()[0].getString())));
|
||||
|
||||
builder.setGroup(groupBuilder.build());
|
||||
}
|
||||
|
||||
if (messageBody != null) {
|
||||
builder.setBody(messageBody);
|
||||
}
|
||||
|
||||
for (PushAttachmentPointer attachment : attachments) {
|
||||
PushMessageContent.AttachmentPointer.Builder attachmentBuilder =
|
||||
PushMessageContent.AttachmentPointer.newBuilder();
|
||||
|
||||
attachmentBuilder.setId(attachment.getId());
|
||||
attachmentBuilder.setContentType(attachment.getContentType());
|
||||
attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey()));
|
||||
|
||||
builder.addAttachments(attachmentBuilder.build());
|
||||
}
|
||||
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
|
||||
Recipient recipient, byte[] plaintext)
|
||||
throws IOException, InvalidNumberException
|
||||
{
|
||||
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
|
||||
long recipientId = recipient.getRecipientId();
|
||||
PushAddress masterDevice = PushAddress.create(context, recipientId, e164number, 1);
|
||||
PushBody masterBody = getEncryptedMessage(socket, threadId, masterDevice, plaintext);
|
||||
|
||||
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
|
||||
messages.add(new OutgoingPushMessage(masterDevice, masterBody));
|
||||
|
||||
for (int deviceId : SessionRecordV2.getSessionSubDevices(context, recipient)) {
|
||||
PushAddress device = PushAddress.create(context, recipientId, e164number, deviceId);
|
||||
PushBody body = getEncryptedMessage(socket, threadId, device, plaintext);
|
||||
|
||||
messages.add(new OutgoingPushMessage(device, body));
|
||||
}
|
||||
|
||||
return new OutgoingPushMessageList(e164number, masterDevice.getRelay(), messages);
|
||||
}
|
||||
|
||||
private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId,
|
||||
PushAddress pushAddress, byte[] plaintext)
|
||||
throws IOException
|
||||
{
|
||||
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) {
|
||||
try {
|
||||
List<PreKeyEntity> preKeys = socket.getPreKeys(pushAddress);
|
||||
|
||||
for (PreKeyEntity preKey : preKeys) {
|
||||
PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId());
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, device);
|
||||
|
||||
processor.processKeyExchangeMessage(preKey, threadId);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Invalid PreKey!");
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
SessionCipher cipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
SessionCipher cipher = SessionCipher.createFor(context, masterSecret, pushAddress);
|
||||
CiphertextMessage message = cipher.encrypt(plaintext);
|
||||
|
||||
if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
|
||||
@@ -198,15 +277,7 @@ public class PushTransport extends BaseTransport {
|
||||
}
|
||||
}
|
||||
|
||||
private void destroySessions(List<String> unregisteredUsers) {
|
||||
for (String unregisteredUser : unregisteredUsers) {
|
||||
Log.w("PushTransport", "Destroying session for: " + unregisteredUser);
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, unregisteredUser, false);
|
||||
SessionRecordV2.delete(context, recipients.getPrimaryRecipient());
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("PushTransport", e);
|
||||
}
|
||||
}
|
||||
private void destroySessions(Recipient recipient) {
|
||||
SessionRecordV2.deleteAll(context, recipient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -160,9 +161,10 @@ public class SmsTransport extends BaseTransport {
|
||||
OutgoingTextMessage message)
|
||||
{
|
||||
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
String body = message.getMessageBody();
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
|
||||
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
|
||||
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
|
||||
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
||||
|
||||
@@ -23,21 +23,18 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.directory.Directory;
|
||||
import org.whispersystems.textsecure.directory.NotInDirectoryException;
|
||||
import org.whispersystems.textsecure.push.ContactTokenDetails;
|
||||
import org.whispersystems.textsecure.push.PushDestination;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class UniversalTransport {
|
||||
@@ -85,60 +82,56 @@ public class UniversalTransport {
|
||||
public MmsSendResult deliver(SendReq mediaMessage, long threadId)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
if (Util.isEmpty(mediaMessage.getTo())) {
|
||||
throw new UndeliverableMessageException("No destination specified");
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
List<PushDestination> destinations = getMediaDestinations(mediaMessage);
|
||||
if (isMultipleRecipients(mediaMessage)) {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
|
||||
if (isPushTransport(destinations)) {
|
||||
try {
|
||||
Log.w("UniversalTransport", "Delivering media message with GCM...");
|
||||
pushTransport.deliver(mediaMessage, destinations, threadId);
|
||||
return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("UniversalTransport", ioe);
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
} else {
|
||||
Log.w("UniversalTransport", "Delivering media message with MMS...");
|
||||
if (isPushTransport(mediaMessage.getTo()[0].getString())) {
|
||||
try {
|
||||
Log.w("UniversalTransport", "Delivering media message with GCM...");
|
||||
pushTransport.deliver(mediaMessage, threadId);
|
||||
return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("UniversalTransport", ioe);
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w("UniversalTransport", e);
|
||||
} else {
|
||||
Log.w("UniversalTransport", "Delivering media message with MMS...");
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PushDestination> getMediaDestinations(SendReq mediaMessage)
|
||||
throws InvalidNumberException
|
||||
{
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
LinkedList<PushDestination> destinations = new LinkedList<PushDestination>();
|
||||
public boolean isMultipleRecipients(SendReq mediaMessage) {
|
||||
int recipientCount = 0;
|
||||
|
||||
if (mediaMessage.getTo() != null) {
|
||||
for (EncodedStringValue to : mediaMessage.getTo()) {
|
||||
destinations.add(PushDestination.create(context, localNumber, to.getString()));
|
||||
}
|
||||
recipientCount += mediaMessage.getTo().length;
|
||||
}
|
||||
|
||||
if (mediaMessage.getCc() != null) {
|
||||
for (EncodedStringValue cc : mediaMessage.getCc()) {
|
||||
destinations.add(PushDestination.create(context, localNumber, cc.getString()));
|
||||
}
|
||||
recipientCount += mediaMessage.getCc().length;
|
||||
}
|
||||
|
||||
if (mediaMessage.getBcc() != null) {
|
||||
for (EncodedStringValue bcc : mediaMessage.getBcc()) {
|
||||
destinations.add(PushDestination.create(context, localNumber, bcc.getString()));
|
||||
}
|
||||
recipientCount += mediaMessage.getBcc().length;
|
||||
}
|
||||
|
||||
return destinations;
|
||||
return recipientCount > 1;
|
||||
}
|
||||
|
||||
private boolean isPushTransport(String destination) {
|
||||
if (GroupUtil.isEncodedGroup(destination)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Directory directory = Directory.getInstance(context);
|
||||
|
||||
try {
|
||||
@@ -163,14 +156,4 @@ public class UniversalTransport {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPushTransport(List<PushDestination> destinations) {
|
||||
for (PushDestination destination : destinations) {
|
||||
if (!isPushTransport(destination.getNumber())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
27
src/org/thoughtcrime/securesms/util/GroupUtil.java
Normal file
27
src/org/thoughtcrime/securesms/util/GroupUtil.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GroupUtil {
|
||||
|
||||
private static final String ENCODED_GROUP_PREFIX = "__textsecure_group__!";
|
||||
|
||||
public static String getEncodedId(byte[] groupId) {
|
||||
return ENCODED_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(String groupId) {
|
||||
return groupId.startsWith(ENCODED_GROUP_PREFIX);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,12 @@ public class NumberUtil {
|
||||
return PhoneNumberUtils.isWellFormedSmsAddress(number) || isValidEmail(number);
|
||||
}
|
||||
|
||||
public static boolean isValidSmsOrEmailOrGroup(String number) {
|
||||
return PhoneNumberUtils.isWellFormedSmsAddress(number) ||
|
||||
isValidEmail(number) ||
|
||||
GroupUtil.isEncodedGroup(number);
|
||||
}
|
||||
|
||||
public static String filterNumber(String number) {
|
||||
if (number == null) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user