mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-22 16:07:30 +00:00
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:
parent
0b3e939ac8
commit
3a8d29e279
@ -30,17 +30,25 @@ import org.thoughtcrime.securesms.crypto.MasterCipher;
|
|||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
|
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
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.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.service.MessageNotifier;
|
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.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;
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
@ -56,6 +64,8 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
private static final int MAX_CACHE_SIZE = 40;
|
private static final int MAX_CACHE_SIZE = 40;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final TouchListener touchListener = new TouchListener();
|
private final TouchListener touchListener = new TouchListener();
|
||||||
private final LinkedHashMap<String,MessageRecord> messageRecordCache;
|
private final LinkedHashMap<String,MessageRecord> messageRecordCache;
|
||||||
private final Handler failedIconClickHandler;
|
private final Handler failedIconClickHandler;
|
||||||
@ -68,7 +78,9 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
private boolean dataChanged;
|
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);
|
super(context, null);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.recipients = recipients;
|
this.recipients = recipients;
|
||||||
@ -85,27 +97,15 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
MessageNotifier.updateNotification(context, false);
|
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
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
ConversationItem item = (ConversationItem)view;
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
|
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);
|
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
|
||||||
|
|
||||||
((ConversationItem)view).set(masterSecret, messageRecord, failedIconClickHandler);
|
item.set(masterSecret, messageRecord, failedIconClickHandler);
|
||||||
|
|
||||||
view.setOnTouchListener(touchListener);
|
view.setOnTouchListener(touchListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,35 +135,85 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
private int getItemViewType(Cursor cursor) {
|
private int getItemViewType(Cursor cursor) {
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
|
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);
|
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
|
||||||
|
|
||||||
if (messageRecord.isOutgoing()) return 0;
|
if (messageRecord.isOutgoing()) return 0;
|
||||||
else return 1;
|
else return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageRecord getNewMmsMessageRecord(long messageId, Cursor cursor) {
|
private MediaMmsMessageRecord getMediaMmsMessageRecord(long messageId, Cursor cursor) {
|
||||||
MessageRecord messageRecord = getNewSmsMessageRecord(messageId, cursor);
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
|
||||||
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));
|
long date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE));
|
||||||
long mmsBox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
|
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
|
||||||
|
Recipient recipient = getIndividualRecipientFor(null);
|
||||||
|
|
||||||
|
SlideDeck slideDeck;
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (MmsException me) {
|
||||||
Log.w("ConversationAdapter", me);
|
Log.w("ConversationAdapter", me);
|
||||||
return messageRecord;
|
slideDeck = null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageRecord getNewSmsMessageRecord(long messageId, Cursor cursor) {
|
return new MediaMmsMessageRecord(context, id, recipients, recipient,
|
||||||
|
date, threadId, slideDeck, box);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
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 date = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE));
|
||||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
||||||
|
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||||||
Recipient recipient = buildRecipient(address);
|
Recipient recipient = getIndividualRecipientFor(address);
|
||||||
MessageRecord messageRecord = new MessageRecord(messageId, recipients, date, type, threadId);
|
// 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);
|
||||||
|
|
||||||
messageRecord.setMessageRecipient(recipient);
|
if (body == null) {
|
||||||
setBody(cursor, messageRecord);
|
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;
|
return messageRecord;
|
||||||
}
|
}
|
||||||
@ -174,22 +224,35 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
MessageRecord messageRecord;
|
MessageRecord messageRecord;
|
||||||
|
|
||||||
if (type.equals("mms")) messageRecord = getNewMmsMessageRecord(messageId, cursor);
|
if (type.equals("mms")) {
|
||||||
else messageRecord = getNewSmsMessageRecord(messageId, cursor);
|
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);
|
messageRecordCache.put(type + messageId, messageRecord);
|
||||||
return messageRecord;
|
return messageRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setBody(Cursor cursor, MessageRecord message) {
|
private Recipient getIndividualRecipientFor(String address) {
|
||||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
Recipient recipient;
|
||||||
|
|
||||||
if (body == null)
|
try {
|
||||||
message.setBody("");
|
if (address == null) recipient = recipients.getPrimaryRecipient();
|
||||||
else
|
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient();
|
||||||
MessageDisplayHelper.setDecryptedMessageBody(context, body, message, masterCipher);
|
} catch (RecipientFormattingException e) {
|
||||||
|
Log.w("ConversationAdapter", e);
|
||||||
|
recipient = new Recipient("Unknown", "Unknown", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return recipient;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
protected void onContentChanged() {
|
protected void onContentChanged() {
|
||||||
super.onContentChanged();
|
super.onContentChanged();
|
||||||
|
@ -21,8 +21,8 @@ import com.actionbarsherlock.app.SherlockListFragment;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
|
||||||
import java.sql.Date;
|
import java.sql.Date;
|
||||||
@ -98,9 +98,8 @@ public class ConversationFragment extends SherlockListFragment
|
|||||||
clipboard.setText(body);
|
clipboard.setText(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDeleteMessage(MessageRecord message) {
|
private void handleDeleteMessage(final MessageRecord message) {
|
||||||
final long messageId = message.getId();
|
final long messageId = message.getId();
|
||||||
final String transport = message.isMms() ? "mms" : "sms";
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
|
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() {
|
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
if (transport.equals("mms")) {
|
if (message.isMms()) {
|
||||||
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageId);
|
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageId);
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageId);
|
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageId);
|
||||||
@ -165,7 +164,8 @@ public class ConversationFragment extends SherlockListFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||||
return new ConversationLoader(getActivity(), threadId);
|
return new ConversationLoader(getActivity(), threadId,
|
||||||
|
(recipients != null && !recipients.isSingleRecipient()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,9 +43,10 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
|
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
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.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.protocol.Tag;
|
import org.thoughtcrime.securesms.protocol.Tag;
|
||||||
@ -126,90 +127,27 @@ public class ConversationItem extends LinearLayout {
|
|||||||
this.masterSecret = masterSecret;
|
this.masterSecret = masterSecret;
|
||||||
this.failedIconHandler = failedIconHandler;
|
this.failedIconHandler = failedIconHandler;
|
||||||
|
|
||||||
// Double-dispatch back to methods below.
|
setBodyText(messageRecord);
|
||||||
messageRecord.setOnConversationItem(this);
|
setStatusIcons(messageRecord);
|
||||||
|
setContactPhoto(messageRecord);
|
||||||
|
setEvents(messageRecord);
|
||||||
|
|
||||||
|
if (messageRecord instanceof NotificationMmsMessageRecord) {
|
||||||
|
setNotificationMmsAttributes((NotificationMmsMessageRecord)messageRecord);
|
||||||
|
} else if (messageRecord instanceof MediaMmsMessageRecord) {
|
||||||
|
setMediaMmsAttributes((MediaMmsMessageRecord)messageRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageRecord getMessageRecord() {
|
public MessageRecord getMessageRecord() {
|
||||||
return messageRecord;
|
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) {
|
public void setHandler(Handler failedIconHandler) {
|
||||||
this.failedIconHandler = failedIconHandler;
|
this.failedIconHandler = failedIconHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForAutoInitiate(MessageRecord messageRecord) {
|
/// MessageRecord Attribute Parsers
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBodyText(MessageRecord messageRecord) {
|
private void setBodyText(MessageRecord messageRecord) {
|
||||||
String body = messageRecord.getBody();
|
String body = messageRecord.getBody();
|
||||||
@ -228,46 +166,12 @@ public class ConversationItem extends LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContactPhotoForUserIdentity() {
|
private void setContactPhoto(MessageRecord messageRecord) {
|
||||||
Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri();
|
if (messageRecord.isOutgoing()) {
|
||||||
|
|
||||||
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 {
|
|
||||||
setContactPhotoForUserIdentity();
|
setContactPhotoForUserIdentity();
|
||||||
|
} else {
|
||||||
|
setContactPhotoForRecipient(messageRecord.getIndividualRecipient());
|
||||||
}
|
}
|
||||||
|
|
||||||
contactPhoto.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBody(MessageRecord messageRecord) {
|
|
||||||
setBodyText(messageRecord);
|
|
||||||
setBodyImage(messageRecord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStatusIcons(MessageRecord messageRecord) {
|
private void setStatusIcons(MessageRecord messageRecord) {
|
||||||
@ -289,9 +193,111 @@ public class ConversationItem extends LinearLayout {
|
|||||||
private void setEvents(MessageRecord messageRecord) {
|
private void setEvents(MessageRecord messageRecord) {
|
||||||
setClickable(messageRecord.isKeyExchange() && !messageRecord.isOutgoing());
|
setClickable(messageRecord.isKeyExchange() && !messageRecord.isOutgoing());
|
||||||
|
|
||||||
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient())
|
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient()) {
|
||||||
checkForAutoInitiate(messageRecord);
|
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() {
|
private void handleKeyExchangeClicked() {
|
||||||
Intent intent = new Intent(context, ReceiveKeyActivity.class);
|
Intent intent = new Intent(context, ReceiveKeyActivity.class);
|
||||||
@ -452,15 +458,16 @@ public class ConversationItem extends LinearLayout {
|
|||||||
|
|
||||||
private class MmsDownloadClickListener implements View.OnClickListener {
|
private class MmsDownloadClickListener implements View.OnClickListener {
|
||||||
public void onClick(View v) {
|
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);
|
mmsDownloadButton.setVisibility(View.GONE);
|
||||||
mmsDownloadingLabel.setVisibility(View.VISIBLE);
|
mmsDownloadingLabel.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
Intent intent = new Intent(context, SendReceiveService.class);
|
Intent intent = new Intent(context, SendReceiveService.class);
|
||||||
intent.putExtra("content_location", new String(((MmsMessageRecord)messageRecord).getContentLocation()));
|
intent.putExtra("content_location", new String(notificationRecord.getContentLocation()));
|
||||||
intent.putExtra("message_id", ((MmsMessageRecord)messageRecord).getId());
|
intent.putExtra("message_id", notificationRecord.getId());
|
||||||
intent.putExtra("transaction_id", ((MmsMessageRecord)messageRecord).getTransactionId());
|
intent.putExtra("transaction_id", notificationRecord.getTransactionId());
|
||||||
intent.putExtra("thread_id", ((MmsMessageRecord)messageRecord).getThreadId());
|
intent.putExtra("thread_id", notificationRecord.getThreadId());
|
||||||
intent.setAction(SendReceiveService.DOWNLOAD_MMS_ACTION);
|
intent.setAction(SendReceiveService.DOWNLOAD_MMS_ACTION);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,8 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.CursorAdapter;
|
import android.widget.CursorAdapter;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
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.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
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 count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
|
||||||
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
|
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
|
||||||
|
|
||||||
MessageRecord message = new MessageRecord(-1, recipients, date, count, read == 1, threadId);
|
ThreadRecord thread = new ThreadRecord(context, recipients, date, count, read == 1, threadId);
|
||||||
setBody(cursor, message);
|
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 == null) body = "(No subject)";
|
||||||
|
thread.setBody(body);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setBody(Cursor cursor, MessageRecord message) {
|
protected void setBody(Cursor cursor, ThreadRecord thread) {
|
||||||
filterBody(message, cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)));
|
filterBody(thread, cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToBatchSet(long threadId) {
|
public void addToBatchSet(long threadId) {
|
||||||
|
@ -33,7 +33,7 @@ import android.widget.QuickContactBadge;
|
|||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -84,22 +84,22 @@ public class ConversationListItem extends RelativeLayout {
|
|||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(MessageRecord message, boolean batchMode) {
|
public void set(ThreadRecord thread, boolean batchMode) {
|
||||||
this.recipients = message.getRecipients();
|
this.recipients = thread.getRecipients();
|
||||||
this.threadId = message.getThreadId();
|
this.threadId = thread.getThreadId();
|
||||||
this.fromView.setText(formatFrom(recipients, message.getCount(), message.getRead()));
|
this.fromView.setText(formatFrom(recipients, thread.getCount(), thread.isRead()));
|
||||||
|
|
||||||
if (message.isKeyExchange())
|
if (thread.isKeyExchange())
|
||||||
this.subjectView.setText(R.string.ConversationListItem_key_exchange_message,
|
this.subjectView.setText(R.string.ConversationListItem_key_exchange_message,
|
||||||
TextView.BufferType.SPANNABLE);
|
TextView.BufferType.SPANNABLE);
|
||||||
else
|
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);
|
((Spannable)this.subjectView.getText()).setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, this.subjectView.getText().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
if (message.getDate() > 0)
|
if (thread.getDate() > 0)
|
||||||
this.dateView.setText(DateUtils.getRelativeTimeSpanString(getContext(), message.getDate(), false));
|
this.dateView.setText(DateUtils.getRelativeTimeSpanString(getContext(), thread.getDate(), false));
|
||||||
|
|
||||||
if (selectedThreads != null)
|
if (selectedThreads != null)
|
||||||
this.checkbox.setChecked(selectedThreads.contains(threadId));
|
this.checkbox.setChecked(selectedThreads.contains(threadId));
|
||||||
|
@ -22,8 +22,9 @@ import android.database.Cursor;
|
|||||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
|
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
|
||||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
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.
|
* A ConversationListAdapter that decrypts encrypted message bodies.
|
||||||
@ -43,9 +44,16 @@ public class DecryptingConversationListAdapter extends ConversationListAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setBody(Cursor cursor, MessageRecord message) {
|
protected void setBody(Cursor cursor, ThreadRecord thread) {
|
||||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET));
|
String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET));
|
||||||
if (body == null || body.equals("")) body = "(No subject)";
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ class ContactIdentityManagerICS extends ContactIdentityManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
@Override
|
@Override
|
||||||
public List<Long> getSelfIdentityRawContactIds() {
|
public List<Long> getSelfIdentityRawContactIds() {
|
||||||
List<Long> results = new LinkedList<Long>();
|
List<Long> results = new LinkedList<Long>();
|
||||||
|
@ -16,17 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms.crypto;
|
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.protocol.Prefix;
|
||||||
import org.thoughtcrime.securesms.util.InvalidMessageException;
|
import org.thoughtcrime.securesms.util.InvalidMessageException;
|
||||||
|
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
public class MessageDisplayHelper {
|
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) {
|
private static String checkCacheForBody(String body) {
|
||||||
if (decryptedBodyCache.containsKey(body)) {
|
if (decryptedBodyCache.containsKey(body)) {
|
||||||
String decryptedBody = decryptedBodyCache.get(body).get();
|
String decryptedBody = decryptedBodyCache.get(body).get();
|
||||||
@ -77,50 +47,19 @@ public class MessageDisplayHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setDecryptedMessageBody(Context context, String body,
|
public static String getDecryptedMessageBody(MasterCipher bodyCipher, String body) throws InvalidMessageException {
|
||||||
MessageRecord message, MasterCipher bodyCipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
|
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
|
||||||
String cacheResult = checkCacheForBody(body);
|
String cacheResult = checkCacheForBody(body);
|
||||||
if (cacheResult != null) {
|
|
||||||
body = cacheResult;
|
if (cacheResult != null)
|
||||||
} else {
|
return cacheResult;
|
||||||
|
|
||||||
String decryptedBody = bodyCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
|
String decryptedBody = bodyCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
|
||||||
decryptedBodyCache.put(body, new SoftReference<String>(decryptedBody));
|
decryptedBodyCache.put(body, new SoftReference<String>(decryptedBody));
|
||||||
body = decryptedBody;
|
|
||||||
}
|
return decryptedBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUnreadableAsymmetricMessage(message.getType())) {
|
return body;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -49,7 +49,7 @@ public class MmsDatabase extends Database {
|
|||||||
public static final String TABLE_NAME = "mms";
|
public static final String TABLE_NAME = "mms";
|
||||||
public static final String ID = "_id";
|
public static final String ID = "_id";
|
||||||
private static final String THREAD_ID = "thread_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";
|
public static final String MESSAGE_BOX = "msg_box";
|
||||||
private static final String READ = "read";
|
private static final String READ = "read";
|
||||||
private static final String MESSAGE_ID = "m_id";
|
private static final String MESSAGE_ID = "m_id";
|
||||||
|
@ -7,17 +7,24 @@ import android.support.v4.content.CursorLoader;
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
|
||||||
public class ConversationLoader extends CursorLoader {
|
public class ConversationLoader extends CursorLoader {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final long threadId;
|
private final long threadId;
|
||||||
|
// private final boolean isGroupConversation;
|
||||||
|
|
||||||
public ConversationLoader(Context context, long threadId) {
|
public ConversationLoader(Context context, long threadId, boolean isGroupConversation) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
|
// this.isGroupConversation = isGroupConversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor loadInBackground() {
|
public Cursor loadInBackground() {
|
||||||
|
// if (!isGroupConversation) {
|
||||||
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
|
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
|
||||||
|
// } else {
|
||||||
|
// return DatabaseFactory.getMmsSmsDatabase(context).getCollatedGroupConversation(threadId);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 android.content.Context;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ConversationItem;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
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.Iterator;
|
||||||
import java.util.List;
|
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;
|
public class MediaMmsMessageRecord extends MessageRecord {
|
||||||
private byte[] contentLocation;
|
|
||||||
private long messageSize;
|
|
||||||
private long expiry;
|
|
||||||
private boolean isNotification;
|
|
||||||
private long mailbox;
|
|
||||||
private int status;
|
|
||||||
private byte[] transactionId;
|
|
||||||
|
|
||||||
public MmsMessageRecord(Context context, MessageRecord record, SlideDeck slideDeck, long mailbox) {
|
private final SlideDeck slideDeck;
|
||||||
super(record);
|
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.slideDeck = slideDeck;
|
||||||
this.isNotification = false;
|
|
||||||
this.mailbox = mailbox;
|
this.mailbox = mailbox;
|
||||||
|
|
||||||
setBodyIfTextAvailable(context);
|
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
|
@Override
|
||||||
public boolean isOutgoing() {
|
public boolean isOutgoing() {
|
||||||
return MmsDatabase.Types.isOutgoingMmsBox(mailbox);
|
return MmsDatabase.Types.isOutgoingMmsBox(mailbox);
|
||||||
@ -84,43 +72,13 @@ public class MmsMessageRecord extends MessageRecord {
|
|||||||
return MmsDatabase.Types.isSecureMmsBox(mailbox);
|
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() {
|
public SlideDeck getSlideDeck() {
|
||||||
return slideDeck;
|
return slideDeck;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBodyFromSlidesIfTextAvailable() {
|
@Override
|
||||||
List<Slide> slides = slideDeck.getSlides();
|
public boolean isMms() {
|
||||||
Iterator<Slide> i = slides.iterator();
|
return true;
|
||||||
|
|
||||||
while (i.hasNext()) {
|
|
||||||
Slide slide = i.next();
|
|
||||||
|
|
||||||
if (slide.hasText())
|
|
||||||
setBody(slide.getText());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBodyIfTextAvailable(Context context) {
|
private void setBodyIfTextAvailable(Context context) {
|
||||||
@ -143,9 +101,16 @@ public class MmsMessageRecord extends MessageRecord {
|
|||||||
setBodyFromSlidesIfTextAvailable();
|
setBodyFromSlidesIfTextAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void setBodyFromSlidesIfTextAvailable() {
|
||||||
public boolean isMms() {
|
List<Slide> slides = slideDeck.getSlides();
|
||||||
return true;
|
Iterator<Slide> i = slides.iterator();
|
||||||
|
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Slide slide = i.next();
|
||||||
|
|
||||||
|
if (slide.hasText())
|
||||||
|
setBody(slide.getText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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());
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -19,7 +19,7 @@ public class Tag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isTagged(String message) {
|
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) {
|
public static String getTaggedMessage(String message) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user