Major reorganization of view/model interactions.

Mostly, the inheritance graph for MessageRecord/MmsMessageRecord was
all messed up, and each class was overloaded for things it shouldn't
have been.

1) Broke MessageRecord/MmsMessageRecord up into: DisplayRecord, ThreadRecord,
MessageRecord, SmsMessageRecord, NotificationMmsMessageRecord, and
MediaMmsMessageRecord.

2) Updated all the adapters/views to keep pace with that change.
This commit is contained in:
Moxie Marlinspike 2012-10-28 16:04:24 -07:00
parent 0b3e939ac8
commit 3a8d29e279
19 changed files with 793 additions and 611 deletions

View file

@ -30,17 +30,25 @@ import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.mms.MmsFactory;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.SlideDeck;
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.service.MessageNotifier;
import org.thoughtcrime.securesms.util.InvalidMessageException;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduHeaders;
import java.util.LinkedHashMap;
@ -56,6 +64,8 @@ public class ConversationAdapter extends CursorAdapter {
private static final int MAX_CACHE_SIZE = 40;
private final TouchListener touchListener = new TouchListener();
private final LinkedHashMap<String,MessageRecord> messageRecordCache;
private final Handler failedIconClickHandler;
@ -68,7 +78,9 @@ public class ConversationAdapter extends CursorAdapter {
private boolean dataChanged;
public ConversationAdapter(Recipients recipients, long threadId, Context context, MasterSecret masterSecret, Handler failedIconClickHandler) {
public ConversationAdapter(Recipients recipients, long threadId, Context context,
MasterSecret masterSecret, Handler failedIconClickHandler)
{
super(context, null);
this.context = context;
this.recipients = recipients;
@ -85,27 +97,15 @@ public class ConversationAdapter extends CursorAdapter {
MessageNotifier.updateNotification(context, false);
}
private Recipient buildRecipient(String address) {
Recipient recipient;
try {
if (address == null) recipient = recipients.getPrimaryRecipient();
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient();
} catch (RecipientFormattingException e) {
Log.w("ConversationAdapter", e);
recipient = new Recipient("Unknown", "Unknown", null);
}
return recipient;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ConversationItem item = (ConversationItem)view;
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.TRANSPORT));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
((ConversationItem)view).set(masterSecret, messageRecord, failedIconClickHandler);
item.set(masterSecret, messageRecord, failedIconClickHandler);
view.setOnTouchListener(touchListener);
}
@ -135,35 +135,85 @@ public class ConversationAdapter extends CursorAdapter {
private int getItemViewType(Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.TRANSPORT));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
if (messageRecord.isOutgoing()) return 0;
else return 1;
}
private MessageRecord getNewMmsMessageRecord(long messageId, Cursor cursor) {
MessageRecord messageRecord = getNewSmsMessageRecord(messageId, cursor);
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));
long mmsBox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
private MediaMmsMessageRecord getMediaMmsMessageRecord(long messageId, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE));
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
Recipient recipient = getIndividualRecipientFor(null);
SlideDeck slideDeck;
try {
return MmsFactory.getMms(context, masterSecret, messageRecord, mmsType, mmsBox);
MultimediaMessagePdu pdu = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret).getMediaMessage(messageId);
slideDeck = new SlideDeck(context, masterSecret, pdu.getBody());
} catch (MmsException me) {
Log.w("ConversationAdapter", me);
return messageRecord;
slideDeck = null;
}
return new MediaMmsMessageRecord(context, id, recipients, recipient,
date, threadId, slideDeck, box);
}
private MessageRecord getNewSmsMessageRecord(long messageId, Cursor cursor) {
long date = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
Recipient recipient = buildRecipient(address);
MessageRecord messageRecord = new MessageRecord(messageId, recipients, date, type, threadId);
private NotificationMmsMessageRecord getNotificationMmsMessageRecord(long messageId, Cursor cursor) {
Recipient recipient = getIndividualRecipientFor(null);
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE));
messageRecord.setMessageRecipient(recipient);
setBody(cursor, messageRecord);
NotificationInd notification;
try {
notification = DatabaseFactory.getMmsDatabase(context).getNotificationMessage(messageId);
} catch (MmsException me) {
Log.w("ConversationAdapter", me);
notification = new NotificationInd(new PduHeaders());
}
return new NotificationMmsMessageRecord(id, recipients, recipient, date, threadId,
notification.getContentLocation(),
notification.getMessageSize(),
notification.getExpiry(),
notification.getStatus(),
notification.getTransactionId());
}
private SmsMessageRecord getSmsMessageRecord(long messageId, Cursor cursor) {
long date = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
Recipient recipient = getIndividualRecipientFor(address);
// MessageRecord.GroupData groupData = null;
//
// if (recipients != null && recipients.isSingleRecipient()) {
// int groupSize = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SIZE));
// int groupSent = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SENT_COUNT));
// int groupSendFailed = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SEND_FAILED_COUNT));
//
// groupData = new MessageRecord.GroupData(groupSize, groupSent, groupSendFailed);
// }
SmsMessageRecord messageRecord = new SmsMessageRecord(context, messageId, recipients,
recipient, date, type, threadId);
if (body == null) {
body = "";
}
try {
String decryptedBody = MessageDisplayHelper.getDecryptedMessageBody(masterCipher, body);
messageRecord.setBody(decryptedBody);
} catch (InvalidMessageException ime) {
Log.w("ConversationAdapter", ime);
messageRecord.setBody(context.getString(R.string.MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question));
messageRecord.setEmphasis(true);
}
return messageRecord;
}
@ -174,22 +224,35 @@ public class ConversationAdapter extends CursorAdapter {
MessageRecord messageRecord;
if (type.equals("mms")) messageRecord = getNewMmsMessageRecord(messageId, cursor);
else messageRecord = getNewSmsMessageRecord(messageId, cursor);
if (type.equals("mms")) {
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));
if (mmsType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
messageRecord = getNotificationMmsMessageRecord(messageId, cursor);
} else {
messageRecord = getMediaMmsMessageRecord(messageId, cursor);
}
} else {
messageRecord = getSmsMessageRecord(messageId, cursor);
}
messageRecordCache.put(type + messageId, messageRecord);
return messageRecord;
}
protected void setBody(Cursor cursor, MessageRecord message) {
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
private Recipient getIndividualRecipientFor(String address) {
Recipient recipient;
if (body == null)
message.setBody("");
else
MessageDisplayHelper.setDecryptedMessageBody(context, body, message, masterCipher);
try {
if (address == null) recipient = recipients.getPrimaryRecipient();
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient();
} catch (RecipientFormattingException e) {
Log.w("ConversationAdapter", e);
recipient = new Recipient("Unknown", "Unknown", null);
}
return recipient;
}
@Override
protected void onContentChanged() {
super.onContentChanged();

View file

@ -21,8 +21,8 @@ import com.actionbarsherlock.app.SherlockListFragment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.sql.Date;
@ -98,9 +98,8 @@ public class ConversationFragment extends SherlockListFragment
clipboard.setText(body);
}
private void handleDeleteMessage(MessageRecord message) {
private void handleDeleteMessage(final MessageRecord message) {
final long messageId = message.getId();
final String transport = message.isMms() ? "mms" : "sms";
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
@ -111,7 +110,7 @@ public class ConversationFragment extends SherlockListFragment
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (transport.equals("mms")) {
if (message.isMms()) {
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageId);
} else {
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageId);
@ -165,7 +164,8 @@ public class ConversationFragment extends SherlockListFragment
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new ConversationLoader(getActivity(), threadId);
return new ConversationLoader(getActivity(), threadId,
(recipients != null && !recipients.isSingleRecipient()));
}
@Override

View file

@ -43,9 +43,10 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.protocol.Tag;
@ -126,90 +127,27 @@ public class ConversationItem extends LinearLayout {
this.masterSecret = masterSecret;
this.failedIconHandler = failedIconHandler;
// Double-dispatch back to methods below.
messageRecord.setOnConversationItem(this);
setBodyText(messageRecord);
setStatusIcons(messageRecord);
setContactPhoto(messageRecord);
setEvents(messageRecord);
if (messageRecord instanceof NotificationMmsMessageRecord) {
setNotificationMmsAttributes((NotificationMmsMessageRecord)messageRecord);
} else if (messageRecord instanceof MediaMmsMessageRecord) {
setMediaMmsAttributes((MediaMmsMessageRecord)messageRecord);
}
}
public MessageRecord getMessageRecord() {
return messageRecord;
}
public void setMessageRecord(MessageRecord messageRecord) {
setBody(messageRecord);
setStatusIcons(messageRecord);
setEvents(messageRecord);
}
public void setMessageRecord(MmsMessageRecord messageRecord) {
setMessageRecord((MessageRecord)messageRecord);
if (messageRecord.isNotification())
setMmsNotificationAttributes(messageRecord);
else
setMmsMediaAttributes(messageRecord);
}
private void setMmsNotificationAttributes(MmsMessageRecord messageRecord) {
String messageSize = String.format(getContext()
.getString(R.string.ConversationItem_message_size_d_kb),
messageRecord.getMessageSize());
String expires = String.format(getContext()
.getString(R.string.ConversationItem_expires_s),
DateUtils.getRelativeTimeSpanString(getContext(),
messageRecord.getExpiration(),
false));
dateText.setText(messageSize + "\n" + expires);
if (MmsDatabase.Types.isDisplayDownloadButton(messageRecord.getStatus())) {
mmsDownloadButton.setVisibility(View.VISIBLE);
mmsDownloadingLabel.setVisibility(View.GONE);
} else {
mmsDownloadingLabel.setText(MmsDatabase.Types.getLabelForStatus(context, messageRecord.getStatus()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
}
if (MmsDatabase.Types.isHardError(messageRecord.getStatus()))
failedImage.setVisibility(View.VISIBLE);
}
private void setMmsMediaAttributes(MmsMessageRecord messageRecord) {
SlideDeck slideDeck = messageRecord.getSlideDeck();
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> iterator = slides.iterator();
while (iterator.hasNext()) {
Slide slide = iterator.next();
if (slide.hasImage()) {
mmsThumbnail.setImageBitmap(slide.getThumbnail());
mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
mmsThumbnail.setOnLongClickListener(new ThumbnailSaveListener(slide));
mmsThumbnail.setVisibility(View.VISIBLE);
return;
}
}
mmsThumbnail.setVisibility(View.GONE);
}
public void setHandler(Handler failedIconHandler) {
this.failedIconHandler = failedIconHandler;
}
private void checkForAutoInitiate(MessageRecord messageRecord) {
if (AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, messageRecord.getRecipients().getPrimaryRecipient(), messageRecord.getBody(), messageRecord.getThreadId())) {
AutoInitiateActivity.exemptThread(context, messageRecord.getThreadId());
Intent intent = new Intent();
intent.setClass(context, AutoInitiateActivity.class);
intent.putExtra("threadId", messageRecord.getThreadId());
intent.putExtra("masterSecret", masterSecret);
intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient());
context.startActivity(intent);
}
}
/// MessageRecord Attribute Parsers
private void setBodyText(MessageRecord messageRecord) {
String body = messageRecord.getBody();
@ -228,46 +166,12 @@ public class ConversationItem extends LinearLayout {
}
}
private void setContactPhotoForUserIdentity() {
Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri();
if (selfIdentityContact!= null) {
Recipient recipient = RecipientFactory.getRecipientForUri(context, selfIdentityContact);
if (recipient != null) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
return;
}
} else {
contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
}
}
private void setBodyImage(MessageRecord messageRecord) {
final Recipient recipient = messageRecord.getMessageRecipient();
if (!messageRecord.isOutgoing()) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
contactPhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
} else {
Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
context.startActivity(intent);
}
}
});
} else {
private void setContactPhoto(MessageRecord messageRecord) {
if (messageRecord.isOutgoing()) {
setContactPhotoForUserIdentity();
} else {
setContactPhotoForRecipient(messageRecord.getIndividualRecipient());
}
contactPhoto.setVisibility(View.VISIBLE);
}
private void setBody(MessageRecord messageRecord) {
setBodyText(messageRecord);
setBodyImage(messageRecord);
}
private void setStatusIcons(MessageRecord messageRecord) {
@ -289,10 +193,112 @@ public class ConversationItem extends LinearLayout {
private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isKeyExchange() && !messageRecord.isOutgoing());
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient())
checkForAutoInitiate(messageRecord);
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient()) {
checkForAutoInitiate(messageRecord.getIndividualRecipient(),
messageRecord.getBody(),
messageRecord.getThreadId());
}
}
private void setNotificationMmsAttributes(NotificationMmsMessageRecord messageRecord) {
String messageSize = String.format(getContext()
.getString(R.string.ConversationItem_message_size_d_kb),
messageRecord.getMessageSize());
String expires = String.format(getContext()
.getString(R.string.ConversationItem_expires_s),
DateUtils.getRelativeTimeSpanString(getContext(),
messageRecord.getExpiration(),
false));
dateText.setText(messageSize + "\n" + expires);
if (MmsDatabase.Types.isDisplayDownloadButton(messageRecord.getStatus())) {
mmsDownloadButton.setVisibility(View.VISIBLE);
mmsDownloadingLabel.setVisibility(View.GONE);
} else {
mmsDownloadingLabel.setText(MmsDatabase.Types.getLabelForStatus(context, messageRecord.getStatus()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
}
}
private void setMediaMmsAttributes(MediaMmsMessageRecord messageRecord) {
SlideDeck slideDeck = messageRecord.getSlideDeck();
if (slideDeck != null) {
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> iterator = slides.iterator();
while (iterator.hasNext()) {
Slide slide = iterator.next();
if (slide.hasImage()) {
mmsThumbnail.setImageBitmap(slide.getThumbnail());
mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
mmsThumbnail.setOnLongClickListener(new ThumbnailSaveListener(slide));
mmsThumbnail.setVisibility(View.VISIBLE);
return;
}
}
}
mmsThumbnail.setVisibility(View.GONE);
}
/// Helper Methods
private void checkForAutoInitiate(Recipient recipient, String body, long threadId) {
if (AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, recipient,
body, threadId))
{
AutoInitiateActivity.exemptThread(context, threadId);
Intent intent = new Intent();
intent.setClass(context, AutoInitiateActivity.class);
intent.putExtra("threadId", threadId);
intent.putExtra("masterSecret", masterSecret);
intent.putExtra("recipient", recipient);
context.startActivity(intent);
}
}
private void setContactPhotoForUserIdentity() {
Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri();
if (selfIdentityContact!= null) {
Recipient recipient = RecipientFactory.getRecipientForUri(context, selfIdentityContact);
if (recipient != null) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
return;
}
} else {
contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
}
contactPhoto.setVisibility(View.VISIBLE);
}
private void setContactPhotoForRecipient(final Recipient recipient) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
contactPhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
} else {
Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
context.startActivity(intent);
}
}
});
contactPhoto.setVisibility(View.VISIBLE);
}
/// Event handlers
private void handleKeyExchangeClicked() {
Intent intent = new Intent(context, ReceiveKeyActivity.class);
intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient());
@ -452,15 +458,16 @@ public class ConversationItem extends LinearLayout {
private class MmsDownloadClickListener implements View.OnClickListener {
public void onClick(View v) {
Log.w("MmsDownloadClickListener", "Content location: " + new String(((MmsMessageRecord)messageRecord).getContentLocation()));
NotificationMmsMessageRecord notificationRecord = (NotificationMmsMessageRecord)messageRecord;
Log.w("MmsDownloadClickListener", "Content location: " + new String(notificationRecord.getContentLocation()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
Intent intent = new Intent(context, SendReceiveService.class);
intent.putExtra("content_location", new String(((MmsMessageRecord)messageRecord).getContentLocation()));
intent.putExtra("message_id", ((MmsMessageRecord)messageRecord).getId());
intent.putExtra("transaction_id", ((MmsMessageRecord)messageRecord).getTransactionId());
intent.putExtra("thread_id", ((MmsMessageRecord)messageRecord).getThreadId());
intent.putExtra("content_location", new String(notificationRecord.getContentLocation()));
intent.putExtra("message_id", notificationRecord.getId());
intent.putExtra("transaction_id", notificationRecord.getTransactionId());
intent.putExtra("thread_id", notificationRecord.getThreadId());
intent.setAction(SendReceiveService.DOWNLOAD_MMS_ACTION);
context.startService(intent);
}

View file

@ -23,9 +23,8 @@ import android.view.ViewGroup;
import android.widget.CursorAdapter;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
@ -68,28 +67,19 @@ public class ConversationListAdapter extends CursorAdapter {
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
MessageRecord message = new MessageRecord(-1, recipients, date, count, read == 1, threadId);
setBody(cursor, message);
ThreadRecord thread = new ThreadRecord(context, recipients, date, count, read == 1, threadId);
setBody(cursor, thread);
((ConversationListItem)view).set(message, batchMode);
((ConversationListItem)view).set(thread, batchMode);
}
protected void filterBody(MessageRecord message, String body) {
protected void filterBody(ThreadRecord thread, String body) {
if (body == null) body = "(No subject)";
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT) || body.startsWith(Prefix.ASYMMETRIC_ENCRYPT) || body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT)) {
message.setBody(context.getString(R.string.ConversationListAdapter_encrypted_message_enter_passphrase));
message.setEmphasis(true);
} else if (body.startsWith(Prefix.KEY_EXCHANGE)) {
message.setBody(context.getString(R.string.ConversationListAdapter_key_exchange_message));
message.setEmphasis(true);
} else {
message.setBody(body);
}
thread.setBody(body);
}
protected void setBody(Cursor cursor, MessageRecord message) {
filterBody(message, cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)));
protected void setBody(Cursor cursor, ThreadRecord thread) {
filterBody(thread, cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)));
}
public void addToBatchSet(long threadId) {

View file

@ -33,7 +33,7 @@ import android.widget.QuickContactBadge;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.Set;
@ -84,22 +84,22 @@ public class ConversationListItem extends RelativeLayout {
super(context, attrs);
}
public void set(MessageRecord message, boolean batchMode) {
this.recipients = message.getRecipients();
this.threadId = message.getThreadId();
this.fromView.setText(formatFrom(recipients, message.getCount(), message.getRead()));
public void set(ThreadRecord thread, boolean batchMode) {
this.recipients = thread.getRecipients();
this.threadId = thread.getThreadId();
this.fromView.setText(formatFrom(recipients, thread.getCount(), thread.isRead()));
if (message.isKeyExchange())
if (thread.isKeyExchange())
this.subjectView.setText(R.string.ConversationListItem_key_exchange_message,
TextView.BufferType.SPANNABLE);
else
this.subjectView.setText(message.getBody(), TextView.BufferType.SPANNABLE);
this.subjectView.setText(thread.getBody(), TextView.BufferType.SPANNABLE);
if (message.getEmphasis())
if (thread.getEmphasis())
((Spannable)this.subjectView.getText()).setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, this.subjectView.getText().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (message.getDate() > 0)
this.dateView.setText(DateUtils.getRelativeTimeSpanString(getContext(), message.getDate(), false));
if (thread.getDate() > 0)
this.dateView.setText(DateUtils.getRelativeTimeSpanString(getContext(), thread.getDate(), false));
if (selectedThreads != null)
this.checkbox.setChecked(selectedThreads.contains(threadId));

View file

@ -22,8 +22,9 @@ import android.database.Cursor;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.util.InvalidMessageException;
/**
* A ConversationListAdapter that decrypts encrypted message bodies.
@ -43,9 +44,16 @@ public class DecryptingConversationListAdapter extends ConversationListAdapter {
}
@Override
protected void setBody(Cursor cursor, MessageRecord message) {
protected void setBody(Cursor cursor, ThreadRecord thread) {
String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET));
if (body == null || body.equals("")) body = "(No subject)";
MessageDisplayHelper.setDecryptedMessageBody(context, body, message, bodyCipher);
try {
String decryptedBody = MessageDisplayHelper.getDecryptedMessageBody(bodyCipher, body);
thread.setBody(decryptedBody);
} catch (InvalidMessageException ime) {
thread.setBody(context.getString(R.string.MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question));
thread.setEmphasis(true);
}
}
}

View file

@ -48,6 +48,7 @@ class ContactIdentityManagerICS extends ContactIdentityManager {
return true;
}
@SuppressLint("NewApi")
@Override
public List<Long> getSelfIdentityRawContactIds() {
List<Long> results = new LinkedList<Long>();

View file

@ -16,17 +16,11 @@
*/
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.util.InvalidMessageException;
import java.lang.ref.SoftReference;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
public class MessageDisplayHelper {
@ -39,30 +33,6 @@ public class MessageDisplayHelper {
}
};
private static boolean isUnreadableAsymmetricMessage(long type) {
return type == SmsDatabase.Types.FAILED_DECRYPT_TYPE;
}
private static boolean isInProcessAsymmetricMessage(String body, long type) {
return type == SmsDatabase.Types.DECRYPT_IN_PROGRESS_TYPE || (type == 0 && body.startsWith(Prefix.ASYMMETRIC_ENCRYPT)) || (type == 0 && body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT));
}
private static boolean isRogueAsymmetricMessage(long type) {
return type == SmsDatabase.Types.NO_SESSION_TYPE;
}
private static boolean isKeyExchange(String body) {
return body.startsWith(Prefix.KEY_EXCHANGE);
}
private static boolean isProcessedKeyExchange(String body) {
return body.startsWith(Prefix.PROCESSED_KEY_EXCHANGE);
}
private static boolean isStaleKeyExchange(String body) {
return body.startsWith(Prefix.STALE_KEY_EXCHANGE);
}
private static String checkCacheForBody(String body) {
if (decryptedBodyCache.containsKey(body)) {
String decryptedBody = decryptedBodyCache.get(body).get();
@ -77,50 +47,19 @@ public class MessageDisplayHelper {
return null;
}
public static void setDecryptedMessageBody(Context context, String body,
MessageRecord message, MasterCipher bodyCipher)
{
try {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
String cacheResult = checkCacheForBody(body);
if (cacheResult != null) {
body = cacheResult;
} else {
String decryptedBody = bodyCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
decryptedBodyCache.put(body, new SoftReference<String>(decryptedBody));
body = decryptedBody;
}
}
public static String getDecryptedMessageBody(MasterCipher bodyCipher, String body) throws InvalidMessageException {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
String cacheResult = checkCacheForBody(body);
if (isUnreadableAsymmetricMessage(message.getType())) {
message.setBody(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
message.setEmphasis(true);
} else if (isInProcessAsymmetricMessage(body, message.getType())) {
message.setBody(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
message.setEmphasis(true);
} else if (isRogueAsymmetricMessage(message.getType())) {
message.setBody(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
message.setEmphasis(true);
} else if (isKeyExchange(body)) {
message.setKeyExchange(true);
message.setEmphasis(true);
message.setBody(body);
} else if (isProcessedKeyExchange(body)) {
message.setProcessedKeyExchange(true);
message.setEmphasis(true);
message.setBody(body);
} else if (isStaleKeyExchange(body)) {
message.setStaleKeyExchange(true);
message.setEmphasis(true);
message.setBody(body);
} else {
message.setBody(body);
message.setEmphasis(false);
}
} catch (InvalidMessageException ime) {
message.setBody(context.getString(R.string.MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question));
message.setEmphasis(true);
if (cacheResult != null)
return cacheResult;
String decryptedBody = bodyCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
decryptedBodyCache.put(body, new SoftReference<String>(decryptedBody));
return decryptedBody;
}
}
return body;
}
}

View file

@ -1,181 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import org.thoughtcrime.securesms.ConversationItem;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class MessageRecord {
private long id;
private long threadId;
private Recipient messageRecipient;
private Recipients recipients;
private String body;
private long date;
private long count;
private boolean read;
private long type;
private boolean emphasis;
private boolean keyExchange;
private boolean processedKeyExchange;
private boolean staleKeyExchange;
public MessageRecord(MessageRecord copy) {
this.id = copy.id;
this.threadId = copy.threadId;
this.messageRecipient = copy.messageRecipient;
this.recipients = copy.recipients;
this.body = copy.body;
this.date = copy.date;
this.count = copy.count;
this.read = copy.read;
this.type = copy.type;
this.emphasis = copy.emphasis;
this.keyExchange = copy.keyExchange;
this.processedKeyExchange = copy.processedKeyExchange;
}
public MessageRecord(long id, Recipients recipients, long date, long type, long threadId) {
this.id = id;
this.date = date;
this.type = type;
this.recipients = recipients;
this.threadId = threadId;
}
public MessageRecord(long id, Recipients recipients, long date, long count, boolean read, long threadId) {
this.id = id;
this.threadId = threadId;
this.recipients = recipients;
this.date = date;
this.count = count;
this.read = read;
}
public void setOnConversationItem(ConversationItem item) {
item.setMessageRecord(this);
}
public boolean isMms() {
return false;
}
public long getType() {
return type;
}
public void setMessageRecipient(Recipient recipient) {
this.messageRecipient = recipient;
}
public Recipient getMessageRecipient() {
return this.messageRecipient;
}
public void setEmphasis(boolean emphasis) {
this.emphasis = emphasis;
}
public boolean getEmphasis() {
return this.emphasis;
}
public void setId(long id) {
this.id = id;
}
public void setBody(String body) {
this.body = body;
}
public long getThreadId() {
return threadId;
}
public long getId() {
return id;
}
public Recipients getRecipients() {
return recipients;
}
public String getBody() {
return body;
}
public long getDate() {
return date;
}
public long getCount() {
return count;
}
public boolean getRead() {
return read;
}
public boolean isStaleKeyExchange() {
return this.staleKeyExchange;
}
public void setStaleKeyExchange(boolean staleKeyExchange) {
this.staleKeyExchange = staleKeyExchange;
}
public boolean isProcessedKeyExchange() {
return processedKeyExchange;
}
public void setProcessedKeyExchange(boolean processedKeyExchange) {
this.processedKeyExchange = processedKeyExchange;
}
public boolean isKeyExchange() {
return keyExchange || processedKeyExchange || staleKeyExchange;
}
public void setKeyExchange(boolean keyExchange) {
this.keyExchange = keyExchange;
}
public boolean isFailedDecryptType() {
return type == SmsDatabase.Types.FAILED_DECRYPT_TYPE;
}
public boolean isFailed() {
return SmsDatabase.Types.isFailedMessageType(type);
}
public boolean isOutgoing() {
return SmsDatabase.Types.isOutgoingMessageType(type);
}
public boolean isPending() {
return SmsDatabase.Types.isPendingMessageType(type);
}
public boolean isSecure() {
return SmsDatabase.Types.isSecureType(type);
}
}

View file

@ -49,7 +49,7 @@ public class MmsDatabase extends Database {
public static final String TABLE_NAME = "mms";
public static final String ID = "_id";
private static final String THREAD_ID = "thread_id";
private static final String DATE = "date";
public static final String DATE = "date";
public static final String MESSAGE_BOX = "msg_box";
private static final String READ = "read";
private static final String MESSAGE_ID = "m_id";

View file

@ -7,17 +7,24 @@ import android.support.v4.content.CursorLoader;
import org.thoughtcrime.securesms.database.DatabaseFactory;
public class ConversationLoader extends CursorLoader {
private final Context context;
private final long threadId;
// private final boolean isGroupConversation;
public ConversationLoader(Context context, long threadId) {
public ConversationLoader(Context context, long threadId, boolean isGroupConversation) {
super(context);
this.context = context.getApplicationContext();
this.threadId = threadId;
this.context = context.getApplicationContext();
this.threadId = threadId;
// this.isGroupConversation = isGroupConversation;
}
@Override
public Cursor loadInBackground() {
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
// if (!isGroupConversation) {
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
// } else {
// return DatabaseFactory.getMmsSmsDatabase(context).getCollatedGroupConversation(threadId);
// }
}
}

View file

@ -0,0 +1,96 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database.model;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The base class for all message record models. Encapsulates basic data
* shared between ThreadRecord and MessageRecord.
*
* @author Moxie Marlinspike
*
*/
public abstract class DisplayRecord {
private final Recipients recipients;
private final long date;
private final long threadId;
private String body;
protected boolean emphasis;
protected boolean keyExchange;
protected boolean processedKeyExchange;
protected boolean staleKeyExchange;
public DisplayRecord(Recipients recipients, long date, long threadId) {
this.threadId = threadId;
this.recipients = recipients;
this.date = date;
this.emphasis = false;
}
public void setEmphasis(boolean emphasis) {
this.emphasis = emphasis;
}
public boolean getEmphasis() {
return emphasis;
}
public void setBody(String body) {
if (body.startsWith(Prefix.KEY_EXCHANGE)) {
this.keyExchange = true;
this.emphasis = true;
this.body = body;
} else if (body.startsWith(Prefix.PROCESSED_KEY_EXCHANGE)) {
this.processedKeyExchange = true;
this.emphasis = true;
this.body = body;
} else if (body.startsWith(Prefix.STALE_KEY_EXCHANGE)) {
this.staleKeyExchange = true;
this.emphasis = true;
this.body = body;
} else {
this.body = body;
this.emphasis = false;
}
}
public String getBody() {
return body;
}
public Recipients getRecipients() {
return recipients;
}
public long getDate() {
return date;
}
public long getThreadId() {
return threadId;
}
public boolean isKeyExchange() {
return keyExchange || processedKeyExchange || staleKeyExchange;
}
}

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2012 Moxie Marlinspike
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,56 +14,44 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import org.thoughtcrime.securesms.ConversationItem;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.Iterator;
import java.util.List;
public class MmsMessageRecord extends MessageRecord {
/**
* Represents the message record model for MMS messages that contain
* media (ie: they've been downloaded).
*
* @author Moxie Marlinspike
*
*/
private SlideDeck slideDeck;
private byte[] contentLocation;
private long messageSize;
private long expiry;
private boolean isNotification;
private long mailbox;
private int status;
private byte[] transactionId;
public class MediaMmsMessageRecord extends MessageRecord {
public MmsMessageRecord(Context context, MessageRecord record, SlideDeck slideDeck, long mailbox) {
super(record);
this.slideDeck = slideDeck;
this.isNotification = false;
this.mailbox = mailbox;
private final SlideDeck slideDeck;
private final long mailbox;
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
Recipient individualRecipient, long date, long threadId,
SlideDeck slideDeck, long mailbox)
{
super(id, recipients, individualRecipient, date, threadId);
this.slideDeck = slideDeck;
this.mailbox = mailbox;
setBodyIfTextAvailable(context);
}
public MmsMessageRecord(MessageRecord record, byte[] contentLocation, long messageSize, long expiry, int status, byte[] transactionId) {
super(record);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
this.expiry = expiry;
this.isNotification = true;
this.status = status;
this.transactionId = transactionId;
}
public byte[] getTransactionId() {
return transactionId;
}
public int getStatus() {
return this.status;
}
@Override
public boolean isOutgoing() {
return MmsDatabase.Types.isOutgoingMmsBox(mailbox);
@ -84,43 +72,13 @@ public class MmsMessageRecord extends MessageRecord {
return MmsDatabase.Types.isSecureMmsBox(mailbox);
}
// This is the double-dispatch pattern, don't refactor
// this into the base class.
@Override
public void setOnConversationItem(ConversationItem item) {
item.setMessageRecord(this);
}
public byte[] getContentLocation() {
return contentLocation;
}
public long getMessageSize() {
return (messageSize + 1023) / 1024;
}
public long getExpiration() {
return expiry * 1000;
}
public boolean isNotification() {
return isNotification;
}
public SlideDeck getSlideDeck() {
return slideDeck;
}
private void setBodyFromSlidesIfTextAvailable() {
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> i = slides.iterator();
while (i.hasNext()) {
Slide slide = i.next();
if (slide.hasText())
setBody(slide.getText());
}
@Override
public boolean isMms() {
return true;
}
private void setBodyIfTextAvailable(Context context) {
@ -135,7 +93,7 @@ public class MmsMessageRecord extends MessageRecord {
return;
case MmsDatabase.Types.MESSAGE_BOX_NO_SESSION_INBOX:
setBody(context
.getString(R.string.MmsMessageRecord_mms_message_encrypted_for_non_existing_session));
.getString(R.string.MmsMessageRecord_mms_message_encrypted_for_non_existing_session));
setEmphasis(true);
return;
}
@ -143,9 +101,16 @@ public class MmsMessageRecord extends MessageRecord {
setBodyFromSlidesIfTextAvailable();
}
@Override
public boolean isMms() {
return true;
private void setBodyFromSlidesIfTextAvailable() {
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> i = slides.iterator();
while (i.hasNext()) {
Slide slide = i.next();
if (slide.hasText())
setBody(slide.getText());
}
}
}

View file

@ -0,0 +1,83 @@
/**
* Copyright (C) 2012 Moxie Marlinpsike
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database.model;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The base class for message record models that are displayed in
* conversations, as opposed to models that are displayed in a thread list.
* Encapsulates the shared data between both SMS and MMS messages.
*
* @author Moxie Marlinspike
*
*/
public abstract class MessageRecord extends DisplayRecord {
private final Recipient individualRecipient;
private final long id;
public MessageRecord(long id, Recipients recipients,
Recipient individualRecipient,
long date, long threadId)
{
super(recipients, date, threadId);
this.id = id;
this.individualRecipient = individualRecipient;
}
public abstract boolean isOutgoing();
public abstract boolean isFailed();
public abstract boolean isSecure();
public abstract boolean isPending();
public abstract boolean isMms();
public long getId() {
return id;
}
public boolean isStaleKeyExchange() {
return this.staleKeyExchange;
}
public boolean isProcessedKeyExchange() {
return this.processedKeyExchange;
}
public Recipient getIndividualRecipient() {
return individualRecipient;
}
//
// public static class GroupData {
// public final int groupSize;
// public final int groupSentCount;
// public final int groupSendFailedCount;
//
// public GroupData(int groupSize, int groupSentCount, int groupSendFailedCount) {
// this.groupSize = groupSize;
// this.groupSentCount = groupSentCount;
// this.groupSendFailedCount = groupSendFailedCount;
// }
// }
}

View file

@ -0,0 +1,101 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database.model;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* Represents the message record model for MMS messages that are
* notifications (ie: they're pointers to undownloaded media).
*
* @author Moxie Marlinspike
*
*/
public class NotificationMmsMessageRecord extends MessageRecord {
private final byte[] contentLocation;
private final long messageSize;
private final long expiry;
private final int status;
private final byte[] transactionId;
public NotificationMmsMessageRecord(long id, Recipients recipients, Recipient individualRecipient,
long date, long threadId, byte[] contentLocation,
long messageSize, long expiry,
int status, byte[] transactionId)
{
super(id, recipients, individualRecipient, date, threadId);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
this.expiry = expiry;
this.status = status;
this.transactionId = transactionId;
setBody("Multimedia Message");
setEmphasis(true);
}
public byte[] getTransactionId() {
return transactionId;
}
public int getStatus() {
return this.status;
}
public byte[] getContentLocation() {
return contentLocation;
}
public long getMessageSize() {
return (messageSize + 1023) / 1024;
}
public long getExpiration() {
return expiry * 1000;
}
@Override
public boolean isOutgoing() {
return false;
}
@Override
public boolean isFailed() {
return MmsDatabase.Types.isHardError(status);
}
@Override
public boolean isSecure() {
return false;
}
@Override
public boolean isPending() {
return false;
}
@Override
public boolean isMms() {
return true;
}
}

View file

@ -0,0 +1,111 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The message record model which represents standard SMS messages.
*
* @author Moxie Marlinspike
*
*/
public class SmsMessageRecord extends MessageRecord {
private final Context context;
private final long type;
public SmsMessageRecord(Context context, long id,
Recipients recipients,
Recipient individualRecipient,
long date, long type, long threadId)
{
super(id, recipients, individualRecipient, date, threadId);
this.context = context.getApplicationContext();
this.type = type;
}
public long getType() {
return type;
}
@Override
public void setBody(String body) {
if (this.type == SmsDatabase.Types.FAILED_DECRYPT_TYPE) {
super.setBody(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
setEmphasis(true);
} else if (this.type == SmsDatabase.Types.DECRYPT_IN_PROGRESS_TYPE ||
(type == 0 && body.startsWith(Prefix.ASYMMETRIC_ENCRYPT)) ||
(type == 0 && body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT)))
{
super.setBody(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
setEmphasis(true);
} else if (type == SmsDatabase.Types.NO_SESSION_TYPE) {
super.setBody(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
setEmphasis(true);
} else {
super.setBody(body);
}
}
@Override
public boolean isFailed() {
return SmsDatabase.Types.isFailedMessageType(getType());
}
@Override
public boolean isOutgoing() {
return SmsDatabase.Types.isOutgoingMessageType(getType());
}
@Override
public boolean isPending() {
return SmsDatabase.Types.isPendingMessageType(getType());
}
@Override
public boolean isSecure() {
return SmsDatabase.Types.isSecureType(getType());
}
@Override
public boolean isMms() {
return false;
}
public static class GroupData {
public final int groupSize;
public final int groupSentCount;
public final int groupSendFailedCount;
public GroupData(int groupSize, int groupSentCount, int groupSendFailedCount) {
this.groupSize = groupSize;
this.groupSentCount = groupSentCount;
this.groupSendFailedCount = groupSendFailedCount;
}
}
}

View file

@ -0,0 +1,71 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The message record model which represents thread heading messages.
*
* @author Moxie Marlinspike
*
*/
public class ThreadRecord extends DisplayRecord {
private final Context context;
private final long count;
private final boolean read;
public ThreadRecord(Context context, Recipients recipients,
long date, long count,
boolean read, long threadId)
{
super(recipients, date, threadId);
this.context = context.getApplicationContext();
this.count = count;
this.read = read;
}
@Override
public void setBody(String body) {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT) ||
body.startsWith(Prefix.ASYMMETRIC_ENCRYPT) ||
body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT))
{
super.setBody(context.getString(R.string.ConversationListAdapter_encrypted_message_enter_passphrase));
setEmphasis(true);
} else if (body.startsWith(Prefix.KEY_EXCHANGE)) {
super.setBody(context.getString(R.string.ConversationListAdapter_key_exchange_message));
setEmphasis(true);
} else {
super.setBody(body);
}
}
public long getCount() {
return count;
}
public boolean isRead() {
return read;
}
}

View file

@ -1,79 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsMessageRecord;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduHeaders;
public class MmsFactory {
public static MmsMessageRecord getMms(Context context, MasterSecret masterSecret, MessageRecord record, long mmsType, long box) throws MmsException {
Log.w("MmsFactory", "MMS Type: " + mmsType);
if (mmsType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
return getNotificationMmsRecord(context, record);
} else {
return getMediaMmsRecord(context, masterSecret, record, box);
}
}
private static MmsMessageRecord getNotificationMmsRecord(Context context, MessageRecord record) throws MmsException {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
NotificationInd notification = database.getNotificationMessage(record.getId());
return new MmsMessageRecord(record, notification.getContentLocation(), notification.getMessageSize(),
notification.getExpiry(), notification.getStatus(), notification.getTransactionId());
}
private static MmsMessageRecord getMediaMmsRecord(Context context, MasterSecret masterSecret, MessageRecord record, long box) throws MmsException {
MmsDatabase database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
MultimediaMessagePdu msg = database.getMediaMessage(record.getId());
SlideDeck slideDeck = new SlideDeck(context, masterSecret, msg.getBody());
return new MmsMessageRecord(context, record, slideDeck, box);
}
// private static void setBodyIfText(SlideModel slide, MessageRecord record) {
// if ((slide != null) && slide.hasText()) {
// TextModel tm = slide.getText();
//
// if (tm.isDrmProtected()) {
// record.setBody("DRM protected");
// } else {
// record.setBody(tm.getText());
// }
// }
// }
//
// private static SlideshowModel getSlideshowModel(Context context, long messageId) throws MmsException {
// LayoutManager.init(context);
// MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
// MultimediaMessagePdu msg = database.getMediaMessage(messageId);
// return SlideshowModel.createFromPduBody(context, msg.getBody());
// }
}

View file

@ -19,7 +19,7 @@ public class Tag {
}
public static boolean isTagged(String message) {
return message.matches(".*[^\\s]" + WHITESPACE_TAG + "$");
return message != null && message.matches(".*[^\\s]" + WHITESPACE_TAG + "$");
}
public static String getTaggedMessage(String message) {